mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
Some checks failed
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Devcontainer Smoke / Config-transform unit tests (push) Has been cancelled
Devcontainer Smoke / Build devcontainer image (push) Has been cancelled
* fix(search): index description column for FTS so doc comments are keyword-searchable Closes #2299. descriptionExtractor (#2286) populates the `description` column for every symbol table, but FTS only indexed name+content on 5 tables, so doc-comment keywords (Javadoc/KDoc/godoc/Rust ///) were invisible to BM25 keyword search. - Add `description` to the Function/Class/Method/Interface FTS indexes (File has no description column, left as name+content). - Add FTS indexes for the remaining EMBEDDABLE_LABELS symbol tables (Struct, Enum, Trait, Impl, Macro, Namespace, Constructor, TypeAlias, Typedef, Const, Property, Record, Union, Static, Variable). - createSearchFTSIndexes now drops-then-creates each index so the schema change reaches existing DBs on incremental re-analyze and --repair-fts (createFTSIndex is idempotent-by-name and would otherwise skip stale indexes). Tests: fts-schema column-subset + coverage guards; drop-before-create order; e2e doc-comment keyword search (Java class + Rust struct found by description-only terms). bm25-search assertions derive from FTS_INDEXES. * fix(review): apply autofix feedback - Guard the --repair-fts path on FTS-extension availability before createSearchFTSIndexes drops-then-creates indexes (P1 regression: without the gate, an unavailable extension could drop existing indexes then fail to recreate them, leaving the DB index-less). Mirrors the analyze path's ftsAvailable gate and fails loudly first. - Add a re-analyze upgrade integration test: seed an old name+content-only DB (no Struct index), run the real createSearchFTSIndexes(), and assert description keyword search + the previously un-indexed Struct now resolve. Proves drop-then-create upgrades a live stale index end-to-end. * fix(ci): add loadFTSExtension to --repair-fts test mocks The R3 review fix added a loadFTSExtension availability gate to the --repair-fts path, but run-analyze-fts-repair.test.ts mocked the lbug adapter without that export, so both repair tests threw `No "loadFTSExtension" export`. Add loadFTSExtension to the two mocks (returning true to preserve their original intent) and add a dedicated test proving the guard fails loudly — and does NOT drop any index — when the extension is unavailable. * test(fts): run fts-description-search in the sequential lbug-db project It was the only FTS-index-creating integration test left in the parallel `default` vitest project; every other ftsIndexes-using test (search-core, search-pool, augmentation, …) runs in the `lbug-db` project, which forces fileParallelism: false to avoid LadybugDB native mmap file-lock conflicts in parallel forks (Windows). Add it to the lbug-db include list and the default exclude list to match the convention and remove the flake risk. * test(ci): fail loudly when FTS extension is unavailable, never silently skip FTS-dependent lbug integration suites (search-core, search-pool, augmentation, fts-description-search, …) self-skip via ctx.skip() when the LadybugDB FTS extension can't load, emitting only a console.warn while the job stays green. That means a broken/missing FTS extension in CI would make these integration tests silently vanish with no signal — false confidence. withTestLbugDB now honors GITNEXUS_REQUIRE_FTS=1: when set and the extension is unavailable, setup() throws instead of skipping, so the suite fails loudly. The CI test jobs (ubuntu coverage + windows/macOS cross-platform) set the flag; local/offline runs leave it unset and keep skipping gracefully. (Verified the extension currently loads on all three runners, so this is a guard against regression, not a behavior change today.) * test(ci): run fts-description-search on macOS/Windows cross-platform jobs The new FTS description-search suite was registered in the sequential lbug-db vitest project (ubuntu/coverage) but absent from LBUG_NATIVE, so the macOS/Windows platform-sensitive jobs (which run only the explicit ALL_CROSS_PLATFORM allowlist via run-cross-platform.ts) never executed it. The GITNEXUS_REQUIRE_FTS=1 hardening on those jobs guarded the old FTS fixtures but not the new 20-index/description path. Add the suite to LBUG_NATIVE so the new path is validated cross-platform too. Refs #2299. * fix(search): verify FTS indexes cover description, not just queryability verifySearchFTSIndexes probed each index with QUERY_FTS_INDEX and treated 'queryable' as 'present'. A stale name+content-only index left on a pre-#2299 DB stays queryable yet silently misses the description column, so verification would pass green while doc-comment search stayed broken. Switch to a single CALL SHOW_INDEXES() that exposes property_names per index, and report an index as missing when it is absent OR does not cover its configured columns. Return contract (string[] of table.indexName) is unchanged, so both run-analyze.ts call sites are untouched. The per-index string interpolation is gone, so the now-dead safeIdentifier helper is removed. The real caller of the live function in tests is bm25-search.test.ts (the repair test mocks verifySearchFTSIndexes wholesale); its two probe-shaped cases are rewritten to feed SHOW_INDEXES rows and now assert column coverage, plus an absent-index case. Refs #2299. * test(search): assert description search via the public query surface The #2299 integration suite only exercised the searchFTSFromLbug helper. Add a third block that drives the public LocalBackend.callTool('query') path — which resolves the repo via the registry and routes BM25 through the pool adapter (a different connection context than the core-adapter helper) — and asserts a description-only keyword returns the seeded class. Reuses the existing description-only SEED and production FTS_INDEXES; partial-mocks repo-manager so listRegisteredRepos points at the test DB while cleanupOldKuzuFiles and the rest stay real. Refs #2299. * test(search): make lbug-core-adapter FTS gate honor GITNEXUS_REQUIRE_FTS lbug-core-adapter.test.ts has its own per-test FTS gate (skipUnlessFtsAvailable) that called ctx.skip() whenever the extension could not load — bypassing the GITNEXUS_REQUIRE_FTS=1 hardening that withTestLbugDB already honors. Since this file is in LBUG_NATIVE it runs on the ubuntu/macOS/windows jobs that all set GITNEXUS_REQUIRE_FTS=1, so an FTS regression on a runner would have let these FTS-primitive tests silently vanish from a green run — the exact gap #2299's test-infra hardening set out to close. Make the helper mirror withTestLbugDB: when GITNEXUS_REQUIRE_FTS=1 and the extension is unavailable, throw (hard fail) instead of skipping. Offline/local runs (no env var) still skip gracefully. Refs #2299.
162 lines
7.3 KiB
TypeScript
162 lines
7.3 KiB
TypeScript
/**
|
|
* Integration test for issue #2299: doc-comment text stored in the `description`
|
|
* column must be reachable via keyword (BM25/FTS) search.
|
|
*
|
|
* Drives the *production* `FTS_INDEXES` through the test harness (not a bespoke
|
|
* fixture list), so removing `description` from a symbol table's index — or
|
|
* dropping a table from FTS coverage — fails this test. Seed rows place the
|
|
* searched keywords ONLY in `description` (never in `name`/`content`) to prove
|
|
* the description column is what matches.
|
|
*/
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
import { withTestLbugDB, type IndexedDBHandle } from '../helpers/test-indexed-db.js';
|
|
import { searchFTSFromLbug } from '../../src/core/search/bm25-index.js';
|
|
import { FTS_INDEXES } from '../../src/core/search/fts-schema.js';
|
|
import { LocalBackend } from '../../src/mcp/local/local-backend.js';
|
|
import { listRegisteredRepos } from '../../src/storage/repo-manager.js';
|
|
|
|
// The public query surface (LocalBackend.callTool('query')) resolves the repo
|
|
// via the registry and routes BM25 through the pool adapter, so the third block
|
|
// below needs listRegisteredRepos mocked to point at the test DB. Inert for the
|
|
// two core-adapter blocks — they call searchFTSFromLbug directly (no registry).
|
|
vi.mock('../../src/storage/repo-manager.js', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('../../src/storage/repo-manager.js')>();
|
|
return {
|
|
...actual,
|
|
listRegisteredRepos: vi.fn().mockResolvedValue([]),
|
|
};
|
|
});
|
|
|
|
const SEED = [
|
|
// Java class: Javadoc keywords live in `description`, NOT in name/content.
|
|
`CREATE (n:Class {id: 'class:RetryScheduler', name: 'RetryScheduler', filePath: 'src/RetryScheduler.java', startLine: 5, endLine: 40, isExported: true, content: 'public class RetryScheduler schedules retries', description: 'Implements the circuit breaker pattern for distributed service mesh fault tolerance, isolating failing downstream dependencies to prevent cascade failures'})`,
|
|
// Rust struct: a table with NO FTS index before this change. Keywords live in
|
|
// `description` only — proves the new-table coverage half of the fix.
|
|
`CREATE (n:Struct {id: 'struct:LruShard', name: 'LruShard', filePath: 'src/cache.rs', startLine: 1, endLine: 20, content: 'struct LruShard holds entries', description: 'least recently used eviction policy for a bounded capacity cache'})`,
|
|
];
|
|
|
|
const PRODUCTION_FTS_INDEXES = FTS_INDEXES.map((i) => ({
|
|
table: i.table,
|
|
indexName: i.indexName,
|
|
columns: [...i.properties],
|
|
}));
|
|
|
|
withTestLbugDB(
|
|
'fts-description-search',
|
|
() => {
|
|
describe('description column is keyword-searchable (#2299)', () => {
|
|
it('finds a class by Javadoc keywords present only in description', async () => {
|
|
const { results } = await searchFTSFromLbug('circuit breaker fault tolerance', 20);
|
|
expect(results.map((r) => r.filePath)).toContain('src/RetryScheduler.java');
|
|
});
|
|
|
|
it('still finds the same class by name (no regression)', async () => {
|
|
const { results } = await searchFTSFromLbug('RetryScheduler', 20);
|
|
expect(results.map((r) => r.filePath)).toContain('src/RetryScheduler.java');
|
|
});
|
|
|
|
it('finds a Struct (previously un-indexed table) by its doc-comment keywords', async () => {
|
|
const { results } = await searchFTSFromLbug('least recently used eviction', 20);
|
|
expect(results.map((r) => r.filePath)).toContain('src/cache.rs');
|
|
});
|
|
});
|
|
},
|
|
{
|
|
seed: SEED,
|
|
ftsIndexes: PRODUCTION_FTS_INDEXES,
|
|
},
|
|
);
|
|
|
|
// Second scenario: simulate a pre-#2299 database — FTS indexes built with the old
|
|
// name+content-only schema, and no Struct index at all — then run the real
|
|
// createSearchFTSIndexes() exactly as an incremental re-analyze / --repair-fts
|
|
// would. This proves the drop-then-create behavior actually UPGRADES a live stale
|
|
// index (the whole reason U2 exists): the old code's idempotent-by-name create
|
|
// would skip the existing class_fts and description search would stay broken.
|
|
const OLD_SCHEMA_FTS_INDEXES = [
|
|
{ table: 'File', indexName: 'file_fts', columns: ['name', 'content'] },
|
|
{ table: 'Class', indexName: 'class_fts', columns: ['name', 'content'] },
|
|
// Struct intentionally omitted — pre-#2299 had no FTS index for it.
|
|
];
|
|
|
|
withTestLbugDB(
|
|
'fts-description-reindex-upgrade',
|
|
() => {
|
|
describe('re-analyze upgrades stale FTS indexes so description becomes searchable (#2299)', () => {
|
|
it('finds a class by description keywords after re-indexing an old name+content-only DB', async () => {
|
|
const { results } = await searchFTSFromLbug('circuit breaker fault tolerance', 20);
|
|
expect(results.map((r) => r.filePath)).toContain('src/RetryScheduler.java');
|
|
});
|
|
|
|
it('makes a previously un-indexed table (Struct) searchable after re-indexing', async () => {
|
|
const { results } = await searchFTSFromLbug('least recently used eviction', 20);
|
|
expect(results.map((r) => r.filePath)).toContain('src/cache.rs');
|
|
});
|
|
});
|
|
},
|
|
{
|
|
seed: SEED,
|
|
ftsIndexes: OLD_SCHEMA_FTS_INDEXES,
|
|
afterSetup: async () => {
|
|
// Real production index build over the now-stale DB — drops then recreates.
|
|
const { createSearchFTSIndexes } = await import('../../src/core/search/fts-indexes.js');
|
|
await createSearchFTSIndexes();
|
|
},
|
|
},
|
|
);
|
|
|
|
// Third scenario: prove the description-only keyword is reachable through the
|
|
// PUBLIC query surface (LocalBackend.callTool('query')), not just the
|
|
// searchFTSFromLbug helper the blocks above exercise. callTool resolves the repo
|
|
// via the registry and routes BM25 through the pool adapter — a different
|
|
// connection context than the core-adapter helper — so this block needs
|
|
// poolAdapter + a listRegisteredRepos mock + a LocalBackend built in afterSetup.
|
|
// Reuses the same description-only SEED and production FTS_INDEXES.
|
|
withTestLbugDB(
|
|
'fts-description-search-public-query',
|
|
(handle) => {
|
|
describe('public query surface returns description-only matches (#2299)', () => {
|
|
it('finds a class by description keywords via callTool("query")', async () => {
|
|
const ext = handle as IndexedDBHandle & { _backend?: LocalBackend };
|
|
expect(ext._backend).toBeDefined();
|
|
const backend = ext._backend!;
|
|
|
|
type QuerySymbol = { id: string };
|
|
type QueryResult = {
|
|
error?: unknown;
|
|
definitions?: QuerySymbol[];
|
|
process_symbols?: QuerySymbol[];
|
|
};
|
|
const result: QueryResult = await backend.callTool('query', {
|
|
query: 'circuit breaker fault tolerance',
|
|
});
|
|
|
|
expect(result.error).toBeUndefined();
|
|
const ids = [...(result.process_symbols ?? []), ...(result.definitions ?? [])].map(
|
|
(s) => s.id,
|
|
);
|
|
expect(ids).toContain('class:RetryScheduler');
|
|
});
|
|
});
|
|
},
|
|
{
|
|
seed: SEED,
|
|
ftsIndexes: PRODUCTION_FTS_INDEXES,
|
|
poolAdapter: true,
|
|
afterSetup: async (handle) => {
|
|
vi.mocked(listRegisteredRepos).mockResolvedValue([
|
|
{
|
|
name: 'test-repo',
|
|
path: '/test/repo',
|
|
storagePath: handle.tmpHandle.dbPath,
|
|
indexedAt: new Date().toISOString(),
|
|
lastCommit: 'abc123',
|
|
stats: { files: 1, nodes: 2, communities: 0, processes: 0 },
|
|
},
|
|
]);
|
|
const backend = new LocalBackend();
|
|
await backend.init();
|
|
(handle as IndexedDBHandle & { _backend?: LocalBackend })._backend = backend;
|
|
},
|
|
},
|
|
);
|