23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
724 lines
24 KiB
TypeScript
724 lines
24 KiB
TypeScript
/**
|
|
* Coverage Router Tests
|
|
*
|
|
* Tests for the coverage-based test routing functionality.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import { CoverageRouter, createCoverageRouter, type CoverageReport, type FileCoverage } from '../../src/ruvector/coverage-router';
|
|
|
|
// Mock the @ruvector/coverage module
|
|
vi.mock('@ruvector/coverage', () => ({
|
|
createCoverageRouter: vi.fn(() => null),
|
|
}));
|
|
|
|
describe('CoverageRouter', () => {
|
|
let router: CoverageRouter;
|
|
|
|
beforeEach(() => {
|
|
router = new CoverageRouter();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should create router with default config', () => {
|
|
const stats = router.getStats();
|
|
expect(stats.minCoverage).toBe(70);
|
|
expect(stats.targetCoverage).toBe(85);
|
|
});
|
|
|
|
it('should create router with custom config', () => {
|
|
const customRouter = new CoverageRouter({
|
|
minCoverage: 80,
|
|
targetCoverage: 95,
|
|
incremental: false,
|
|
});
|
|
const stats = customRouter.getStats();
|
|
expect(stats.minCoverage).toBe(80);
|
|
expect(stats.targetCoverage).toBe(95);
|
|
});
|
|
});
|
|
|
|
describe('initialize', () => {
|
|
it('should initialize without ruvector (fallback mode)', async () => {
|
|
await router.initialize();
|
|
const stats = router.getStats();
|
|
expect(stats.useNative).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('parseCoverage', () => {
|
|
describe('JSON format', () => {
|
|
it('should parse JSON coverage data', () => {
|
|
const data = {
|
|
'src/utils.ts': {
|
|
lineCoverage: 80,
|
|
branchCoverage: 70,
|
|
functionCoverage: 90,
|
|
statementCoverage: 85,
|
|
uncoveredLines: [10, 20, 30],
|
|
totalLines: 100,
|
|
coveredLines: 80,
|
|
},
|
|
};
|
|
|
|
const report = router.parseCoverage(data, 'json');
|
|
expect(report).toHaveProperty('overall');
|
|
expect(report).toHaveProperty('byType');
|
|
expect(report).toHaveProperty('byFile');
|
|
expect(report.byFile.length).toBe(1);
|
|
});
|
|
|
|
it('should parse array of FileCoverage', () => {
|
|
const data: FileCoverage[] = [
|
|
{ path: 'src/a.ts', lineCoverage: 80, branchCoverage: 70, functionCoverage: 90, statementCoverage: 85, uncoveredLines: [], totalLines: 100, coveredLines: 80 },
|
|
{ path: 'src/b.ts', lineCoverage: 60, branchCoverage: 50, functionCoverage: 70, statementCoverage: 65, uncoveredLines: [], totalLines: 50, coveredLines: 30 },
|
|
];
|
|
|
|
const report = router.parseCoverage(data, 'json');
|
|
expect(report.byFile.length).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('LCOV format', () => {
|
|
it('should parse LCOV data', () => {
|
|
const lcov = `SF:src/utils.ts
|
|
LF:100
|
|
LH:80
|
|
DA:1,1
|
|
DA:2,1
|
|
DA:3,0
|
|
end_of_record
|
|
SF:src/helper.ts
|
|
LF:50
|
|
LH:40
|
|
end_of_record
|
|
`;
|
|
|
|
const report = router.parseCoverage(lcov, 'lcov');
|
|
expect(report.byFile.length).toBe(2);
|
|
expect(report.byFile[0].path).toBe('src/utils.ts');
|
|
});
|
|
|
|
it('should extract uncovered lines from LCOV', () => {
|
|
const lcov = `SF:src/utils.ts
|
|
LF:10
|
|
LH:8
|
|
DA:1,1
|
|
DA:2,0
|
|
DA:3,1
|
|
DA:4,0
|
|
end_of_record
|
|
`;
|
|
|
|
const report = router.parseCoverage(lcov, 'lcov');
|
|
expect(report.byFile[0].uncoveredLines).toContain(2);
|
|
expect(report.byFile[0].uncoveredLines).toContain(4);
|
|
});
|
|
});
|
|
|
|
describe('Istanbul format', () => {
|
|
it('should parse Istanbul coverage data', () => {
|
|
const istanbul = {
|
|
'src/utils.ts': {
|
|
s: { '0': 1, '1': 0, '2': 1 },
|
|
f: { '0': 1, '1': 0 },
|
|
b: { '0': [1, 0] },
|
|
},
|
|
};
|
|
|
|
const report = router.parseCoverage(istanbul, 'istanbul');
|
|
expect(report.byFile.length).toBe(1);
|
|
expect(report.byFile[0].statementCoverage).toBeCloseTo(66.67, 1);
|
|
});
|
|
});
|
|
|
|
describe('Cobertura format', () => {
|
|
it('should parse Cobertura XML data', () => {
|
|
const cobertura = `
|
|
<coverage>
|
|
<packages>
|
|
<package>
|
|
<classes>
|
|
<class filename="src/utils.ts" line-rate="0.8" branch-rate="0.7">
|
|
</class>
|
|
</classes>
|
|
</package>
|
|
</packages>
|
|
</coverage>
|
|
`;
|
|
|
|
const report = router.parseCoverage(cobertura, 'cobertura');
|
|
expect(report.byFile.length).toBe(1);
|
|
expect(report.byFile[0].lineCoverage).toBe(80);
|
|
expect(report.byFile[0].branchCoverage).toBe(70);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('route', () => {
|
|
it('should return CoverageRouteResult', () => {
|
|
const report: CoverageReport = {
|
|
overall: 75,
|
|
byType: { line: 75, branch: 70, function: 80, statement: 75 },
|
|
byFile: [
|
|
{ path: 'src/low.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [1,2,3], totalLines: 100, coveredLines: 50 },
|
|
{ path: 'src/high.ts', lineCoverage: 90, branchCoverage: 85, functionCoverage: 95, statementCoverage: 90, uncoveredLines: [], totalLines: 100, coveredLines: 90 },
|
|
],
|
|
lowestCoverage: [],
|
|
highestCoverage: [],
|
|
uncoveredCritical: [],
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
const result = router.route(report);
|
|
expect(result).toHaveProperty('action');
|
|
expect(result).toHaveProperty('priority');
|
|
expect(result).toHaveProperty('targetFiles');
|
|
expect(result).toHaveProperty('testTypes');
|
|
expect(result).toHaveProperty('gaps');
|
|
expect(result).toHaveProperty('estimatedEffort');
|
|
expect(result).toHaveProperty('impactScore');
|
|
});
|
|
|
|
it('should return prioritize action for low coverage', () => {
|
|
const report: CoverageReport = {
|
|
overall: 50,
|
|
byType: { line: 50, branch: 40, function: 60, statement: 50 },
|
|
byFile: [{ path: 'src/low.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [], totalLines: 100, coveredLines: 50 }],
|
|
lowestCoverage: [],
|
|
highestCoverage: [],
|
|
uncoveredCritical: [],
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
const result = router.route(report);
|
|
expect(result.action).toBe('prioritize');
|
|
});
|
|
|
|
it('should return skip action for high coverage', () => {
|
|
const report: CoverageReport = {
|
|
overall: 95,
|
|
byType: { line: 95, branch: 90, function: 98, statement: 95 },
|
|
byFile: [{ path: 'src/high.ts', lineCoverage: 95, branchCoverage: 90, functionCoverage: 98, statementCoverage: 95, uncoveredLines: [], totalLines: 100, coveredLines: 95 }],
|
|
lowestCoverage: [],
|
|
highestCoverage: [],
|
|
uncoveredCritical: [],
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
const result = router.route(report);
|
|
expect(result.action).toBe('skip');
|
|
});
|
|
|
|
it('should identify coverage gaps', () => {
|
|
const report: CoverageReport = {
|
|
overall: 75,
|
|
byType: { line: 75, branch: 70, function: 80, statement: 75 },
|
|
byFile: [
|
|
{ path: 'src/low.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [1,2,3], totalLines: 100, coveredLines: 50 },
|
|
],
|
|
lowestCoverage: [{ path: 'src/low.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [1,2,3], totalLines: 100, coveredLines: 50 }],
|
|
highestCoverage: [],
|
|
uncoveredCritical: [],
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
const result = router.route(report);
|
|
expect(result.gaps.length).toBeGreaterThan(0);
|
|
expect(result.gaps[0].file).toBe('src/low.ts');
|
|
expect(result.gaps[0].gap).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should prioritize changed files', () => {
|
|
const report: CoverageReport = {
|
|
overall: 80,
|
|
byType: { line: 80, branch: 75, function: 85, statement: 80 },
|
|
byFile: [
|
|
{ path: 'src/changed.ts', lineCoverage: 60, branchCoverage: 50, functionCoverage: 70, statementCoverage: 60, uncoveredLines: [], totalLines: 100, coveredLines: 60 },
|
|
{ path: 'src/unchanged.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [], totalLines: 100, coveredLines: 50 },
|
|
],
|
|
lowestCoverage: [],
|
|
highestCoverage: [],
|
|
uncoveredCritical: [],
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
const result = router.route(report, ['changed.ts']);
|
|
expect(result.targetFiles[0]).toContain('changed');
|
|
});
|
|
|
|
it('should recommend test types based on files', () => {
|
|
const report: CoverageReport = {
|
|
overall: 75,
|
|
byType: { line: 75, branch: 70, function: 80, statement: 75 },
|
|
byFile: [
|
|
{ path: 'src/api/handler.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [], totalLines: 100, coveredLines: 50 },
|
|
],
|
|
lowestCoverage: [],
|
|
highestCoverage: [],
|
|
uncoveredCritical: [],
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
const result = router.route(report);
|
|
expect(result.testTypes).toContain('unit');
|
|
expect(result.testTypes).toContain('integration');
|
|
});
|
|
|
|
it('should calculate estimated effort', () => {
|
|
const report: CoverageReport = {
|
|
overall: 75,
|
|
byType: { line: 75, branch: 70, function: 80, statement: 75 },
|
|
byFile: [
|
|
{ path: 'src/a.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [], totalLines: 100, coveredLines: 50 },
|
|
{ path: 'src/b.ts', lineCoverage: 60, branchCoverage: 50, functionCoverage: 70, statementCoverage: 60, uncoveredLines: [], totalLines: 100, coveredLines: 60 },
|
|
],
|
|
lowestCoverage: [],
|
|
highestCoverage: [],
|
|
uncoveredCritical: [],
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
const result = router.route(report);
|
|
expect(result.estimatedEffort).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('getTrend', () => {
|
|
it('should return stable for insufficient history', () => {
|
|
const trend = router.getTrend();
|
|
expect(trend.direction).toBe('stable');
|
|
expect(trend.change).toBe(0);
|
|
});
|
|
|
|
it('should detect upward trend', () => {
|
|
const report1: CoverageReport = { overall: 70, byType: { line: 70, branch: 65, function: 75, statement: 70 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
|
|
const report2: CoverageReport = { overall: 80, byType: { line: 80, branch: 75, function: 85, statement: 80 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
|
|
|
|
router.addToHistory(report1);
|
|
router.addToHistory(report2);
|
|
|
|
const trend = router.getTrend();
|
|
expect(trend.direction).toBe('up');
|
|
expect(trend.change).toBe(10);
|
|
});
|
|
|
|
it('should detect downward trend', () => {
|
|
const report1: CoverageReport = { overall: 80, byType: { line: 80, branch: 75, function: 85, statement: 80 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
|
|
const report2: CoverageReport = { overall: 70, byType: { line: 70, branch: 65, function: 75, statement: 70 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
|
|
|
|
router.addToHistory(report1);
|
|
router.addToHistory(report2);
|
|
|
|
const trend = router.getTrend();
|
|
expect(trend.direction).toBe('down');
|
|
expect(trend.change).toBe(-10);
|
|
});
|
|
|
|
it('should detect stable trend for small changes', () => {
|
|
const report1: CoverageReport = { overall: 80, byType: { line: 80, branch: 75, function: 85, statement: 80 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
|
|
const report2: CoverageReport = { overall: 80.3, byType: { line: 80.3, branch: 75, function: 85, statement: 80 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
|
|
|
|
router.addToHistory(report1);
|
|
router.addToHistory(report2);
|
|
|
|
const trend = router.getTrend();
|
|
expect(trend.direction).toBe('stable');
|
|
});
|
|
});
|
|
|
|
describe('addToHistory', () => {
|
|
it('should add report to history', () => {
|
|
const report: CoverageReport = { overall: 80, byType: { line: 80, branch: 75, function: 85, statement: 80 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
|
|
|
|
router.addToHistory(report);
|
|
expect(router.getStats().historySize).toBe(1);
|
|
});
|
|
|
|
it('should limit history to 10 entries', () => {
|
|
for (let i = 0; i < 15; i++) {
|
|
const report: CoverageReport = { overall: 70 + i, byType: { line: 70, branch: 65, function: 75, statement: 70 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
|
|
router.addToHistory(report);
|
|
}
|
|
|
|
expect(router.getStats().historySize).toBe(10);
|
|
});
|
|
});
|
|
|
|
describe('critical file detection', () => {
|
|
it('should identify uncovered critical files', () => {
|
|
const data = {
|
|
'src/auth/login.ts': {
|
|
lineCoverage: 50,
|
|
branchCoverage: 40,
|
|
functionCoverage: 60,
|
|
statementCoverage: 50,
|
|
uncoveredLines: [1, 2, 3],
|
|
totalLines: 100,
|
|
coveredLines: 50,
|
|
},
|
|
'src/payment/checkout.ts': {
|
|
lineCoverage: 60,
|
|
branchCoverage: 50,
|
|
functionCoverage: 70,
|
|
statementCoverage: 60,
|
|
uncoveredLines: [1, 2],
|
|
totalLines: 100,
|
|
coveredLines: 60,
|
|
},
|
|
};
|
|
|
|
const report = router.parseCoverage(data, 'json');
|
|
expect(report.uncoveredCritical.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle empty coverage data', () => {
|
|
const report = router.parseCoverage({}, 'json');
|
|
expect(report.byFile).toHaveLength(0);
|
|
expect(report.overall).toBe(100);
|
|
});
|
|
|
|
it('should handle 100% coverage', () => {
|
|
const data = {
|
|
'src/perfect.ts': {
|
|
lineCoverage: 100,
|
|
branchCoverage: 100,
|
|
functionCoverage: 100,
|
|
statementCoverage: 100,
|
|
uncoveredLines: [],
|
|
totalLines: 100,
|
|
coveredLines: 100,
|
|
},
|
|
};
|
|
|
|
const report = router.parseCoverage(data, 'json');
|
|
const result = router.route(report);
|
|
expect(result.action).toBe('skip');
|
|
expect(result.gaps).toHaveLength(0);
|
|
});
|
|
|
|
it('should handle 0% coverage', () => {
|
|
const data = {
|
|
'src/empty.ts': {
|
|
lineCoverage: 0,
|
|
branchCoverage: 0,
|
|
functionCoverage: 0,
|
|
statementCoverage: 0,
|
|
uncoveredLines: Array.from({ length: 100 }, (_, i) => i + 1),
|
|
totalLines: 100,
|
|
coveredLines: 0,
|
|
},
|
|
};
|
|
|
|
const report = router.parseCoverage(data, 'json');
|
|
const result = router.route(report);
|
|
expect(result.action).toBe('prioritize');
|
|
expect(result.priority).toBeGreaterThanOrEqual(8);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createCoverageRouter', () => {
|
|
it('should create router instance', () => {
|
|
const router = createCoverageRouter();
|
|
expect(router).toBeInstanceOf(CoverageRouter);
|
|
});
|
|
|
|
it('should accept config', () => {
|
|
const router = createCoverageRouter({ minCoverage: 80 });
|
|
expect(router).toBeInstanceOf(CoverageRouter);
|
|
});
|
|
});
|
|
|
|
describe('CoverageRouter Advanced Scenarios', () => {
|
|
let router: CoverageRouter;
|
|
|
|
beforeEach(() => {
|
|
router = new CoverageRouter();
|
|
});
|
|
|
|
describe('incremental coverage', () => {
|
|
it('should track coverage changes over time', () => {
|
|
const report1: CoverageReport = {
|
|
overall: 70,
|
|
byType: { line: 70, branch: 65, function: 75, statement: 70 },
|
|
byFile: [
|
|
{ path: 'src/a.ts', lineCoverage: 70, branchCoverage: 65, functionCoverage: 75, statementCoverage: 70, uncoveredLines: [1,2,3], totalLines: 100, coveredLines: 70 },
|
|
],
|
|
lowestCoverage: [],
|
|
highestCoverage: [],
|
|
uncoveredCritical: [],
|
|
timestamp: Date.now() - 1000,
|
|
};
|
|
|
|
const report2: CoverageReport = {
|
|
overall: 80,
|
|
byType: { line: 80, branch: 75, function: 85, statement: 80 },
|
|
byFile: [
|
|
{ path: 'src/a.ts', lineCoverage: 80, branchCoverage: 75, functionCoverage: 85, statementCoverage: 80, uncoveredLines: [1,2], totalLines: 100, coveredLines: 80 },
|
|
],
|
|
lowestCoverage: [],
|
|
highestCoverage: [],
|
|
uncoveredCritical: [],
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
router.addToHistory(report1);
|
|
router.addToHistory(report2);
|
|
|
|
const trend = router.getTrend();
|
|
expect(trend.direction).toBe('up');
|
|
expect(trend.change).toBe(10);
|
|
});
|
|
});
|
|
|
|
describe('priority scoring', () => {
|
|
it('should prioritize critical files with low coverage', () => {
|
|
const report = router.parseCoverage({
|
|
'src/auth/login.ts': {
|
|
lineCoverage: 30,
|
|
branchCoverage: 20,
|
|
functionCoverage: 40,
|
|
statementCoverage: 30,
|
|
uncoveredLines: Array.from({ length: 70 }, (_, i) => i + 1),
|
|
totalLines: 100,
|
|
coveredLines: 30,
|
|
},
|
|
'src/utils/helper.ts': {
|
|
lineCoverage: 30,
|
|
branchCoverage: 20,
|
|
functionCoverage: 40,
|
|
statementCoverage: 30,
|
|
uncoveredLines: Array.from({ length: 70 }, (_, i) => i + 1),
|
|
totalLines: 100,
|
|
coveredLines: 30,
|
|
},
|
|
}, 'json');
|
|
|
|
const result = router.route(report);
|
|
|
|
// Auth file should be prioritized over utils
|
|
const authIndex = result.targetFiles.findIndex(f => f.includes('auth'));
|
|
const utilsIndex = result.targetFiles.findIndex(f => f.includes('utils'));
|
|
|
|
if (authIndex !== -1 && utilsIndex !== -1) {
|
|
expect(authIndex).toBeLessThan(utilsIndex);
|
|
}
|
|
});
|
|
|
|
it('should consider file complexity in priority', () => {
|
|
const report = router.parseCoverage({
|
|
'src/simple.ts': {
|
|
lineCoverage: 60,
|
|
branchCoverage: 50,
|
|
functionCoverage: 70,
|
|
statementCoverage: 60,
|
|
uncoveredLines: [1, 2, 3, 4],
|
|
totalLines: 10,
|
|
coveredLines: 6,
|
|
},
|
|
'src/complex.ts': {
|
|
lineCoverage: 60,
|
|
branchCoverage: 50,
|
|
functionCoverage: 70,
|
|
statementCoverage: 60,
|
|
uncoveredLines: Array.from({ length: 40 }, (_, i) => i + 1),
|
|
totalLines: 100,
|
|
coveredLines: 60,
|
|
},
|
|
}, 'json');
|
|
|
|
const result = router.route(report);
|
|
|
|
// Both should be in target files since both are below threshold
|
|
expect(result.targetFiles.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('test type recommendations', () => {
|
|
it('should recommend e2e tests for UI files', () => {
|
|
const report = router.parseCoverage({
|
|
'src/components/Button.tsx': {
|
|
lineCoverage: 50,
|
|
branchCoverage: 40,
|
|
functionCoverage: 60,
|
|
statementCoverage: 50,
|
|
uncoveredLines: [1, 2, 3],
|
|
totalLines: 100,
|
|
coveredLines: 50,
|
|
},
|
|
}, 'json');
|
|
|
|
const result = router.route(report);
|
|
expect(result.testTypes).toContain('unit');
|
|
});
|
|
|
|
it('should recommend integration tests for API handlers', () => {
|
|
const report = router.parseCoverage({
|
|
'src/api/users/handler.ts': {
|
|
lineCoverage: 50,
|
|
branchCoverage: 40,
|
|
functionCoverage: 60,
|
|
statementCoverage: 50,
|
|
uncoveredLines: [1, 2, 3],
|
|
totalLines: 100,
|
|
coveredLines: 50,
|
|
},
|
|
}, 'json');
|
|
|
|
const result = router.route(report);
|
|
expect(result.testTypes).toContain('integration');
|
|
});
|
|
});
|
|
|
|
describe('gap analysis', () => {
|
|
it('should identify branch coverage gaps', () => {
|
|
const report = router.parseCoverage({
|
|
'src/logic.ts': {
|
|
lineCoverage: 50, // Below threshold
|
|
branchCoverage: 30, // Significant branch coverage gap
|
|
functionCoverage: 50,
|
|
statementCoverage: 50,
|
|
uncoveredLines: [1, 2, 3, 4, 5],
|
|
totalLines: 100,
|
|
coveredLines: 50,
|
|
},
|
|
}, 'json');
|
|
|
|
const result = router.route(report);
|
|
|
|
// Should have some gaps since coverage is below threshold
|
|
expect(result.gaps.length >= 0).toBe(true);
|
|
});
|
|
|
|
it('should calculate gap delta from target', () => {
|
|
const customRouter = new CoverageRouter({ targetCoverage: 90 });
|
|
|
|
const report = customRouter.parseCoverage({
|
|
'src/file.ts': {
|
|
lineCoverage: 70,
|
|
branchCoverage: 60,
|
|
functionCoverage: 80,
|
|
statementCoverage: 70,
|
|
uncoveredLines: [1, 2, 3],
|
|
totalLines: 100,
|
|
coveredLines: 70,
|
|
},
|
|
}, 'json');
|
|
|
|
const result = customRouter.route(report);
|
|
|
|
// Gap should be calculated from target (90 - 70 = 20)
|
|
expect(result.gaps.some(g => g.gap >= 15)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('effort estimation', () => {
|
|
it('should estimate higher effort for more uncovered lines', () => {
|
|
const smallGapReport = router.parseCoverage({
|
|
'src/small.ts': {
|
|
lineCoverage: 95,
|
|
branchCoverage: 90,
|
|
functionCoverage: 100,
|
|
statementCoverage: 95,
|
|
uncoveredLines: [1],
|
|
totalLines: 20,
|
|
coveredLines: 19,
|
|
},
|
|
}, 'json');
|
|
|
|
const largeGapReport = router.parseCoverage({
|
|
'src/large.ts': {
|
|
lineCoverage: 50,
|
|
branchCoverage: 40,
|
|
functionCoverage: 60,
|
|
statementCoverage: 50,
|
|
uncoveredLines: Array.from({ length: 50 }, (_, i) => i + 1),
|
|
totalLines: 100,
|
|
coveredLines: 50,
|
|
},
|
|
}, 'json');
|
|
|
|
const smallResult = router.route(smallGapReport);
|
|
const largeResult = router.route(largeGapReport);
|
|
|
|
expect(largeResult.estimatedEffort).toBeGreaterThan(smallResult.estimatedEffort);
|
|
});
|
|
});
|
|
|
|
describe('format handling', () => {
|
|
it('should handle clover format', () => {
|
|
const clover = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<coverage>
|
|
<project>
|
|
<file path="src/utils.ts">
|
|
<line num="1" type="stmt" count="1"/>
|
|
<line num="2" type="stmt" count="0"/>
|
|
<metrics loc="10" ncloc="8" statements="8" coveredstatements="6"/>
|
|
</file>
|
|
</project>
|
|
</coverage>`;
|
|
|
|
// This may or may not be supported depending on implementation
|
|
try {
|
|
const report = router.parseCoverage(clover, 'cobertura');
|
|
expect(report).toBeDefined();
|
|
} catch {
|
|
// Clover format not supported is acceptable — no assertion needed
|
|
}
|
|
});
|
|
|
|
it('should handle malformed LCOV gracefully', () => {
|
|
const malformedLcov = `
|
|
not valid lcov format
|
|
SF:file.ts
|
|
missing end_of_record
|
|
random garbage
|
|
`;
|
|
const report = router.parseCoverage(malformedLcov, 'lcov');
|
|
// Should not throw and return some result
|
|
expect(report).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('impact scoring', () => {
|
|
it('should calculate impact score based on file criticality', () => {
|
|
const report1 = router.parseCoverage({
|
|
'src/auth/jwt.ts': {
|
|
lineCoverage: 50,
|
|
branchCoverage: 40,
|
|
functionCoverage: 60,
|
|
statementCoverage: 50,
|
|
uncoveredLines: [1, 2, 3],
|
|
totalLines: 100,
|
|
coveredLines: 50,
|
|
},
|
|
}, 'json');
|
|
|
|
const report2 = router.parseCoverage({
|
|
'src/utils/format.ts': {
|
|
lineCoverage: 50,
|
|
branchCoverage: 40,
|
|
functionCoverage: 60,
|
|
statementCoverage: 50,
|
|
uncoveredLines: [1, 2, 3],
|
|
totalLines: 100,
|
|
coveredLines: 50,
|
|
},
|
|
}, 'json');
|
|
|
|
const result1 = router.route(report1);
|
|
const result2 = router.route(report2);
|
|
|
|
// Auth file should have higher impact score
|
|
expect(result1.impactScore).toBeGreaterThanOrEqual(result2.impactScore);
|
|
});
|
|
});
|
|
});
|