test(ai-review): 補行號定位、誤報裁決邊界、統計與 appendExclusions 測試
This commit is contained in:
+57
-1
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { loadOldFindings, loadExclusions, applyExclusions, filterFalsePositivesWithAI, appendExclusions } from './findings.js';
|
||||
import { loadOldFindings, loadExclusions, applyExclusions, filterFalsePositivesWithAI, appendExclusions, resolveMissingLineNumbers } from './findings.js';
|
||||
import { EXCLUSIONS_PATH, FINDINGS_PATH } from './config.js';
|
||||
|
||||
describe('findings exclusions', () => {
|
||||
@@ -59,6 +59,18 @@ describe('findings exclusions', () => {
|
||||
assert.equal(merged.length, 2);
|
||||
});
|
||||
|
||||
it('appendExclusions keeps same-path entries that have different original text', () => {
|
||||
const fullPath = path.join(workspace, EXCLUSIONS_PATH);
|
||||
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
||||
fs.writeFileSync(fullPath, JSON.stringify([{ location: 'app/a.js:1', original_finding: '問題甲' }], null, 2));
|
||||
|
||||
appendExclusions(workspace, [{ location: 'app/a.js:5', original_finding: '問題乙', reason: 'r' }]);
|
||||
|
||||
const onDisk = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
|
||||
assert.equal(onDisk.length, 2); // 同檔但原文不同 → 視為不同排除條目,兩者皆保留
|
||||
assert.deepEqual(onDisk.map(e => e.original_finding), ['問題甲', '問題乙']);
|
||||
});
|
||||
|
||||
it('writes appended exclusions to both workspace and mirror dir', () => {
|
||||
const repoRoot = path.join(workspace, 'repo');
|
||||
fs.mkdirSync(repoRoot, { recursive: true });
|
||||
@@ -225,6 +237,50 @@ describe('findings exclusions', () => {
|
||||
assert.equal(result.length, 2);
|
||||
});
|
||||
|
||||
it('keeps a finding when the defender returns an out-of-range verdict value', async () => {
|
||||
const findings = [{ level: 'warning', role: 'Mage', location: 'a.js:1', problem: 'p', suggestion: 's' }];
|
||||
const chatFn = async () => ({ verdict: 'maybe', reason: 'x' }); // 非 confirmed/false_positive
|
||||
const result = await filterFalsePositivesWithAI(findings, [], chatFn);
|
||||
assert.equal(result.length, 1); // 只有明確 false_positive 才剔除,其餘保守保留
|
||||
});
|
||||
|
||||
it('resolveMissingLineNumbers fills missing line numbers by re-asking the role', async () => {
|
||||
const findings = [
|
||||
{ level: 'critical', role: 'Maya', location: 'app/a.js', problem: 'p', suggestion: 's' },
|
||||
{ level: 'warning', role: 'Leo', location: 'app/b.js:20', problem: 'p', suggestion: 's' }, // 已有行號 → 不動
|
||||
];
|
||||
let calls = 0;
|
||||
const chatFn = async () => { calls += 1; return { line: 42 }; };
|
||||
|
||||
await resolveMissingLineNumbers(findings, 'diff --git a/app/a.js b/app/a.js\n@@ -1 +1 @@', { chatFn, getRole: () => ({ name: 'Maya' }) });
|
||||
|
||||
assert.equal(findings[0].location, 'app/a.js:42'); // 補上行號
|
||||
assert.equal(findings[1].location, 'app/b.js:20'); // 不變
|
||||
assert.equal(calls, 1); // 只對缺行號者呼叫
|
||||
});
|
||||
|
||||
it('resolveMissingLineNumbers retries until a valid line appears', async () => {
|
||||
const findings = [{ level: 'warning', role: 'Leo', location: 'app/x.js', problem: 'p', suggestion: 's' }];
|
||||
let n = 0;
|
||||
const chatFn = async () => { n += 1; return n < 3 ? { line: 0 } : { line: 7 }; };
|
||||
|
||||
await resolveMissingLineNumbers(findings, 'd', { chatFn, getRole: () => null, maxAttempts: 5 });
|
||||
|
||||
assert.equal(findings[0].location, 'app/x.js:7');
|
||||
assert.equal(n, 3); // 第三次才給出有效行號
|
||||
});
|
||||
|
||||
it('resolveMissingLineNumbers keeps the filename after exhausting retries', async () => {
|
||||
const findings = [{ level: 'warning', role: 'Leo', location: 'app/y.js', problem: 'p', suggestion: 's' }];
|
||||
let n = 0;
|
||||
const chatFn = async () => { n += 1; return { line: 0 }; };
|
||||
|
||||
await resolveMissingLineNumbers(findings, 'd', { chatFn, getRole: () => null, maxAttempts: 3 });
|
||||
|
||||
assert.equal(findings[0].location, 'app/y.js'); // 仍保留檔名
|
||||
assert.equal(n, 3); // 嘗試 3 次後放棄
|
||||
});
|
||||
|
||||
it('logs exclusions file metadata and repo state when loading exclusions', () => {
|
||||
const fullPath = path.join(workspace, EXCLUSIONS_PATH);
|
||||
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user