GitNexus/gitnexus/test/unit/impact-confidence.test.ts
Shunsuke Hayashi d206bf6772 fix(ingestion): calculate confidence per resolution tier for heritage/MRO edges (#412)
Instead of hardcoding confidence: 1.0, compute it at ingestion time using
the same resolution tier system that CALLS edges already use.

Heritage edges (EXTENDS, IMPLEMENTS):
- resolveHeritageId now returns { id, confidence } using TIER_CONFIDENCE
- Same-file → 0.95, import-scoped → 0.9, global → 0.5
- Edge confidence = geometric mean of source and target confidence
  (principled for partially-correlated cross-scope estimates, per
  Dillig et al. POPL 2011 and Dempster-Shafer theory)

MRO edges (OVERRIDES):
- MRO-ordered → 0.9, class method wins → 0.95
- Single interface → 0.85, ambiguous/unresolved → 0.5

IMPORTS and CONTAINS intentionally keep 1.0 (deterministic).

Closes #412
2026-03-22 18:23:40 +09:00

140 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Unit Tests: Impact confidence per relation type (#412)
*
* Tests IMPACT_RELATION_CONFIDENCE and confidenceForRelType (tested
* indirectly) to verify that:
*
* 1. Each known relation type maps to the expected confidence floor.
* 2. Unknown / undefined relation types fall back to 0.5 (conservative).
* 3. Stored graph confidence is preferred over the type-based floor.
* 4. Confidence values are in the valid 0–1 range.
*/
import { describe, it, expect } from 'vitest';
import { IMPACT_RELATION_CONFIDENCE, VALID_RELATION_TYPES } from '../../src/mcp/local/local-backend.js';
// ─── IMPACT_RELATION_CONFIDENCE — value assertions ────────────────────────
describe('IMPACT_RELATION_CONFIDENCE', () => {
it('CALLS has confidence 0.9 (direct reference)', () => {
expect(IMPACT_RELATION_CONFIDENCE['CALLS']).toBe(0.9);
});
it('IMPORTS has confidence 0.9 (direct reference)', () => {
expect(IMPACT_RELATION_CONFIDENCE['IMPORTS']).toBe(0.9);
});
it('EXTENDS has confidence 0.85 (statically verifiable inheritance)', () => {
expect(IMPACT_RELATION_CONFIDENCE['EXTENDS']).toBe(0.85);
});
it('IMPLEMENTS has confidence 0.85 (statically verifiable contract)', () => {
expect(IMPACT_RELATION_CONFIDENCE['IMPLEMENTS']).toBe(0.85);
});
it('OVERRIDES has confidence 0.85 (statically verifiable override)', () => {
expect(IMPACT_RELATION_CONFIDENCE['OVERRIDES']).toBe(0.85);
});
it('HAS_METHOD has confidence 0.95 (structural containment)', () => {
expect(IMPACT_RELATION_CONFIDENCE['HAS_METHOD']).toBe(0.95);
});
it('HAS_PROPERTY has confidence 0.95 (structural containment)', () => {
expect(IMPACT_RELATION_CONFIDENCE['HAS_PROPERTY']).toBe(0.95);
});
it('ACCESSES has confidence 0.8 (may be indirect read/write)', () => {
expect(IMPACT_RELATION_CONFIDENCE['ACCESSES']).toBe(0.8);
});
it('CONTAINS has confidence 0.95 (folder/file structural containment)', () => {
expect(IMPACT_RELATION_CONFIDENCE['CONTAINS']).toBe(0.95);
});
it('all defined confidence values are in the valid [0, 1] range', () => {
for (const [type, confidence] of Object.entries(IMPACT_RELATION_CONFIDENCE)) {
expect(confidence, `${type} confidence out of range`).toBeGreaterThanOrEqual(0);
expect(confidence, `${type} confidence out of range`).toBeLessThanOrEqual(1);
}
});
});
// ─── confidenceForRelType — fallback semantics ────────────────────────────
//
// confidenceForRelType is not exported, so we replicate its logic here to
// verify the semantics that the production code must uphold.
const confidenceForRelType = (relType: string | undefined): number =>
IMPACT_RELATION_CONFIDENCE[relType ?? ''] ?? 0.5;
describe('confidenceForRelType', () => {
it('returns the correct floor for known types', () => {
expect(confidenceForRelType('CALLS')).toBe(0.9);
expect(confidenceForRelType('IMPORTS')).toBe(0.9);
expect(confidenceForRelType('EXTENDS')).toBe(0.85);
expect(confidenceForRelType('IMPLEMENTS')).toBe(0.85);
expect(confidenceForRelType('OVERRIDES')).toBe(0.85);
expect(confidenceForRelType('HAS_METHOD')).toBe(0.95);
expect(confidenceForRelType('HAS_PROPERTY')).toBe(0.95);
expect(confidenceForRelType('ACCESSES')).toBe(0.8);
expect(confidenceForRelType('CONTAINS')).toBe(0.95);
});
it('returns 0.5 for unknown relation types', () => {
expect(confidenceForRelType('UNKNOWN_EDGE')).toBe(0.5);
expect(confidenceForRelType('SOME_FUTURE_TYPE')).toBe(0.5);
expect(confidenceForRelType('')).toBe(0.5);
});
it('returns 0.5 for undefined relation type', () => {
expect(confidenceForRelType(undefined)).toBe(0.5);
});
});
// ─── Effective confidence selection — stored value wins ───────────────────
//
// Verify the priority logic: stored graph confidence beats the type floor.
describe('effective confidence selection (stored vs type-floor)', () => {
const pickConfidence = (storedConfidence: number | undefined, relationType: string): number => {
return typeof storedConfidence === 'number' && storedConfidence > 0
? storedConfidence
: confidenceForRelType(relationType);
};
it('uses stored confidence when it is a positive number', () => {
expect(pickConfidence(0.95, 'CALLS')).toBe(0.95);
expect(pickConfidence(0.7, 'EXTENDS')).toBe(0.7);
expect(pickConfidence(0.3, 'ACCESSES')).toBe(0.3);
});
it('falls back to type floor when stored confidence is undefined', () => {
expect(pickConfidence(undefined, 'CALLS')).toBe(0.9);
expect(pickConfidence(undefined, 'EXTENDS')).toBe(0.85);
expect(pickConfidence(undefined, 'UNKNOWN')).toBe(0.5);
});
it('falls back to type floor when stored confidence is 0 (not a valid confidence)', () => {
// 0 means "no confidence stored", not "zero confidence"
expect(pickConfidence(0, 'CALLS')).toBe(0.9);
expect(pickConfidence(0, 'IMPLEMENTS')).toBe(0.85);
});
it('stored confidence can be lower than the type floor (respects analysis result)', () => {
// If analysis determined a low-confidence match, honour it
expect(pickConfidence(0.5, 'HAS_METHOD')).toBe(0.5); // floor is 0.95, stored wins
});
});
// ─── VALID_RELATION_TYPES consistency ─────────────────────────────────────
describe('IMPACT_RELATION_CONFIDENCE vs VALID_RELATION_TYPES', () => {
it('every key in IMPACT_RELATION_CONFIDENCE except CONTAINS is in VALID_RELATION_TYPES', () => {
// CONTAINS is a graph-internal structural type, not exposed in impact filters
const skipInValid = new Set(['CONTAINS']);
for (const type of Object.keys(IMPACT_RELATION_CONFIDENCE)) {
if (skipInValid.has(type)) continue;
expect(VALID_RELATION_TYPES.has(type), `${type} missing from VALID_RELATION_TYPES`).toBe(true);
}
});
});