From b493b644cf4e77f4ff9bd5b30862b01ba7ed3198 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Fri, 28 Aug 2026 18:33:48 +0200 Subject: [PATCH] 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 --- tests/test_pipeline.c | 66 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index 3f874bd19..1eb10290d 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -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