Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions tests/test_mcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -4125,6 +4125,144 @@ TEST(readonly_query_succeeds_on_readonly_fs) {

#undef ROQ_PROJECT

/* ══════════════════════════════════════════════════════════════════
* #823 — CLI/supervised index_repository must preserve name override
* ══════════════════════════════════════════════════════════════════ */

enum {
IDX823_OK = 0,
IDX823_NO_SERVER = 61,
IDX823_NO_RESULT = 62,
IDX823_NOT_INDEXED = 63,
IDX823_RESPONSE_NAME_MISSING = 64,
IDX823_LIST_NAME_MISSING = 65,
IDX823_SEARCH_FAILED = 66,
};

#ifndef _WIN32 /* helper used only by the POSIX fork harness below */
static int idx823_supervised_name_override_check(const char *repo_dir, const char *custom_name) {
/* Match the real CLI/MCP server state: a marked host with the supervisor
* enabled. The worker receives the same args JSON the CLI forwards. */
cbm_index_supervisor_mark_host();
cbm_unsetenv("CBM_INDEX_SUPERVISOR");
cbm_setenv("CBM_INDEX_MAX_RESTARTS", "1", 1);
cbm_setenv("CBM_INDEX_WORKER_TIMEOUT_S", "30", 1);

cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
if (!srv) {
return IDX823_NO_SERVER;
}

char args[1024];
snprintf(args, sizeof(args), "{\"repo_path\":\"%s\",\"mode\":\"fast\",\"name\":\"%s\"}",
repo_dir, custom_name);
char *resp = cbm_mcp_handle_tool(srv, "index_repository", args);
int code = IDX823_OK;
if (!resp) {
code = IDX823_NO_RESULT;
} else if (!response_contains_json_fragment(resp, "\"status\":\"indexed\"")) {
code = IDX823_NOT_INDEXED;
} else {
char expected[256];
snprintf(expected, sizeof(expected), "\"project\":\"%s\"", custom_name);
if (!response_contains_json_fragment(resp, expected)) {
code = IDX823_RESPONSE_NAME_MISSING;
}
}
free(resp);

if (code == IDX823_OK) {
char *projects = cbm_mcp_handle_tool(srv, "list_projects", "{}");
char expected[256];
snprintf(expected, sizeof(expected), "\"name\":\"%s\"", custom_name);
if (!projects || !response_contains_json_fragment(projects, expected)) {
code = IDX823_LIST_NAME_MISSING;
}
free(projects);
}

if (code == IDX823_OK) {
char q[512];
snprintf(q, sizeof(q),
"{\"project\":\"%s\",\"name_pattern\":\"idx823_fn\",\"label\":\"Function\"}",
custom_name);
char *sr = cbm_mcp_handle_tool(srv, "search_graph", q);
if (!sr || !strstr(sr, "idx823_fn")) {
code = IDX823_SEARCH_FAILED;
}
free(sr);
}

cbm_mcp_server_free(srv);
return code;
}
#endif

TEST(index_repository_cli_name_override_issue823) {
#ifdef _WIN32
SKIP_PLATFORM("POSIX fork harness required to isolate supervisor host mark");
#else
char tmp_dir[256];
snprintf(tmp_dir, sizeof(tmp_dir), "/tmp/cbm-idx823-repo-XXXXXX");
if (!cbm_mkdtemp(tmp_dir)) {
FAIL("cbm_mkdtemp repo failed");
}
char cache[256];
snprintf(cache, sizeof(cache), "/tmp/cbm-idx823-cache-XXXXXX");
if (!cbm_mkdtemp(cache)) {
th_rmtree(tmp_dir);
FAIL("cbm_mkdtemp cache failed");
}

const char *saved_cache = getenv("CBM_CACHE_DIR");
char *saved_cache_copy = saved_cache ? strdup(saved_cache) : NULL;
cbm_setenv("CBM_CACHE_DIR", cache, 1);

char src_path[512];
snprintf(src_path, sizeof(src_path), "%s/main.py", tmp_dir);
ASSERT_EQ(th_write_file(src_path, "def idx823_fn():\n return 823\n"), 0);

const char *custom_name = "issue823-custom-project";
int code = -1;
bool signalled = false;
int sig = 0;

fflush(NULL);
pid_t pid = fork();
if (pid == 0) {
alarm(60);
_exit(idx823_supervised_name_override_check(tmp_dir, custom_name));
}
ASSERT_TRUE(pid > 0);
int status = 0;
(void)waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
code = WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
signalled = true;
sig = WTERMSIG(status);
}

char *path_project = cbm_project_name_from_path(tmp_dir);
cleanup_project_db(cache, custom_name);
cleanup_project_db(cache, path_project);
free(path_project);
restore_cache_dir(saved_cache_copy);
free(saved_cache_copy);
th_rmtree(cache);
th_rmtree(tmp_dir);

if (signalled) {
printf(" child killed by signal %d (alarm => worker hang)\n", sig);
} else if (code != IDX823_OK) {
printf(" child exit code %d (64=response name, 65=list name, 66=search)\n", code);
}
ASSERT_FALSE(signalled);
ASSERT_EQ(code, IDX823_OK);
PASS();
#endif
}

/* ══════════════════════════════════════════════════════════════════
* #845 — supervisor gate must not wrap embedders of cbm_mcp_handle_tool
* ══════════════════════════════════════════════════════════════════ */
Expand Down Expand Up @@ -5083,6 +5221,7 @@ SUITE(mcp) {
RUN_TEST(tool_manage_adr_unified_backend_issue256);
RUN_TEST(tool_index_repository_reports_store_backed_adr);
RUN_TEST(tool_index_repository_dot_uses_absolute_project_key_and_preserves_adr);
RUN_TEST(index_repository_cli_name_override_issue823);
RUN_TEST(index_supervisor_gate_requires_marked_host_issue845);
RUN_TEST(index_bg_paths_route_through_supervisor_issue832);
RUN_TEST(index_recovery_parallel_quarantines_crasher);
Expand Down
Loading