mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-10 03:27:59 +00:00
fix(group): restore out-of-scope reporting and phase1 timeout semantics
This commit is contained in:
parent
ae68bfbfb2
commit
149e8ee735
2 changed files with 131 additions and 3 deletions
|
|
@ -117,6 +117,11 @@ function mergeRisk(
|
|||
return order[idx] ?? base;
|
||||
}
|
||||
|
||||
function computePhase1Timeout(timeout: number): number {
|
||||
if (timeout <= 1000) return timeout;
|
||||
return Math.min(Math.ceil(timeout * 0.3), 10000);
|
||||
}
|
||||
|
||||
export async function runGroupImpactLegacy(
|
||||
opts: LegacyGroupImpactOptions,
|
||||
): Promise<GroupImpactResult> {
|
||||
|
|
@ -126,7 +131,7 @@ export async function runGroupImpactLegacy(
|
|||
|
||||
const tStart = Date.now();
|
||||
const wallDeadline = tStart + timeout;
|
||||
const phase1Timeout = Math.min(5000, timeout);
|
||||
const phase1Timeout = computePhase1Timeout(timeout);
|
||||
|
||||
const localResult = await Promise.race([
|
||||
opts.localImpactFn(opts.target, opts.direction).then((v) => ({ ok: true as const, v })),
|
||||
|
|
@ -309,7 +314,7 @@ export async function runGroupImpact(opts: GroupImpactOptions): Promise<GroupImp
|
|||
|
||||
const tStart = Date.now();
|
||||
const wallDeadline = tStart + timeout;
|
||||
const phase1Timeout = Math.min(5000, timeout);
|
||||
const phase1Timeout = computePhase1Timeout(timeout);
|
||||
|
||||
const localResult = await Promise.race([
|
||||
opts.localImpactFn(opts.target, opts.direction).then((v) => ({ ok: true as const, v })),
|
||||
|
|
@ -335,6 +340,7 @@ export async function runGroupImpact(opts: GroupImpactOptions): Promise<GroupImp
|
|||
const uids = collectPhase1Uids(local);
|
||||
const phase1Refs = collectPhase1Refs(local);
|
||||
const cross: CrossRepoImpact[] = [];
|
||||
const outOfScope: OutOfScopeLink[] = [];
|
||||
const truncatedRepos: string[] = [];
|
||||
|
||||
/* Phase 2 — Cypher bridge query */
|
||||
|
|
@ -365,6 +371,17 @@ export async function runGroupImpact(opts: GroupImpactOptions): Promise<GroupImp
|
|||
const distinctRepos = new Set<string>();
|
||||
|
||||
for (const row of rows) {
|
||||
if (!inSubgroup(row.fanOutRepo, opts.subgroup)) {
|
||||
outOfScope.push({
|
||||
from: row.fanOutRepo,
|
||||
to: opts.repoPath,
|
||||
contractId: row.contractId,
|
||||
matchType: row.matchType as OutOfScopeLink['matchType'],
|
||||
confidence: row.confidence,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Date.now() > wallDeadline) {
|
||||
truncated = true;
|
||||
break;
|
||||
|
|
@ -414,7 +431,7 @@ export async function runGroupImpact(opts: GroupImpactOptions): Promise<GroupImp
|
|||
local,
|
||||
group: opts.groupName,
|
||||
cross,
|
||||
outOfScope: [],
|
||||
outOfScope,
|
||||
truncated,
|
||||
truncatedRepos,
|
||||
summary: {
|
||||
|
|
|
|||
|
|
@ -188,6 +188,42 @@ describe('runGroupImpactLegacy', () => {
|
|||
|
||||
expect(result.truncated).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[500, 500],
|
||||
[5000, 1500],
|
||||
[30000, 9000],
|
||||
[60000, 10000],
|
||||
])(
|
||||
'test_runGroupImpactLegacy_phase1_timeout_contract_%i_to_%i',
|
||||
async (timeout, expectedPhase1Timeout) => {
|
||||
const t0 = Date.now();
|
||||
const result = await runGroupImpactLegacy({
|
||||
groupName: 'test',
|
||||
target: 'slowTarget',
|
||||
repoPath: 'app/backend',
|
||||
direction: 'upstream',
|
||||
timeout,
|
||||
registry: mockRegistry,
|
||||
localImpactFn: async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, expectedPhase1Timeout + 50));
|
||||
return {
|
||||
target: { id: '', name: 'slowTarget', filePath: '' },
|
||||
direction: 'upstream',
|
||||
impactedCount: 0,
|
||||
risk: 'LOW',
|
||||
summary: {},
|
||||
affected_processes: [],
|
||||
affected_modules: [],
|
||||
byDepth: {},
|
||||
};
|
||||
},
|
||||
crossImpactFn: async () => null,
|
||||
});
|
||||
expect(result.truncated).toBe(true);
|
||||
expect(Date.now() - t0).toBeLessThan(expectedPhase1Timeout + 250);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('runGroupImpact (Cypher-based)', () => {
|
||||
|
|
@ -368,6 +404,45 @@ describe('runGroupImpact (Cypher-based)', () => {
|
|||
expect(cypher).toContain('$subgroup');
|
||||
});
|
||||
|
||||
it('test_runGroupImpact_subgroup_filtered_rows_are_reported_out_of_scope', async () => {
|
||||
const bridgeQuery = makeBridgeQuery([
|
||||
{
|
||||
fanOutRepo: 'other/team/frontend',
|
||||
fanOutUid: 'uid-fetch',
|
||||
fanOutFilePath: 'src/api.ts',
|
||||
fanOutSymbolName: 'fetchUsers',
|
||||
matchedLocalUid: 'uid-ctrl',
|
||||
matchedLocalFilePath: 'src/ctrl.ts',
|
||||
matchedLocalSymbolName: 'UserController.list',
|
||||
matchType: 'exact',
|
||||
confidence: 1.0,
|
||||
contractId: 'http::GET::/api/users',
|
||||
contractType: 'http',
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await runGroupImpact({
|
||||
groupName: 'test',
|
||||
target: 'UserController.list',
|
||||
repoPath: 'app/backend',
|
||||
direction: 'upstream',
|
||||
subgroup: 'team/backend',
|
||||
bridgeQuery,
|
||||
localImpactFn,
|
||||
crossImpactFn: vi.fn().mockResolvedValue(null),
|
||||
});
|
||||
|
||||
expect(result.cross).toHaveLength(0);
|
||||
expect(result.outOfScope).toEqual([
|
||||
expect.objectContaining({
|
||||
from: 'other/team/frontend',
|
||||
to: 'app/backend',
|
||||
contractId: 'http::GET::/api/users',
|
||||
matchType: 'exact',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('test_runGroupImpact_error_object_not_counted_as_hit', async () => {
|
||||
const bridgeQuery = makeBridgeQuery([
|
||||
{
|
||||
|
|
@ -399,4 +474,40 @@ describe('runGroupImpact (Cypher-based)', () => {
|
|||
expect(result.cross).toHaveLength(0);
|
||||
expect(result.summary.cross_repo_hits).toBe(0);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[500, 500],
|
||||
[5000, 1500],
|
||||
[30000, 9000],
|
||||
[60000, 10000],
|
||||
])(
|
||||
'test_runGroupImpact_phase1_timeout_contract_%i_to_%i',
|
||||
async (timeout, expectedPhase1Timeout) => {
|
||||
const t0 = Date.now();
|
||||
const result = await runGroupImpact({
|
||||
groupName: 'test',
|
||||
target: 'slowTarget',
|
||||
repoPath: 'app/backend',
|
||||
direction: 'upstream',
|
||||
timeout,
|
||||
bridgeQuery: vi.fn().mockResolvedValue([]),
|
||||
localImpactFn: async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, expectedPhase1Timeout + 50));
|
||||
return {
|
||||
target: { id: '', name: 'slowTarget', filePath: '' },
|
||||
direction: 'upstream',
|
||||
impactedCount: 0,
|
||||
risk: 'LOW',
|
||||
summary: {},
|
||||
affected_processes: [],
|
||||
affected_modules: [],
|
||||
byDepth: {},
|
||||
};
|
||||
},
|
||||
crossImpactFn: vi.fn().mockResolvedValue(null),
|
||||
});
|
||||
expect(result.truncated).toBe(true);
|
||||
expect(Date.now() - t0).toBeLessThan(expectedPhase1Timeout + 250);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue