mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(pipeline): format the arg-URL verb edge and test the path that reaches it (#2768)
clang-format the new snprintf in detect_url_in_args (the line ran past 100 columns, the CI lint failure) and raise eprops to 1K: two escaped 255-byte strings plus the keys and the verb no longer fit in 512. Add a parallel-route test with wrapper calls that actually reach the arg-URL heuristic: a project wrapper whose verb is not a route suffix (this.api.head / this.api.options, URL first) and a route-verb wrapper whose URL sits in a keyword the registration reader does not take as a path (self.session.get(resource=...)). Each gets one HTTP_CALLS edge typed with its verb and no ANY twin. The sequential resolver has no arg-URL heuristic (#2848), so the test is parallel only. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
b9bd036602
commit
6b6213e8cc
2 files changed
+104
-4
No files matched your search
@@ -2610,11 +2610,14 @@ static void detect_url_in_args(cbm_gbuf_t *gbuf, const cbm_gbuf_node_t *source,
|
||||
char esc_n[CBM_SZ_256];
|
||||
cbm_json_escape(esc_c, sizeof(esc_c), call->callee_name);
|
||||
cbm_json_escape(esc_n, sizeof(esc_n), norm);
|
||||
char eprops[CBM_SZ_512];
|
||||
/* Two escaped 255-byte strings plus the fixed keys and the verb: 1K
|
||||
* never truncates the JSON (512 could, once the method was added). */
|
||||
char eprops[CBM_SZ_1K];
|
||||
if (method) {
|
||||
snprintf(eprops, sizeof(eprops),
|
||||
"{\"callee\":\"%s\",\"url_path\":\"%s\",\"via\":\"arg_url\",\"method\":\"%s\"}",
|
||||
esc_c, esc_n, method);
|
||||
snprintf(
|
||||
eprops, sizeof(eprops),
|
||||
"{\"callee\":\"%s\",\"url_path\":\"%s\",\"via\":\"arg_url\",\"method\":\"%s\"}",
|
||||
esc_c, esc_n, method);
|
||||
} else {
|
||||
snprintf(eprops, sizeof(eprops),
|
||||
"{\"callee\":\"%s\",\"url_path\":\"%s\",\"via\":\"arg_url\"}", esc_c, esc_n);
|
||||
|
||||
@@ -15040,6 +15040,102 @@ TEST(pipeline_angular_http_client_typed_sequential) {
|
||||
return angular_http_client_typed_case(0);
|
||||
}
|
||||
|
||||
/* #2768: the arg-URL heuristic (detect_url_in_args, via=arg_url) covers calls
|
||||
* no service pattern knows, and it now types the edge with the verb the callee
|
||||
* names instead of minting a method-less __route__ANY__ route. Two wrapper
|
||||
* shapes reach it on the parallel resolver:
|
||||
* - a project wrapper method whose verb is not a route-registration suffix
|
||||
* (`this.api.head('/items/health')`, `.options(...)`), resolved to the
|
||||
* project method, so the URL may be the first argument;
|
||||
* - a route-verb wrapper (`.get`) whose URL is not route shaped for the
|
||||
* registration reader: here a keyword it does not read as a path
|
||||
* (`self.session.get(resource="/items/list")`).
|
||||
* Parallel only: the sequential resolver has no arg-URL heuristic (#2848). */
|
||||
TEST(pipeline_arg_url_wrapper_call_typed_with_verb_issue2768) {
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_arg_url_verb_XXXXXX");
|
||||
if (!cbm_mkdtemp(tmp)) {
|
||||
FAIL("tmpdir");
|
||||
}
|
||||
write_temp_file(tmp, "src/items.api.ts",
|
||||
"export class ItemsApi {\n"
|
||||
" head(path: string): Promise<Response> {\n"
|
||||
" return fetch(path, { method: 'HEAD' });\n"
|
||||
" }\n"
|
||||
" options(path: string): Promise<Response> {\n"
|
||||
" return fetch(path, { method: 'OPTIONS' });\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
write_temp_file(tmp, "src/item.service.ts",
|
||||
"import { ItemsApi } from './items.api';\n"
|
||||
"export class ItemService {\n"
|
||||
" constructor(private readonly api: ItemsApi) {}\n"
|
||||
" pingItems() {\n"
|
||||
" return this.api.head('/items/health');\n"
|
||||
" }\n"
|
||||
" probeItems() {\n"
|
||||
" return this.api.options('/items/list');\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
write_temp_file(tmp, "src/reader.py",
|
||||
"class ItemsSession:\n"
|
||||
" def get(self, *args, **kwargs):\n"
|
||||
" return args\n"
|
||||
"\n"
|
||||
"class Reader:\n"
|
||||
" def __init__(self):\n"
|
||||
" self.session = ItemsSession()\n"
|
||||
"\n"
|
||||
" def read_items(self):\n"
|
||||
" return self.session.get(resource=\"/items/list\")\n");
|
||||
for (int i = 0; i < 52; i++) {
|
||||
char name[64];
|
||||
char body[128];
|
||||
snprintf(name, sizeof(name), "src/filler%d.ts", i);
|
||||
snprintf(body, sizeof(body), "export function filler%d(): number {\n return %d;\n}\n", i,
|
||||
i);
|
||||
write_temp_file(tmp, name, body);
|
||||
}
|
||||
char *old_workers = getenv("CBM_WORKERS");
|
||||
char *saved = old_workers ? strdup(old_workers) : NULL;
|
||||
cbm_setenv("CBM_WORKERS", "4", 1);
|
||||
char db_path[512];
|
||||
snprintf(db_path, sizeof(db_path), "%s/arg_url_verb.db", tmp);
|
||||
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
|
||||
ASSERT_NOT_NULL(p);
|
||||
ASSERT_EQ(cbm_pipeline_run(p), 0);
|
||||
const char *project = cbm_pipeline_project_name(p);
|
||||
cbm_store_t *s = cbm_store_open_path(db_path);
|
||||
ASSERT_NOT_NULL(s);
|
||||
|
||||
static const struct {
|
||||
const char *caller;
|
||||
const char *typed_route;
|
||||
} wrapped[] = {{"pingItems", "__route__HEAD__/items/health"},
|
||||
{"probeItems", "__route__OPTIONS__/items/list"},
|
||||
{"read_items", "__route__GET__/items/list"}};
|
||||
for (size_t i = 0; i < sizeof(wrapped) / sizeof(wrapped[0]); i++) {
|
||||
/* The edge carries the callee's verb ... */
|
||||
ASSERT_EQ(http_calls_to_route(s, project, wrapped[i].caller, wrapped[i].typed_route), 1);
|
||||
/* ... and is the call site's only HTTP_CALLS edge: no ANY twin. */
|
||||
ASSERT_EQ(http_calls_to_route(s, project, wrapped[i].caller, "__route__ANY__"), 0);
|
||||
ASSERT_EQ(http_calls_to_route(s, project, wrapped[i].caller, "__route__"), 1);
|
||||
}
|
||||
/* Not a route registration */
|
||||
ASSERT_EQ(cbm_store_count_edges_by_type(s, project, "HANDLES"), 0);
|
||||
|
||||
cbm_store_close(s);
|
||||
cbm_pipeline_free(p);
|
||||
if (saved) {
|
||||
cbm_setenv("CBM_WORKERS", saved, 1);
|
||||
free(saved);
|
||||
} else {
|
||||
cbm_unsetenv("CBM_WORKERS");
|
||||
}
|
||||
th_rmtree(tmp);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #706 / #1147 end to end: a URL composed from module constants reaches the
|
||||
* HTTP classifier, so the client call forms an HTTP_CALLS edge. Python
|
||||
* `API_URL = API_BASE_URL + "/orders/create"` (env-derived base, literal path
|
||||
@@ -29486,6 +29582,7 @@ SUITE(pipeline) {
|
||||
RUN_TEST(pipeline_typed_http_call_has_no_arg_url_twin_sequential);
|
||||
RUN_TEST(pipeline_angular_http_client_typed_parallel);
|
||||
RUN_TEST(pipeline_angular_http_client_typed_sequential);
|
||||
RUN_TEST(pipeline_arg_url_wrapper_call_typed_with_verb_issue2768);
|
||||
RUN_TEST(pipeline_url_constant_folding_http_calls_issue706_1147);
|
||||
RUN_TEST(pipeline_folded_topics_preserve_identity_sequential_parallel);
|
||||
RUN_TEST(pipeline_relative_folded_wrapper_url_candidates);
|
||||
|
||||
Reference in new issue
Block a user