mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
Some checks are pending
CI / quality (push) Waiting to run
CI / tests (push) Waiting to run
CI / e2e (push) Waiting to run
CI / scope-parity (push) Waiting to run
CI / Save PR Metadata (push) Blocked by required conditions
CI / CI Gate (push) Blocked by required conditions
Release Candidate / Check if release candidate should run (push) Waiting to run
Release Candidate / ci (push) Blocked by required conditions
Release Candidate / Publish release candidate to npm (push) Blocked by required conditions
Release Candidate / Build & Push RC Docker images (push) Blocked by required conditions
* Initial plan * feat: detect sibling-clone graph drift via remote URL fingerprint Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/e5decb67-7fec-40e7-b2a1-b5e94a0d393f Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * test: address review feedback — fake commit, same-commit case, regex docs Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/e5decb67-7fec-40e7-b2a1-b5e94a0d393f Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix(mcp): address review feedback — CI green, perf, dead branch, one-shot test Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/cc2259f7-94e4-4243-aaa9-e03b7c632d32 * Merge branch 'main' into copilot/fix-single-path-indexing-issue Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/5840b3dd-e879-4854-a067-d1622bec2634 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * Merge branch 'main' into copilot/fix-single-path-indexing-issue Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/9025262f-4dd4-4774-8f32-e14434100004 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * style: prettier format run-analyze.ts after merge with main Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/a7be18dd-102f-4a7b-ac56-53fbd414fe3b Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * test: realpath both sides of cwdGitRoot assertion for Windows 8.3 short-name compat Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/b2a1c6a3-e454-4b87-b0e4-69d7c0d9a51b Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix(test): use path-agnostic assertion for cwdGitRoot on Windows (#1015) git rev-parse --show-toplevel returns long path names on Windows while os.tmpdir() returns 8.3 short names. fs.realpathSync does not expand short names, so exact path comparison always fails on Windows CI runners. Replace with behavioral assertions instead. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <copilot-swe-agent[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: evolution <wjc163@sina.cn>
320 lines
19 KiB
TypeScript
320 lines
19 KiB
TypeScript
/**
|
|
* Integration Tests: Java Class node traversal fix (#480)
|
|
*
|
|
* Reproduces the exact scenario from the issue:
|
|
* - SessionTracker class with 1 production caller + 4 test callers
|
|
* - RankPermissionHandler class with 1 caller + 10 importers
|
|
*
|
|
* Before fix: impact(upstream) → impactedCount: 0, context() → incoming: {}
|
|
* After fix: both tools surface callers/importers correctly
|
|
*/
|
|
import { describe, it, expect, beforeAll, vi } from 'vitest';
|
|
import { executeQuery } from '../../src/mcp/core/lbug-adapter.js';
|
|
import { LocalBackend } from '../../src/mcp/local/local-backend.js';
|
|
import { listRegisteredRepos } from '../../src/storage/repo-manager.js';
|
|
import { withTestLbugDB } from '../helpers/test-indexed-db.js';
|
|
|
|
vi.mock('../../src/storage/repo-manager.js', () => ({
|
|
listRegisteredRepos: vi.fn().mockResolvedValue([]),
|
|
cleanupOldKuzuFiles: vi.fn().mockResolvedValue({ found: false, needsReindex: false }),
|
|
findSiblingClones: vi.fn().mockResolvedValue([]),
|
|
}));
|
|
|
|
// Mirrors the exact graph structure from issue #480:
|
|
// SessionTracker: 1 prod caller + 4 test callers via Constructor
|
|
// 1 importer via File
|
|
// RankPermissionHandler: 1 caller via Constructor, 10 importers via File
|
|
const SEED = [
|
|
// ── SessionTracker ──────────────────────────────────────────────────
|
|
`CREATE (f:File {id: 'file:SessionTracker.java', name: 'SessionTracker.java', filePath: 'api/session/SessionTracker.java', content: ''})`,
|
|
`CREATE (c:Class {id: 'class:SessionTracker', name: 'SessionTracker', filePath: 'api/session/SessionTracker.java', startLine: 1, endLine: 80, isExported: true, content: 'class SessionTracker {}', description: 'Session tracker'})`,
|
|
`CREATE (ctor:Constructor {id: 'ctor:SessionTracker', name: 'SessionTracker', filePath: 'api/session/SessionTracker.java', startLine: 10, endLine: 15, content: 'SessionTracker() {}', description: ''})`,
|
|
|
|
// 1 production caller
|
|
`CREATE (m1:Method {id: 'method:registerSessionTracker', name: 'registerSessionTracker', filePath: 'core/bootstrap/ServerBootstrap.java', startLine: 20, endLine: 30, isExported: false, content: '', description: ''})`,
|
|
// 4 test callers — use src/test/java/... paths so isTestFilePath() filters them
|
|
`CREATE (m2:Method {id: 'method:setUp', name: 'setUp', filePath: 'src/test/java/api/session/SessionTrackerTest.java', startLine: 5, endLine: 10, isExported: false, content: '', description: ''})`,
|
|
`CREATE (m3:Method {id: 'method:constructor_nullGameMode_accepted', name: 'constructor_nullGameMode_accepted', filePath: 'src/test/java/api/session/SessionTrackerTest.java', startLine: 15, endLine: 22, isExported: false, content: '', description: ''})`,
|
|
`CREATE (m4:Method {id: 'method:constructor_nullServerId_accepted', name: 'constructor_nullServerId_accepted', filePath: 'src/test/java/api/session/SessionTrackerTest.java', startLine: 24, endLine: 31, isExported: false, content: '', description: ''})`,
|
|
`CREATE (m5:Method {id: 'method:startPlayerSession_passesGameModeAndServerId', name: 'startPlayerSession_passesGameModeAndServerId', filePath: 'src/test/java/api/session/SessionTrackerTest.java', startLine: 33, endLine: 42, isExported: false, content: '', description: ''})`,
|
|
|
|
// 1 importer file
|
|
`CREATE (f2:File {id: 'file:ServerBootstrap.java', name: 'ServerBootstrap.java', filePath: 'core/bootstrap/ServerBootstrap.java', content: ''})`,
|
|
|
|
// CALLS → Constructor (Java graph structure — NOT to Class)
|
|
`MATCH (a:Method {id:'method:registerSessionTracker'}), (b:Constructor {id:'ctor:SessionTracker'}) CREATE (a)-[:CodeRelation {type:'CALLS', confidence:0.9, reason:'direct', step:0}]->(b)`,
|
|
`MATCH (a:Method {id:'method:setUp'}), (b:Constructor {id:'ctor:SessionTracker'}) CREATE (a)-[:CodeRelation {type:'CALLS', confidence:0.9, reason:'direct', step:0}]->(b)`,
|
|
`MATCH (a:Method {id:'method:constructor_nullGameMode_accepted'}), (b:Constructor {id:'ctor:SessionTracker'}) CREATE (a)-[:CodeRelation {type:'CALLS', confidence:0.9, reason:'direct', step:0}]->(b)`,
|
|
`MATCH (a:Method {id:'method:constructor_nullServerId_accepted'}), (b:Constructor {id:'ctor:SessionTracker'}) CREATE (a)-[:CodeRelation {type:'CALLS', confidence:0.9, reason:'direct', step:0}]->(b)`,
|
|
`MATCH (a:Method {id:'method:startPlayerSession_passesGameModeAndServerId'}), (b:Constructor {id:'ctor:SessionTracker'}) CREATE (a)-[:CodeRelation {type:'CALLS', confidence:0.9, reason:'direct', step:0}]->(b)`,
|
|
|
|
// IMPORTS → File (Java graph structure — NOT to Class)
|
|
`MATCH (a:File {id:'file:ServerBootstrap.java'}), (b:File {id:'file:SessionTracker.java'}) CREATE (a)-[:CodeRelation {type:'IMPORTS', confidence:0.9, reason:'import', step:0}]->(b)`,
|
|
|
|
// Class structure edges
|
|
`MATCH (c:Class {id:'class:SessionTracker'}), (ctor:Constructor {id:'ctor:SessionTracker'}) CREATE (c)-[:CodeRelation {type:'HAS_METHOD', confidence:1.0, reason:'class-method', step:0}]->(ctor)`,
|
|
`MATCH (f:File {id:'file:SessionTracker.java'}), (c:Class {id:'class:SessionTracker'}) CREATE (f)-[:CodeRelation {type:'DEFINES', confidence:1.0, reason:'', step:0}]->(c)`,
|
|
|
|
// ── RankPermissionHandler ───────────────────────────────────────────
|
|
`CREATE (f3:File {id: 'file:RankPermissionHandler.java', name: 'RankPermissionHandler.java', filePath: 'core/rank/RankPermissionHandler.java', content: ''})`,
|
|
`CREATE (c2:Class {id: 'class:RankPermissionHandler', name: 'RankPermissionHandler', filePath: 'core/rank/RankPermissionHandler.java', startLine: 1, endLine: 60, isExported: true, content: 'class RankPermissionHandler {}', description: ''})`,
|
|
`CREATE (ctor2:Constructor {id: 'ctor:RankPermissionHandler', name: 'RankPermissionHandler', filePath: 'core/rank/RankPermissionHandler.java', startLine: 5, endLine: 10, content: '', description: ''})`,
|
|
|
|
// 1 caller via Constructor
|
|
`CREATE (m6:Method {id: 'method:initRankHandler', name: 'initRankHandler', filePath: 'core/rank/RankService.java', startLine: 10, endLine: 20, isExported: false, content: '', description: ''})`,
|
|
`MATCH (a:Method {id:'method:initRankHandler'}), (b:Constructor {id:'ctor:RankPermissionHandler'}) CREATE (a)-[:CodeRelation {type:'CALLS', confidence:0.9, reason:'direct', step:0}]->(b)`,
|
|
|
|
// 10 importers via File
|
|
`CREATE (fi1:File {id:'file:imp1.java', name:'RankCommand.java', filePath:'core/rank/RankCommand.java', content:''})`,
|
|
`CREATE (fi2:File {id:'file:imp2.java', name:'RankListener.java', filePath:'core/rank/RankListener.java', content:''})`,
|
|
`CREATE (fi3:File {id:'file:imp3.java', name:'RankManager.java', filePath:'core/rank/RankManager.java', content:''})`,
|
|
`CREATE (fi4:File {id:'file:imp4.java', name:'RankConfig.java', filePath:'core/rank/RankConfig.java', content:''})`,
|
|
`CREATE (fi5:File {id:'file:imp5.java', name:'RankAPI.java', filePath:'core/rank/RankAPI.java', content:''})`,
|
|
`CREATE (fi6:File {id:'file:imp6.java', name:'RankTest1.java', filePath:'src/test/java/core/rank/RankTest1.java', content:''})`,
|
|
`CREATE (fi7:File {id:'file:imp7.java', name:'RankTest2.java', filePath:'src/test/java/core/rank/RankTest2.java', content:''})`,
|
|
`CREATE (fi8:File {id:'file:imp8.java', name:'RankTest3.java', filePath:'src/test/java/core/rank/RankTest3.java', content:''})`,
|
|
`CREATE (fi9:File {id:'file:imp9.java', name:'RankTest4.java', filePath:'src/test/java/core/rank/RankTest4.java', content:''})`,
|
|
`CREATE (fi10:File {id:'file:imp10.java', name:'RankTest5.java', filePath:'src/test/java/core/rank/RankTest5.java', content:''})`,
|
|
`MATCH (a:File {id:'file:imp1.java'}), (b:File {id:'file:RankPermissionHandler.java'}) CREATE (a)-[:CodeRelation {type:'IMPORTS', confidence:0.9, reason:'import', step:0}]->(b)`,
|
|
`MATCH (a:File {id:'file:imp2.java'}), (b:File {id:'file:RankPermissionHandler.java'}) CREATE (a)-[:CodeRelation {type:'IMPORTS', confidence:0.9, reason:'import', step:0}]->(b)`,
|
|
`MATCH (a:File {id:'file:imp3.java'}), (b:File {id:'file:RankPermissionHandler.java'}) CREATE (a)-[:CodeRelation {type:'IMPORTS', confidence:0.9, reason:'import', step:0}]->(b)`,
|
|
`MATCH (a:File {id:'file:imp4.java'}), (b:File {id:'file:RankPermissionHandler.java'}) CREATE (a)-[:CodeRelation {type:'IMPORTS', confidence:0.9, reason:'import', step:0}]->(b)`,
|
|
`MATCH (a:File {id:'file:imp5.java'}), (b:File {id:'file:RankPermissionHandler.java'}) CREATE (a)-[:CodeRelation {type:'IMPORTS', confidence:0.9, reason:'import', step:0}]->(b)`,
|
|
`MATCH (a:File {id:'file:imp6.java'}), (b:File {id:'file:RankPermissionHandler.java'}) CREATE (a)-[:CodeRelation {type:'IMPORTS', confidence:0.9, reason:'import', step:0}]->(b)`,
|
|
`MATCH (a:File {id:'file:imp7.java'}), (b:File {id:'file:RankPermissionHandler.java'}) CREATE (a)-[:CodeRelation {type:'IMPORTS', confidence:0.9, reason:'import', step:0}]->(b)`,
|
|
`MATCH (a:File {id:'file:imp8.java'}), (b:File {id:'file:RankPermissionHandler.java'}) CREATE (a)-[:CodeRelation {type:'IMPORTS', confidence:0.9, reason:'import', step:0}]->(b)`,
|
|
`MATCH (a:File {id:'file:imp9.java'}), (b:File {id:'file:RankPermissionHandler.java'}) CREATE (a)-[:CodeRelation {type:'IMPORTS', confidence:0.9, reason:'import', step:0}]->(b)`,
|
|
`MATCH (a:File {id:'file:imp10.java'}), (b:File {id:'file:RankPermissionHandler.java'}) CREATE (a)-[:CodeRelation {type:'IMPORTS', confidence:0.9, reason:'import', step:0}]->(b)`,
|
|
|
|
`MATCH (c2:Class {id:'class:RankPermissionHandler'}), (ctor2:Constructor {id:'ctor:RankPermissionHandler'}) CREATE (c2)-[:CodeRelation {type:'HAS_METHOD', confidence:1.0, reason:'class-method', step:0}]->(ctor2)`,
|
|
`MATCH (f3:File {id:'file:RankPermissionHandler.java'}), (c2:Class {id:'class:RankPermissionHandler'}) CREATE (f3)-[:CodeRelation {type:'DEFINES', confidence:1.0, reason:'', step:0}]->(c2)`,
|
|
];
|
|
|
|
withTestLbugDB(
|
|
'java-class-impact',
|
|
(handle) => {
|
|
// ─── Confirm root cause is present in graph ─────────────────────────
|
|
|
|
describe('root cause confirmed: Java graph edge structure', () => {
|
|
it('CALLS edges go to Constructor, not Class — so naive Class traversal finds 0', async () => {
|
|
const onClass = await executeQuery(
|
|
handle.repoId,
|
|
`MATCH (a)-[r:CodeRelation {type:'CALLS'}]->(b:Class {name:'SessionTracker'}) RETURN a.name AS name`,
|
|
);
|
|
expect(onClass).toHaveLength(0); // this is the bug — Class has no CALLS edges
|
|
|
|
const onCtor = await executeQuery(
|
|
handle.repoId,
|
|
`MATCH (a)-[r:CodeRelation {type:'CALLS'}]->(b:Constructor {name:'SessionTracker'}) RETURN a.name AS name`,
|
|
);
|
|
expect(onCtor).toHaveLength(5); // all 5 callers are on Constructor
|
|
});
|
|
|
|
it('IMPORTS edges go to File, not Class', async () => {
|
|
const onClass = await executeQuery(
|
|
handle.repoId,
|
|
`MATCH (a)-[r:CodeRelation {type:'IMPORTS'}]->(b:Class {name:'SessionTracker'}) RETURN a.name AS name`,
|
|
);
|
|
expect(onClass).toHaveLength(0);
|
|
|
|
const onFile = await executeQuery(
|
|
handle.repoId,
|
|
`MATCH (a)-[r:CodeRelation {type:'IMPORTS'}]->(b:File {name:'SessionTracker.java'}) RETURN a.name AS name`,
|
|
);
|
|
expect(onFile).toHaveLength(1); // ServerBootstrap.java
|
|
});
|
|
});
|
|
|
|
// ─── Bug 1: impact() fix ────────────────────────────────────────────
|
|
|
|
describe('Bug 1 fix: impact(upstream) on Class returns callers', () => {
|
|
let backend: LocalBackend;
|
|
beforeAll(async () => {
|
|
backend = (handle as any)._backend;
|
|
});
|
|
|
|
it('default call (no includeTests) finds the 1 production caller and excludes test callers', async () => {
|
|
// Exact call from the issue: gitnexus_impact({target: "SessionTracker", direction: "upstream"})
|
|
// Before fix: impactedCount: 0, risk: LOW, byDepth: {}
|
|
const result = await backend.callTool('impact', {
|
|
target: 'SessionTracker',
|
|
direction: 'upstream',
|
|
});
|
|
|
|
expect(result).not.toHaveProperty('error');
|
|
// At minimum: 1 production caller (registerSessionTracker) + 1 file
|
|
// importer (ServerBootstrap.java discovered via BFS from the seeded File)
|
|
expect(result.impactedCount).toBeGreaterThanOrEqual(2);
|
|
|
|
const d1 = result.byDepth[1] || result.byDepth['1'] || [];
|
|
const names = d1.map((d: any) => d.name);
|
|
|
|
// Production caller must be present
|
|
expect(names).toContain('registerSessionTracker');
|
|
|
|
// Test callers must be excluded (paths match /test/ via isTestFilePath)
|
|
expect(names).not.toContain('setUp');
|
|
expect(names).not.toContain('constructor_nullGameMode_accepted');
|
|
expect(names).not.toContain('constructor_nullServerId_accepted');
|
|
expect(names).not.toContain('startPlayerSession_passesGameModeAndServerId');
|
|
});
|
|
|
|
it('with includeTests finds all 5 callers (1 prod + 4 tests)', async () => {
|
|
const result = await backend.callTool('impact', {
|
|
target: 'SessionTracker',
|
|
direction: 'upstream',
|
|
includeTests: true,
|
|
});
|
|
|
|
expect(result).not.toHaveProperty('error');
|
|
expect(result.impactedCount).toBeGreaterThanOrEqual(5);
|
|
|
|
const d1 = result.byDepth[1] || result.byDepth['1'] || [];
|
|
const names = d1.map((d: any) => d.name);
|
|
expect(names).toContain('registerSessionTracker');
|
|
expect(names).toContain('setUp');
|
|
expect(names).toContain('constructor_nullGameMode_accepted');
|
|
expect(names).toContain('constructor_nullServerId_accepted');
|
|
expect(names).toContain('startPlayerSession_passesGameModeAndServerId');
|
|
|
|
// Owning file (SessionTracker.java) must NOT appear — it is the
|
|
// definition container, not an upstream dependent (#480 Copilot review)
|
|
const allNames = Object.values(result.byDepth as Record<string, any[]>)
|
|
.flat()
|
|
.map((d: any) => d.name);
|
|
expect(allNames).not.toContain('SessionTracker.java');
|
|
});
|
|
|
|
it('RankPermissionHandler: 1 caller via Constructor + 10 importers via File (was 0 before fix)', async () => {
|
|
const result = await backend.callTool('impact', {
|
|
target: 'RankPermissionHandler',
|
|
direction: 'upstream',
|
|
includeTests: true,
|
|
});
|
|
|
|
expect(result).not.toHaveProperty('error');
|
|
// 1 caller (depth 1 via Constructor) + 10 importers (depth 2 via File)
|
|
expect(result.impactedCount).toBeGreaterThanOrEqual(11);
|
|
|
|
const allNames = Object.values(result.byDepth as Record<string, any[]>)
|
|
.flat()
|
|
.map((d: any) => d.name);
|
|
expect(allNames).toContain('initRankHandler');
|
|
expect(allNames).toContain('RankCommand.java');
|
|
expect(allNames).toContain('RankManager.java');
|
|
|
|
// Owning file must NOT appear in results
|
|
expect(allNames).not.toContain('RankPermissionHandler.java');
|
|
});
|
|
|
|
it('RankPermissionHandler: default call (no includeTests) excludes test importers', async () => {
|
|
const result = await backend.callTool('impact', {
|
|
target: 'RankPermissionHandler',
|
|
direction: 'upstream',
|
|
});
|
|
|
|
expect(result).not.toHaveProperty('error');
|
|
|
|
const allNames = Object.values(result.byDepth as Record<string, any[]>)
|
|
.flat()
|
|
.map((d: any) => d.name);
|
|
|
|
// Production importers must be present
|
|
expect(allNames).toContain('RankCommand.java');
|
|
|
|
// Test importers (src/test/java/... paths) must be excluded
|
|
expect(allNames).not.toContain('RankTest1.java');
|
|
expect(allNames).not.toContain('RankTest2.java');
|
|
expect(allNames).not.toContain('RankTest3.java');
|
|
expect(allNames).not.toContain('RankTest4.java');
|
|
expect(allNames).not.toContain('RankTest5.java');
|
|
});
|
|
});
|
|
|
|
// ─── Bug 2: context() fix ───────────────────────────────────────────
|
|
|
|
describe('Bug 2 fix: context() on Class shows non-empty incoming', () => {
|
|
let backend: LocalBackend;
|
|
beforeAll(async () => {
|
|
backend = (handle as any)._backend;
|
|
});
|
|
|
|
it('incoming.calls contains Constructor callers (was empty before fix)', async () => {
|
|
// Exact call from issue: gitnexus_context({name: "SessionTracker", file_path: "api/.../SessionTracker.java"})
|
|
// Before fix: incoming: {}
|
|
const result = await backend.callTool('context', {
|
|
name: 'SessionTracker',
|
|
file_path: 'api/session/SessionTracker.java',
|
|
});
|
|
|
|
expect(result.status).toBe('found');
|
|
expect(result.symbol.kind).toBe('Class');
|
|
expect(result.incoming).toBeDefined();
|
|
|
|
// Should have calls from the Constructor callers
|
|
const calls = result.incoming.calls || [];
|
|
expect(calls.length).toBeGreaterThanOrEqual(1);
|
|
const callerNames = calls.map((c: any) => c.name);
|
|
expect(callerNames).toContain('registerSessionTracker');
|
|
});
|
|
|
|
it('incoming.imports contains File importers', async () => {
|
|
const result = await backend.callTool('context', {
|
|
name: 'SessionTracker',
|
|
file_path: 'api/session/SessionTracker.java',
|
|
});
|
|
|
|
expect(result.status).toBe('found');
|
|
const imports = result.incoming.imports || [];
|
|
expect(imports.length).toBeGreaterThanOrEqual(1);
|
|
const importerNames = imports.map((c: any) => c.name);
|
|
expect(importerNames).toContain('ServerBootstrap.java');
|
|
});
|
|
|
|
it('contrast: context() on a Method still works (regression check)', async () => {
|
|
// The issue notes context() on methods worked before — must still work after fix
|
|
const result = await backend.callTool('context', {
|
|
name: 'registerSessionTracker',
|
|
});
|
|
expect(result.status).toBe('found');
|
|
// registerSessionTracker calls SessionTracker constructor — should appear in outgoing
|
|
expect(result.outgoing).toBeDefined();
|
|
});
|
|
|
|
it('RankPermissionHandler: incoming shows caller and importers', async () => {
|
|
const result = await backend.callTool('context', {
|
|
name: 'RankPermissionHandler',
|
|
file_path: 'core/rank/RankPermissionHandler.java',
|
|
});
|
|
|
|
expect(result.status).toBe('found');
|
|
expect(result.symbol.kind).toBe('Class');
|
|
|
|
const calls = result.incoming.calls || [];
|
|
expect(calls.map((c: any) => c.name)).toContain('initRankHandler');
|
|
|
|
const imports = result.incoming.imports || [];
|
|
expect(imports.length).toBeGreaterThanOrEqual(10);
|
|
});
|
|
});
|
|
},
|
|
{
|
|
seed: SEED,
|
|
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: 10, nodes: 20, communities: 0, processes: 0 },
|
|
},
|
|
]);
|
|
const backend = new LocalBackend();
|
|
await backend.init();
|
|
(handle as any)._backend = backend;
|
|
},
|
|
},
|
|
);
|