GitNexus/gitnexus/test/unit/impact-confidence.test.ts
Gergő Magyar bf09eab95b
feat: configure prettier with pre-commit hook (#563)
* feat: configure prettier with pre-commit hook integration

Add prettier, lint-staged, and prettier-plugin-tailwindcss at the repo
root with husky pre-commit hook integration. Moves husky from
gitnexus/ to root package.json for reliable hook installation.

- Root package.json with prepare/format/format:check scripts
- .prettierrc with endOfLine:lf and tailwindStylesheet for TW v4
- .prettierignore excluding fixtures, vendor, generated, *.d.ts, *.md
- .gitattributes enforcing LF line endings for Windows consistency
- Pre-commit hook uses direct node_modules/.bin/ paths (no npx)

* style: apply prettier formatting to entire codebase

One-time bulk format. No logic changes.
Use .git-blame-ignore-revs to skip this commit in git blame.

* chore: add .git-blame-ignore-revs for prettier format commit

* perf: pre-commit hook runs only tests related to staged files

Use vitest --related to scope test execution to tests that import
the changed files, instead of running the full suite on every commit.

* perf: remove vitest from pre-commit hook, keep in CI only

Pre-commit now runs lint-staged + tsc only. Tests run in CI
(ci-tests.yml) where they belong — keeps commits fast.

* ci: add prettier format check to quality workflow

PRs will now fail if code isn't formatted with prettier.
2026-03-28 14:58:04 +00:00

145 lines
5.9 KiB
TypeScript
Raw Permalink 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,
);
}
});
});