GitNexus/gitnexus/test/integration/group/http-route-resolve-symbol.test.ts
azizur100389 7f0ab16ffe
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / Classify release event (push) Waiting to run
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
feat(routes): support JS data route tables (#2972)
2026-08-18 04:39:45 +01:00

111 lines
5.9 KiB
TypeScript

/**
* Issue #2325 (http-route extractor half): `RESOLVE_BY_NAME_QUERY` and
* `RESOLVE_IN_MODULE_QUERY` were converted from the `MATCH (n:A|B)` multi-label
* disjunction to the `labels(n) IN [...]` allowlist form, for consistency with
* the manifest custom-branch fix.
*
* IMPORTANT nuance (verified against the real parser): the actual #2325 failure
* was triggered by *reserved-keyword* labels. LadybugDB's parser rejects a
* disjunction that names a reserved keyword — `Macro` and `Union` both are — so
* the manifest custom branch (whose 21-label list contains `Macro`/`Union`)
* genuinely threw, and the resolver's try/catch swallowed it. The http-route
* 3-label disjunction `(n:Function|Method|CodeElement)` contains no reserved
* keyword and actually PARSES — so this half was a consistency conversion, not a
* parser fix. The value of these cases is verifying the EXPORTED production
* queries resolve and filter correctly against a real LadybugDB (the unit tests
* cover the resolution *logic* with a fake executor; these cover *parsing +
* filtering*). The last case pins the real reserved-keyword trigger so a future
* query that reintroduces a `MATCH (n:…|Macro|…)` disjunction is caught.
*/
import { it, expect, afterEach } from 'vitest';
import {
RESOLVE_BY_NAME_QUERY,
RESOLVE_IN_EXACT_MODULE_QUERY,
} from '../../../src/core/group/extractors/http-route-extractor.js';
import { initLbug, executeParameterized, closeLbug } from '../../../src/core/lbug/pool-adapter.js';
import { withTestLbugDB } from '../../helpers/test-indexed-db.js';
const SEED = [
// BY_NAME: one real-file Function + a same-named File (label excluded) + a
// same-named CodeElement with empty filePath (excluded by `n.filePath <> ''`).
`CREATE (:Function {id:'fn:getOrders', name:'getOrders', filePath:'src/handlers/orders.ts', startLine:1, endLine:9, content:'', description:''})`,
`CREATE (:File {id:'file:getOrders', name:'getOrders', filePath:'src/getOrders.ts'})`,
`CREATE (:CodeElement {id:'ce:getOrders', name:'getOrders', filePath:''})`,
// IN_MODULE: same name in two modules — only the prefixed one resolves.
`CREATE (:Function {id:'fn:listUsers:handlers', name:'listUsers', filePath:'src/handlers/users.ts', startLine:1, endLine:9, content:'', description:''})`,
`CREATE (:Function {id:'fn:listUsers:test-decoy', name:'listUsers', filePath:'src/handlers/users.test.ts', startLine:1, endLine:9, content:'', description:''})`,
`CREATE (:Function {id:'fn:listUsers:admin', name:'listUsers', filePath:'src/admin/users.ts', startLine:1, endLine:9, content:'', description:''})`,
// IN_MODULE label-allowlist decoy: same name, SAME module prefix, wrong label.
// The STARTS-WITH prefix would match it, so only the `labels(n) IN [...]` filter
// excludes it — drop the filter and this surfaces, flipping the row count.
`CREATE (:File {id:'file:listUsers:handlers', name:'listUsers', filePath:'src/handlers/users.ts'})`,
// LIMIT 2 cap: three same-named Functions — the uniqueness count must stay exact.
`CREATE (:Function {id:'fn:dup:1', name:'dup', filePath:'src/a.ts', startLine:1, endLine:9, content:'', description:''})`,
`CREATE (:Function {id:'fn:dup:2', name:'dup', filePath:'src/b.ts', startLine:1, endLine:9, content:'', description:''})`,
`CREATE (:Function {id:'fn:dup:3', name:'dup', filePath:'src/c.ts', startLine:1, endLine:9, content:'', description:''})`,
];
withTestLbugDB(
'issue-2325-http-route-resolveSymbol',
(handle) => {
afterEach(async () => {
try {
await closeLbug(handle.repoId);
} catch {
/* best-effort */
}
});
it('RESOLVE_BY_NAME_QUERY resolves a handler by name, excluding a File and an empty-filePath node', async () => {
await initLbug(handle.repoId, handle.dbPath);
const rows = await executeParameterized(handle.repoId, RESOLVE_BY_NAME_QUERY, {
name: 'getOrders',
});
// File (wrong label) and the empty-filePath CodeElement are both excluded.
expect(rows).toHaveLength(1);
expect(rows[0].uid).toBe('fn:getOrders');
expect(rows[0].filePath).toBe('src/handlers/orders.ts');
});
it('RESOLVE_IN_EXACT_MODULE_QUERY excludes sibling module stems', async () => {
await initLbug(handle.repoId, handle.dbPath);
const rows = await executeParameterized(handle.repoId, RESOLVE_IN_EXACT_MODULE_QUERY, {
name: 'listUsers',
filePaths: ['src/handlers/users.ts', 'src/handlers/users/index.ts'],
});
expect(rows).toHaveLength(1);
expect(rows[0].uid).toBe('fn:listUsers:handlers');
expect(rows[0].filePath).toBe('src/handlers/users.ts');
});
it('RESOLVE_BY_NAME_QUERY caps materialization at 2 rows (uniqueness count stays exact)', async () => {
await initLbug(handle.repoId, handle.dbPath);
const rows = await executeParameterized(handle.repoId, RESOLVE_BY_NAME_QUERY, {
name: 'dup',
});
// Three matches exist; LIMIT 2 returns exactly two so the caller treats it
// as ambiguous (>=2) without over-materializing.
expect(rows).toHaveLength(2);
});
it('LadybugDB rejects a disjunction naming a reserved-keyword label (the real #2325 trigger)', async () => {
await initLbug(handle.repoId, handle.dbPath);
// The genuine #2325 failure: a `MATCH (n:A|B)` disjunction whose label set
// includes a reserved keyword (`Macro`/`Union`) is a parser error the
// resolver's try/catch silently swallowed. The exported queries avoid this
// by using the `labels(n) IN [...]` allowlist form. (The http-route
// `Function|Method|CodeElement` form parses — this guards the real cause.)
await expect(
executeParameterized(
handle.repoId,
`MATCH (n:Function|Macro|Union) WHERE n.name = $name RETURN n.id AS uid LIMIT 2`,
{ name: 'getOrders' },
),
).rejects.toThrow(/Parser exception|Invalid input|Prepare failed/i);
});
},
{
seed: SEED,
poolAdapter: true,
},
);