test(search): end-to-end guard from source prose to a findable body match

The #518/#519 tests so far each cover one layer: extraction produces the
docstring, the store indexes it into nodes_fts.body, the delta merge writes
the same columns, and the query returns Section and Module. Nothing yet
proved the layers connect.

This indexes a real temporary repository -- a README.md whose section body
runs well past the 500-byte cap, plus a META.yaml with a top-level
description -- through the full pipeline, then asserts both prose words are
matchable in the `body` column.

It is also the guard on the size budget. build_def_props drops an oversized
field ATOMICALLY, so a section body that did not fit the 2 KB properties
buffer would vanish without a trace and every narrower test would still
pass.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-08-29 01:04:35 +02:00
parent 01c7e549ef
commit b493b644cf
+66
View File
@@ -12528,6 +12528,71 @@ TEST(pipeline_delta_patch_indexes_docstring_into_fts_body) {
PASS();
}
/* End-to-end for #518/#519: source → docstring → properties JSON → nodes_fts
* `body` → findable. Each layer has its own test; this one proves they connect.
* It is also the guard on the size budget: build_def_props drops an oversized
* field ATOMICALLY, so a 500-byte section body that did not fit the 2 KB
* properties buffer would vanish silently and every narrower test would still
* pass. */
TEST(pipeline_markdown_and_config_prose_reaches_fts_body) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_prose_XXXXXX");
ASSERT_NOT_NULL(cbm_mkdtemp(tmp));
char path[512];
char dbpath[512];
snprintf(dbpath, sizeof(dbpath), "%s/prose.db", tmp);
/* A section body well past the 500-byte cap, so the truncating path — the
* one that produces the longest properties JSON — is what gets indexed. */
snprintf(path, sizeof(path), "%s/README.md", tmp);
FILE *f = fopen(path, "w");
ASSERT_NOT_NULL(f);
fprintf(f, "# Installation\n\nThe phlogiston bootstrap provisions a workstation.\n");
for (int i = 0; i < 40; i++) {
fprintf(f, "Filler prose line %d that pads the section well past the cap.\n", i);
}
fclose(f);
snprintf(path, sizeof(path), "%s/META.yaml", tmp);
f = fopen(path, "w");
ASSERT_NOT_NULL(f);
fprintf(f, "name: widget\ndescription: Aggregates quicksilver telemetry per shard.\n");
fclose(f);
/* One code file so the run is a normal index rather than a docs-only edge. */
snprintf(path, sizeof(path), "%s/main.go", tmp);
f = fopen(path, "w");
ASSERT_NOT_NULL(f);
fprintf(f, "package main\n\nfunc main() {}\n");
fclose(f);
cbm_pipeline_t *p = cbm_pipeline_new(tmp, dbpath, CBM_MODE_FULL);
ASSERT_NOT_NULL(p);
ASSERT_EQ(cbm_pipeline_run(p), 0);
cbm_pipeline_free(p);
cbm_store_t *s = cbm_store_open_path(dbpath);
ASSERT_NOT_NULL(s);
sqlite3_stmt *st = NULL;
ASSERT_EQ(sqlite3_prepare_v2(cbm_store_get_db(s),
"SELECT COUNT(*) FROM nodes_fts WHERE nodes_fts MATCH ?1", -1, &st,
NULL),
SQLITE_OK);
const char *cases[] = {"body:phlogiston", "body:quicksilver"};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
sqlite3_reset(st);
sqlite3_bind_text(st, 1, cases[i], -1, SQLITE_TRANSIENT);
ASSERT_EQ(sqlite3_step(st), SQLITE_ROW);
ASSERT_GT(sqlite3_column_int(st, 0), 0);
}
sqlite3_finalize(st);
cbm_store_close(s);
th_rmtree(tmp);
PASS();
}
SUITE(pipeline) {
RUN_TEST(pipeline_lsp_surface_persisted_and_body_edit_invariant);
/* Index lock */
@@ -12846,6 +12911,7 @@ SUITE(pipeline) {
RUN_TEST(pipeline_ensemble_routing_settings_targets);
RUN_TEST(pipeline_ensemble_routing_unterminated_item_is_safe);
RUN_TEST(pipeline_delta_patch_indexes_docstring_into_fts_body);
RUN_TEST(pipeline_markdown_and_config_prose_reaches_fts_body);
}
/* Focused semantic-manifest and publication contracts. Kept separate from the