mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Merge pull request #2738 from petterip/fix/route-properties-json
fix(pipeline): emit and reconstruct route properties as valid JSON
This commit is contained in:
2 files changed
+318
-40
No files matched your search
@@ -408,61 +408,58 @@ void cbm_pipeline_emit_def_route(cbm_gbuf_t *gb, int64_t func_id, const CBMDefin
|
||||
snprintf(route_qn, sizeof(route_qn), "__route__%s__%s", rm,
|
||||
cbm_route_canon_path(def->route_path, cpath, sizeof(cpath)));
|
||||
char rprops[CBM_SZ_256];
|
||||
snprintf(rprops, sizeof(rprops), "{\"method\":\"%s\",\"source\":\"decorator\"}", rm);
|
||||
char esc_m[CBM_SZ_128];
|
||||
cbm_json_escape(esc_m, sizeof(esc_m), rm);
|
||||
(void)snprintf(rprops, sizeof(rprops), "{\"method\":\"%s\",\"source\":\"decorator\"}", esc_m);
|
||||
int64_t route_id = cbm_gbuf_upsert_node(gb, "Route", def->route_path, route_qn,
|
||||
def->file_path ? def->file_path : rel, 0, 0, rprops);
|
||||
char hprops[CBM_SZ_512];
|
||||
char hprops[CBM_SZ_1K]; /* must exceed escaped value + wrapper or snprintf cuts the
|
||||
closing brace */
|
||||
char esc_h[CBM_SZ_512];
|
||||
cbm_json_escape(esc_h, sizeof(esc_h), def->qualified_name);
|
||||
snprintf(hprops, sizeof(hprops), "{\"handler\":\"%s\"}", esc_h);
|
||||
(void)snprintf(hprops, sizeof(hprops), "{\"handler\":\"%s\"}", esc_h);
|
||||
cbm_gbuf_insert_edge(gb, func_id, route_id, "HANDLES", hprops);
|
||||
}
|
||||
|
||||
/* Phase 2a: Ensure all functions with route_path properties have Route+HANDLES edges.
|
||||
* During incremental indexing, only changed files get Route nodes from extraction.
|
||||
* This pass scans ALL Function/Method nodes and creates missing Route+HANDLES. */
|
||||
/* Extract a JSON string property value into buf. Returns true if found. */
|
||||
static bool extract_json_prop(const char *json, const char *key, char *buf, int bufsz) {
|
||||
if (!json) {
|
||||
/* Extract the string property `key` of a parsed properties object into buf,
|
||||
* with its escapes decoded. Returns true if found: a non-empty string with no
|
||||
* NUL that fits buf. Nothing is copied otherwise. */
|
||||
static bool extract_json_prop(yyjson_val *root, const char *key, char *buf, size_t bufsz) {
|
||||
yyjson_val *val = yyjson_obj_get(root, key);
|
||||
const char *str = yyjson_get_str(val);
|
||||
size_t len = yyjson_get_len(val);
|
||||
if (!str || len == 0 || len >= bufsz || memchr(str, '\0', len)) {
|
||||
return false;
|
||||
}
|
||||
char pattern[CBM_SZ_64];
|
||||
snprintf(pattern, sizeof(pattern), "\"%s\":\"", key);
|
||||
const char *p = strstr(json, pattern);
|
||||
if (!p) {
|
||||
return false;
|
||||
}
|
||||
p += strlen(pattern);
|
||||
const char *end = strchr(p, '"');
|
||||
if (!end || end <= p) {
|
||||
return false;
|
||||
}
|
||||
int len = (int)(end - p);
|
||||
if (len >= bufsz) {
|
||||
return false;
|
||||
}
|
||||
memcpy(buf, p, (size_t)len);
|
||||
buf[len] = '\0';
|
||||
memcpy(buf, str, len + SKIP_ONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Process a single Function/Method node: create Route+HANDLES if it has route_path.
|
||||
* Returns 1 if a new HANDLES edge was created, 0 otherwise. */
|
||||
static int ensure_one_decorator_route(cbm_gbuf_t *gb, const cbm_gbuf_node_t *func) {
|
||||
if (!func->properties_json) {
|
||||
/* Most nodes carry no route: skip them before parsing their properties. */
|
||||
if (!func->properties_json || !strstr(func->properties_json, "\"route_path\"")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Properties hold source text as its bytes, which need not be UTF-8. */
|
||||
yyjson_doc *doc = yyjson_read(func->properties_json, strlen(func->properties_json),
|
||||
YYJSON_READ_ALLOW_INVALID_UNICODE);
|
||||
yyjson_val *root = yyjson_doc_get_root(doc);
|
||||
char path[CBM_SZ_256];
|
||||
if (!extract_json_prop(func->properties_json, "route_path", path, sizeof(path))) {
|
||||
return 0;
|
||||
}
|
||||
if (path[0] != '/') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char method[CBM_SZ_16] = "ANY";
|
||||
extract_json_prop(func->properties_json, "route_method", method, sizeof(method));
|
||||
bool routed = extract_json_prop(root, "route_path", path, sizeof(path)) && path[0] == '/';
|
||||
if (routed) {
|
||||
extract_json_prop(root, "route_method", method, sizeof(method));
|
||||
}
|
||||
yyjson_doc_free(doc);
|
||||
if (!routed) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char route_qn[CBM_ROUTE_QN_SIZE];
|
||||
char cpath[CBM_SZ_256];
|
||||
@@ -471,7 +468,9 @@ static int ensure_one_decorator_route(cbm_gbuf_t *gb, const cbm_gbuf_node_t *fun
|
||||
const cbm_gbuf_node_t *existing = cbm_gbuf_find_by_qn(gb, route_qn);
|
||||
|
||||
char rprops[CBM_SZ_256];
|
||||
snprintf(rprops, sizeof(rprops), "{\"method\":\"%s\",\"source\":\"decorator\"}", method);
|
||||
char esc_m[CBM_SZ_128];
|
||||
cbm_json_escape(esc_m, sizeof(esc_m), method);
|
||||
(void)snprintf(rprops, sizeof(rprops), "{\"method\":\"%s\",\"source\":\"decorator\"}", esc_m);
|
||||
int64_t route_id = cbm_gbuf_upsert_node(gb, "Route", path, route_qn,
|
||||
func->file_path ? func->file_path : "", 0, 0, rprops);
|
||||
|
||||
@@ -486,9 +485,12 @@ static int ensure_one_decorator_route(cbm_gbuf_t *gb, const cbm_gbuf_node_t *fun
|
||||
}
|
||||
}
|
||||
|
||||
char hprops[CBM_SZ_512];
|
||||
snprintf(hprops, sizeof(hprops), "{\"handler\":\"%s\"}",
|
||||
func->qualified_name ? func->qualified_name : "");
|
||||
/* The same properties cbm_pipeline_emit_def_route gives the edge. */
|
||||
char hprops[CBM_SZ_1K]; /* must exceed escaped value + wrapper or snprintf cuts the
|
||||
closing brace */
|
||||
char esc_h[CBM_SZ_512];
|
||||
cbm_json_escape(esc_h, sizeof(esc_h), func->qualified_name ? func->qualified_name : "");
|
||||
(void)snprintf(hprops, sizeof(hprops), "{\"handler\":\"%s\"}", esc_h);
|
||||
cbm_gbuf_insert_edge(gb, func->id, route_id, "HANDLES", hprops);
|
||||
return SKIP_ONE;
|
||||
}
|
||||
@@ -1180,6 +1182,17 @@ typedef struct {
|
||||
int files_seen;
|
||||
} sveltekit_ctx_t;
|
||||
|
||||
/* The properties of a SvelteKit handler's HANDLES edge: its QN, escaped as
|
||||
* every HANDLES edge's handler is. */
|
||||
static void sveltekit_handles_props(char *out, size_t outsz, const cbm_gbuf_node_t *child,
|
||||
bool is_actions) {
|
||||
char esc_h[CBM_SZ_512];
|
||||
cbm_json_escape(esc_h, sizeof(esc_h),
|
||||
child->qualified_name ? child->qualified_name : child->name);
|
||||
(void)snprintf(out, outsz, "{\"handler\":\"%s\",\"framework\":\"sveltekit\"%s}", esc_h,
|
||||
is_actions ? ",\"via\":\"actions_object\"" : "");
|
||||
}
|
||||
|
||||
/* Process one File node: if it matches a SvelteKit server-side file,
|
||||
* synthesise a Route node and HANDLES edges from any handler functions
|
||||
* (or actions Variable) defined in the file. */
|
||||
@@ -1256,10 +1269,9 @@ static void sveltekit_file_visitor(const cbm_gbuf_node_t *node, void *userdata)
|
||||
}
|
||||
ctx->routes_created++;
|
||||
|
||||
char hprops[CBM_SZ_256];
|
||||
snprintf(hprops, sizeof(hprops), "{\"handler\":\"%s\",\"framework\":\"sveltekit\"%s}",
|
||||
child->qualified_name ? child->qualified_name : child->name,
|
||||
is_actions ? ",\"via\":\"actions_object\"" : "");
|
||||
char hprops[CBM_SZ_1K]; /* must exceed escaped value + wrapper or snprintf cuts the
|
||||
closing brace */
|
||||
sveltekit_handles_props(hprops, sizeof(hprops), child, is_actions);
|
||||
cbm_gbuf_insert_edge(ctx->gb, child->id, route_id, "HANDLES", hprops);
|
||||
ctx->handles_created++;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
#include "graph_buffer/graph_buffer.h"
|
||||
#include "pipeline/pipeline_internal.h"
|
||||
#include "service_patterns.h"
|
||||
#include "yyjson/yyjson.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static int has_data_flow(cbm_gbuf_t *gb, int64_t source_id, int64_t target_id) {
|
||||
const cbm_gbuf_edge_t **edges = NULL;
|
||||
@@ -385,6 +387,264 @@ TEST(infrascan_http_calls_join_matching_handler_route) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* An unchanged handler's Route is rebuilt from its stored route metadata. The
|
||||
* metadata is JSON, so a route path with a quote or a backslash (a legal POSIX
|
||||
* directory name) is stored escaped; reading it up to the first quote named a
|
||||
* second Route "/api/a\" beside the extracted one, and the handler QN went into
|
||||
* the HANDLES properties unescaped. Running the pass twice proves the rebuild
|
||||
* finds the Route it made instead of adding another edge. */
|
||||
TEST(infrascan_decorator_route_metadata_keeps_its_escapes) {
|
||||
cbm_gbuf_t *gb = cbm_gbuf_new("test", "/tmp/cbm_infrascan_route_escapes");
|
||||
ASSERT_NOT_NULL(gb);
|
||||
int64_t handler = cbm_gbuf_upsert_node(
|
||||
gb, "Function", "GET", "test.app.api.a\"b.route.GET", "app/api/a\"b/route.ts", 1, 1,
|
||||
"{\"route_path\":\"/api/a\\\"b\\\\c\",\"route_method\":\"GET\"}");
|
||||
ASSERT_GT(handler, 0);
|
||||
|
||||
cbm_pipeline_create_route_nodes(gb);
|
||||
cbm_pipeline_create_route_nodes(gb);
|
||||
|
||||
const cbm_gbuf_node_t **routes = NULL;
|
||||
int route_count = 0;
|
||||
ASSERT_EQ(cbm_gbuf_find_by_label(gb, "Route", &routes, &route_count), 0);
|
||||
ASSERT_EQ(route_count, 1);
|
||||
const cbm_gbuf_node_t *route = cbm_gbuf_find_by_qn(gb, "__route__GET__/api/a\"b\\c");
|
||||
ASSERT_NOT_NULL(route);
|
||||
ASSERT_STR_EQ(route->name, "/api/a\"b\\c");
|
||||
ASSERT_STR_EQ(route->file_path, "app/api/a\"b/route.ts");
|
||||
const cbm_gbuf_edge_t **edges = NULL;
|
||||
int edge_count = 0;
|
||||
ASSERT_EQ(cbm_gbuf_find_edges_by_target_type(gb, route->id, "HANDLES", &edges, &edge_count), 0);
|
||||
ASSERT_EQ(edge_count, 1);
|
||||
ASSERT_EQ(edges[0]->source_id, handler);
|
||||
yyjson_doc *doc = yyjson_read(edges[0]->properties_json, strlen(edges[0]->properties_json), 0);
|
||||
ASSERT_NOT_NULL(doc);
|
||||
const char *qn = yyjson_get_str(yyjson_obj_get(yyjson_doc_get_root(doc), "handler"));
|
||||
bool same_handler = qn && strcmp(qn, "test.app.api.a\"b.route.GET") == 0;
|
||||
yyjson_doc_free(doc);
|
||||
ASSERT_TRUE(same_handler);
|
||||
|
||||
cbm_gbuf_free(gb);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Does the one HANDLES edge into Route route_qn hold properties that parse to
|
||||
* {"handler": qn}? */
|
||||
static bool handles_names_handler(cbm_gbuf_t *gb, const char *route_qn, const char *qn) {
|
||||
const cbm_gbuf_node_t *route = cbm_gbuf_find_by_qn(gb, route_qn);
|
||||
const cbm_gbuf_edge_t **edges = NULL;
|
||||
int edge_count = 0;
|
||||
if (!route ||
|
||||
cbm_gbuf_find_edges_by_target_type(gb, route->id, "HANDLES", &edges, &edge_count) != 0 ||
|
||||
edge_count != 1) {
|
||||
return false;
|
||||
}
|
||||
yyjson_doc *doc = yyjson_read(edges[0]->properties_json, strlen(edges[0]->properties_json), 0);
|
||||
const char *handler = yyjson_get_str(yyjson_obj_get(yyjson_doc_get_root(doc), "handler"));
|
||||
bool same = handler && strcmp(handler, qn) == 0;
|
||||
yyjson_doc_free(doc);
|
||||
return same;
|
||||
}
|
||||
|
||||
/* The HANDLES properties hold the handler's QN escaped. A QN that fills the
|
||||
* escape buffer left the closing `"}` off them, and one malformed properties
|
||||
* row fails every query that reads edge properties. The edge extraction emits
|
||||
* and the one an unchanged handler's metadata rebuilds must both parse. */
|
||||
TEST(infrascan_handles_properties_hold_a_long_handler_qn) {
|
||||
char qn[CBM_SZ_512];
|
||||
memset(qn, 'q', sizeof(qn) - SKIP_ONE);
|
||||
qn[sizeof(qn) - SKIP_ONE] = '\0';
|
||||
|
||||
cbm_gbuf_t *emitted = cbm_gbuf_new("test", "/tmp/cbm_infrascan_long_qn_emit");
|
||||
ASSERT_NOT_NULL(emitted);
|
||||
int64_t handler = cbm_gbuf_upsert_node(emitted, "Function", "GET", qn, "server.ts", 1, 1, "{}");
|
||||
ASSERT_GT(handler, 0);
|
||||
CBMDefinition def = {0};
|
||||
def.qualified_name = qn;
|
||||
def.file_path = "server.ts";
|
||||
def.route_path = "/long";
|
||||
def.route_method = "GET";
|
||||
cbm_pipeline_emit_def_route(emitted, handler, &def, "server.ts");
|
||||
bool emitted_parses = handles_names_handler(emitted, "__route__GET__/long", qn);
|
||||
cbm_gbuf_free(emitted);
|
||||
|
||||
cbm_gbuf_t *rebuilt = cbm_gbuf_new("test", "/tmp/cbm_infrascan_long_qn_rebuild");
|
||||
ASSERT_NOT_NULL(rebuilt);
|
||||
ASSERT_GT(cbm_gbuf_upsert_node(rebuilt, "Function", "GET", qn, "server.ts", 1, 1,
|
||||
"{\"route_path\":\"/long\",\"route_method\":\"GET\"}"),
|
||||
0);
|
||||
cbm_pipeline_create_route_nodes(rebuilt);
|
||||
bool rebuilt_parses = handles_names_handler(rebuilt, "__route__GET__/long", qn);
|
||||
cbm_gbuf_free(rebuilt);
|
||||
|
||||
ASSERT_TRUE(emitted_parses);
|
||||
ASSERT_TRUE(rebuilt_parses);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Does Route route_qn hold properties that parse to {"method": method}? */
|
||||
static bool route_names_method(cbm_gbuf_t *gb, const char *route_qn, const char *method) {
|
||||
const cbm_gbuf_node_t *route = cbm_gbuf_find_by_qn(gb, route_qn);
|
||||
if (!route || !route->properties_json) {
|
||||
return false;
|
||||
}
|
||||
yyjson_doc *doc = yyjson_read(route->properties_json, strlen(route->properties_json), 0);
|
||||
const char *got = yyjson_get_str(yyjson_obj_get(yyjson_doc_get_root(doc), "method"));
|
||||
bool same = got && strcmp(got, method) == 0;
|
||||
yyjson_doc_free(doc);
|
||||
return same;
|
||||
}
|
||||
|
||||
/* A route method is source text where a Python DRF action names it
|
||||
* (`@action(methods=['a"b'])`), so the Route's properties hold it escaped:
|
||||
* both the Route extraction emits and the one rebuilt from the handler's
|
||||
* metadata must parse. */
|
||||
TEST(infrascan_route_properties_escape_the_method) {
|
||||
cbm_gbuf_t *emitted = cbm_gbuf_new("test", "/tmp/cbm_infrascan_method_emit");
|
||||
ASSERT_NOT_NULL(emitted);
|
||||
int64_t handler =
|
||||
cbm_gbuf_upsert_node(emitted, "Function", "act", "test.views.act", "views.py", 1, 1, "{}");
|
||||
ASSERT_GT(handler, 0);
|
||||
CBMDefinition def = {0};
|
||||
def.qualified_name = "test.views.act";
|
||||
def.file_path = "views.py";
|
||||
def.route_path = "/m";
|
||||
def.route_method = "A\"B";
|
||||
cbm_pipeline_emit_def_route(emitted, handler, &def, "views.py");
|
||||
bool emitted_parses = route_names_method(emitted, "__route__A\"B__/m", "A\"B");
|
||||
cbm_gbuf_free(emitted);
|
||||
|
||||
cbm_gbuf_t *rebuilt = cbm_gbuf_new("test", "/tmp/cbm_infrascan_method_rebuild");
|
||||
ASSERT_NOT_NULL(rebuilt);
|
||||
ASSERT_GT(cbm_gbuf_upsert_node(rebuilt, "Function", "act", "test.views.act", "views.py", 1, 1,
|
||||
"{\"route_path\":\"/m\",\"route_method\":\"A\\\"B\"}"),
|
||||
0);
|
||||
cbm_pipeline_create_route_nodes(rebuilt);
|
||||
bool rebuilt_parses = route_names_method(rebuilt, "__route__A\"B__/m", "A\"B");
|
||||
cbm_gbuf_free(rebuilt);
|
||||
|
||||
ASSERT_TRUE(emitted_parses);
|
||||
ASSERT_TRUE(rebuilt_parses);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A SvelteKit +server handler's HANDLES properties hold its QN escaped, as
|
||||
* every other HANDLES edge's do: a quote in a route directory, or a QN longer
|
||||
* than the old 256-byte properties buffer, made them malformed. */
|
||||
TEST(infrascan_sveltekit_handles_properties_escape_the_handler_qn) {
|
||||
char dir[CBM_SZ_512];
|
||||
snprintf(dir, sizeof(dir), "a\"b/%0300d", 0);
|
||||
char file[CBM_SZ_1K];
|
||||
snprintf(file, sizeof(file), "src/routes/%s/+server.ts", dir);
|
||||
char qn[CBM_SZ_1K];
|
||||
snprintf(qn, sizeof(qn), "test.src.routes.%s.+server.GET", dir);
|
||||
|
||||
cbm_gbuf_t *gb = cbm_gbuf_new("test", "/tmp/cbm_infrascan_sveltekit_handles");
|
||||
ASSERT_NOT_NULL(gb);
|
||||
int64_t file_id =
|
||||
cbm_gbuf_upsert_node(gb, "File", "+server.ts", "test.server", file, 1, 1, "{}");
|
||||
int64_t handler = cbm_gbuf_upsert_node(gb, "Function", "GET", qn, file, 1, 1, "{}");
|
||||
ASSERT_GT(file_id, 0);
|
||||
ASSERT_GT(handler, 0);
|
||||
cbm_gbuf_insert_edge(gb, file_id, handler, "DEFINES", "{}");
|
||||
cbm_pipeline_create_route_nodes(gb);
|
||||
|
||||
const cbm_gbuf_edge_t **edges = NULL;
|
||||
int edge_count = 0;
|
||||
bool same = false;
|
||||
if (cbm_gbuf_find_edges_by_source_type(gb, handler, "HANDLES", &edges, &edge_count) == 0 &&
|
||||
edge_count == 1) {
|
||||
yyjson_doc *doc =
|
||||
yyjson_read(edges[0]->properties_json, strlen(edges[0]->properties_json), 0);
|
||||
yyjson_val *root = yyjson_doc_get_root(doc);
|
||||
const char *got = yyjson_get_str(yyjson_obj_get(root, "handler"));
|
||||
const char *framework = yyjson_get_str(yyjson_obj_get(root, "framework"));
|
||||
same = got && strcmp(got, qn) == 0 && framework && strcmp(framework, "sveltekit") == 0;
|
||||
yyjson_doc_free(doc);
|
||||
}
|
||||
cbm_gbuf_free(gb);
|
||||
ASSERT_TRUE(same);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Route metadata that does not parse, is not a top-level string, holds a NUL or
|
||||
* does not fit the path buffer names no Route at all, rather than whatever text
|
||||
* happens to sit between two quotes. The 255-byte path is the longest that fits
|
||||
* and must still route. */
|
||||
TEST(infrascan_decorator_route_metadata_rejects_malformed_values) {
|
||||
cbm_gbuf_t *gb = cbm_gbuf_new("test", "/tmp/cbm_infrascan_route_malformed");
|
||||
ASSERT_NOT_NULL(gb);
|
||||
char longest[CBM_SZ_256 + CBM_SZ_64];
|
||||
char too_long[CBM_SZ_256 + CBM_SZ_64];
|
||||
char path[CBM_SZ_256 + 1];
|
||||
path[0] = '/';
|
||||
memset(path + 1, 'x', CBM_SZ_256 - 1);
|
||||
path[CBM_SZ_256] = '\0';
|
||||
snprintf(too_long, sizeof(too_long), "{\"route_path\":\"%s\"}", path);
|
||||
path[CBM_SZ_256 - 1] = '\0';
|
||||
snprintf(longest, sizeof(longest), "{\"route_path\":\"%s\"}", path);
|
||||
const char *props[] = {
|
||||
"{\"route_path\":\"/truncated\"",
|
||||
"{\"meta\":{\"route_path\":\"/nested\"}}",
|
||||
"{\"route_path\":[\"/array\"]}",
|
||||
"{\"route_path\":42}",
|
||||
"{\"route_path\":\"/a\\u0000b\"}",
|
||||
"{\"route_path\":\"\"}",
|
||||
"{\"route_path\":\"relative\"}",
|
||||
"[\"route_path\",\"/not-an-object\"]",
|
||||
too_long,
|
||||
};
|
||||
const int prop_count = (int)(sizeof(props) / sizeof(props[0]));
|
||||
for (int i = 0; i < prop_count; i++) {
|
||||
char qn[CBM_SZ_32];
|
||||
snprintf(qn, sizeof(qn), "test.bad%d", i);
|
||||
ASSERT_GT(cbm_gbuf_upsert_node(gb, "Function", qn, qn, "server.ts", 1, 1, props[i]), 0);
|
||||
}
|
||||
cbm_pipeline_create_route_nodes(gb);
|
||||
ASSERT_EQ(cbm_gbuf_edge_count_by_type(gb, "HANDLES"), 0);
|
||||
const cbm_gbuf_node_t **routes = NULL;
|
||||
int route_count = 0;
|
||||
ASSERT_EQ(cbm_gbuf_find_by_label(gb, "Route", &routes, &route_count), 0);
|
||||
ASSERT_EQ(route_count, 0);
|
||||
|
||||
int64_t fits =
|
||||
cbm_gbuf_upsert_node(gb, "Function", "fits", "test.fits", "server.ts", 1, 1, longest);
|
||||
ASSERT_GT(fits, 0);
|
||||
cbm_pipeline_create_route_nodes(gb);
|
||||
ASSERT_EQ(cbm_gbuf_find_by_label(gb, "Route", &routes, &route_count), 0);
|
||||
ASSERT_EQ(route_count, 1);
|
||||
ASSERT_STR_EQ(routes[0]->name, path);
|
||||
ASSERT_EQ(cbm_gbuf_edge_count_by_type(gb, "HANDLES"), 1);
|
||||
|
||||
cbm_gbuf_free(gb);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Properties hold source text as its bytes, which need not be UTF-8: a
|
||||
* Latin-1 docstring beside the route metadata, or a Latin-1 route path. The
|
||||
* Route extraction emits for them must come back from the stored metadata too,
|
||||
* as it did before the metadata was parsed. */
|
||||
TEST(infrascan_decorator_route_metadata_survives_invalid_utf8) {
|
||||
cbm_gbuf_t *gb = cbm_gbuf_new("test", "/tmp/cbm_infrascan_route_latin1");
|
||||
ASSERT_NOT_NULL(gb);
|
||||
ASSERT_GT(cbm_gbuf_upsert_node(gb, "Function", "doc", "test.doc", "views.py", 1, 1,
|
||||
"{\"docstring\":\"caf\xe9\",\"route_path\":\"/doc\","
|
||||
"\"route_method\":\"GET\"}"),
|
||||
0);
|
||||
ASSERT_GT(cbm_gbuf_upsert_node(gb, "Function", "GET", "test.app.caf\xe9.route.GET",
|
||||
"app/caf\xe9/route.ts", 1, 1,
|
||||
"{\"route_path\":\"/caf\xe9\",\"route_method\":\"GET\"}"),
|
||||
0);
|
||||
cbm_pipeline_create_route_nodes(gb);
|
||||
const cbm_gbuf_node_t *doc_route = cbm_gbuf_find_by_qn(gb, "__route__GET__/doc");
|
||||
const cbm_gbuf_node_t *dir_route = cbm_gbuf_find_by_qn(gb, "__route__GET__/caf\xe9");
|
||||
bool both = doc_route && dir_route && strcmp(dir_route->name, "/caf\xe9") == 0 &&
|
||||
cbm_gbuf_edge_count_by_type(gb, "HANDLES") == 2;
|
||||
cbm_gbuf_free(gb);
|
||||
ASSERT_TRUE(both);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* FD16: a route's DATA_FLOWS were built from the first 64 callers and the first 32 infra handlers
|
||||
* in edge-insertion order (parallel worker interleaving), so the flow set changed between two
|
||||
* indexes of the same tree and every caller past the 64th lost its flow. */
|
||||
@@ -557,6 +817,12 @@ SUITE(infrascan) {
|
||||
RUN_TEST(infrascan_service_pattern_match_keeps_real_library_qns);
|
||||
RUN_TEST(infrascan_route_nodes_skip_bad_http_url_paths);
|
||||
RUN_TEST(infrascan_http_calls_join_matching_handler_route);
|
||||
RUN_TEST(infrascan_decorator_route_metadata_keeps_its_escapes);
|
||||
RUN_TEST(infrascan_decorator_route_metadata_rejects_malformed_values);
|
||||
RUN_TEST(infrascan_decorator_route_metadata_survives_invalid_utf8);
|
||||
RUN_TEST(infrascan_handles_properties_hold_a_long_handler_qn);
|
||||
RUN_TEST(infrascan_route_properties_escape_the_method);
|
||||
RUN_TEST(infrascan_sveltekit_handles_properties_escape_the_handler_qn);
|
||||
RUN_TEST(infrascan_data_flows_link_every_caller_of_a_route);
|
||||
RUN_TEST(infrascan_data_flows_do_not_depend_on_caller_insertion_order);
|
||||
RUN_TEST(infrascan_data_flows_reach_every_infra_handler);
|
||||
|
||||
Reference in new issue
Block a user