test(review-resolve): 更新 wrapper 與模型驗證測試

This commit is contained in:
2026-08-07 16:42:22 +00:00
parent a8d3fb60ed
commit 938db793a7
4 changed files with 68 additions and 34 deletions
+15 -9
View File
@@ -21,10 +21,14 @@ describe('saveFindings', () => {
saveFindings(workspace, findings, mirrorDir);
const workspaceText = fs.readFileSync(path.join(workspace, FINDINGS_PATH), 'utf8');
const mirrorText = fs.readFileSync(path.join(mirrorDir, FINDINGS_PATH), 'utf8');
assert.equal(workspaceText, JSON.stringify(findings, null, 2) + '\n');
assert.equal(mirrorText, JSON.stringify(findings, null, 2) + '\n');
const workspaceData = JSON.parse(fs.readFileSync(path.join(workspace, FINDINGS_PATH), 'utf8'));
const mirrorData = JSON.parse(fs.readFileSync(path.join(mirrorDir, FINDINGS_PATH), 'utf8'));
assert.equal(typeof workspaceData.generatedAt, 'string');
assert.equal(typeof workspaceData.commitSha, 'string');
assert.ok(Array.isArray(workspaceData.findings));
assert.deepEqual(workspaceData.findings, findings);
assert.deepEqual(workspaceData.excluded, []);
assert.deepEqual(mirrorData, workspaceData);
});
it('writes only to workspace when mirrorDir is omitted', () => {
@@ -33,8 +37,9 @@ describe('saveFindings', () => {
saveFindings(workspace, findings);
const workspaceText = fs.readFileSync(path.join(workspace, FINDINGS_PATH), 'utf8');
assert.equal(workspaceText, JSON.stringify(findings, null, 2) + '\n');
const workspaceData = JSON.parse(fs.readFileSync(path.join(workspace, FINDINGS_PATH), 'utf8'));
assert.deepEqual(workspaceData.findings, findings);
assert.deepEqual(workspaceData.excluded, []);
});
it('does not duplicate writes when mirrorDir matches workspace', () => {
@@ -58,13 +63,14 @@ describe('saveFindings', () => {
assert.equal(writeCalls[0], path.join(workspace, FINDINGS_PATH));
});
it('writes an empty JSON array when findings is empty', () => {
it('writes an empty findings wrapper when findings is empty', () => {
const workspace = makeTempDir('findings-empty-');
saveFindings(workspace, []);
const workspaceText = fs.readFileSync(path.join(workspace, FINDINGS_PATH), 'utf8');
assert.equal(workspaceText, '[]\n');
const workspaceData = JSON.parse(fs.readFileSync(path.join(workspace, FINDINGS_PATH), 'utf8'));
assert.deepEqual(workspaceData.findings, []);
assert.deepEqual(workspaceData.excluded, []);
});
afterEach(() => {
+10
View File
@@ -72,6 +72,16 @@ describe('getLLMConfig', () => {
assert.match(cfg.modelError, /無效的 model 參數/);
});
it('accepts slash-delimited model names', () => {
process.env.CLI_PROXY_API = 'https://proxy.example';
process.env.MODEL = 'provider/gpt-5.5';
const cfg = getLLMConfig();
assert.equal(cfg.model, 'provider/gpt-5.5');
assert.equal(cfg.modelError, null);
});
it('returns null provider when CLI_PROXY_API is missing', () => {
process.env.MODEL = 'gpt-5.5';
const cfg = getLLMConfig();
+11 -4
View File
@@ -348,12 +348,19 @@ describe('findings exclusions', () => {
assert.ok(logs.some(line => line.includes(`path=${path.relative(workspace, fullPath)}`)));
});
it('logs findings file metadata when loading old findings', () => {
it('loads wrapper findings and logs findings file metadata', () => {
const fullPath = path.join(workspace, FINDINGS_PATH);
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, JSON.stringify([
{ level: 'info', role: 'Maya', location: 'README.md:12', suggestion: 'keep' },
], null, 2));
fs.writeFileSync(fullPath, JSON.stringify({
generatedAt: '2026/08/07 16:47:53',
commitSha: 'deadbeef',
prNumber: 7,
tool: { name: 'ai-code-review', version: '1.0.0', model: 'auto' },
findings: [
{ level: 'info', role: 'Maya', location: 'README.md:12', suggestion: 'keep' },
],
excluded: [],
}, null, 2));
const findings = loadOldFindings(workspace);
+32 -21
View File
@@ -37,6 +37,19 @@ describe('json helpers', () => {
assert.ok(capturedUserContent.includes('"{broken"'));
});
it('creates an empty findings wrapper when asked to ensure existence', () => {
const fullPath = path.join(workspace, '.gitea/ai-review/findings.json');
const created = ensureJSONArrayFileExists(fullPath, '.gitea/ai-review/findings.json');
assert.equal(created, true);
const written = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
assert.equal(typeof written.generatedAt, 'string');
assert.ok(Array.isArray(written.findings));
assert.deepEqual(written.findings, []);
assert.deepEqual(written.excluded, []);
});
it('reports missing file without creating it', async () => {
const fullPath = path.join(workspace, '.gitea/ai-review/findings.json');
@@ -46,15 +59,6 @@ describe('json helpers', () => {
assert.equal(fs.existsSync(fullPath), false);
});
it('creates an empty array file when asked to ensure existence', () => {
const fullPath = path.join(workspace, '.gitea/ai-review/findings.json');
const created = ensureJSONArrayFileExists(fullPath, '.gitea/ai-review/findings.json');
assert.equal(created, true);
assert.equal(fs.readFileSync(fullPath, 'utf8'), '[]\n');
});
it('returns false when ensuring an existing file', () => {
const fullPath = path.join(workspace, '.gitea/ai-review/exclusions.json');
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
@@ -77,29 +81,32 @@ describe('json helpers', () => {
assert.equal(fs.readFileSync(fullPath, 'utf8'), '[]\n');
});
it('rejects repaired JSON that is not an array', async () => {
it('rejects repaired JSON that is not a findings wrapper', async () => {
const fullPath = path.join(workspace, '.gitea/ai-review/findings.json');
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, '{broken', 'utf8');
await assert.rejects(
() => validateJSONArrayFile(fullPath, '.gitea/ai-review/findings.json', async () => '{"ok":true}'),
/不是 JSON 陣列/,
/不是 findings wrapper/,
);
assert.equal(fs.readFileSync(fullPath, 'utf8'), '{broken');
});
it('reads a valid JSON file whose size equals the maximum limit', async () => {
it('normalizes a valid legacy findings array whose size equals the maximum limit', async () => {
const fullPath = path.join(workspace, '.gitea/ai-review/findings.json');
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, `[]${' '.repeat(MAX_JSON_BYTES - 2)}`, 'utf8');
const result = await validateJSONArrayFile(fullPath, '.gitea/ai-review/findings.json');
assert.deepEqual(result, { exists: true, valid: true, repaired: false });
assert.deepEqual(result, { exists: true, valid: true, repaired: true });
const written = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
assert.deepEqual(written.findings, []);
assert.deepEqual(written.excluded, []);
});
it('repairs invalid JSON using AI output and rewrites the file', async () => {
it('repairs invalid findings JSON using AI output and rewrites the file as a wrapper', async () => {
const fullPath = path.join(workspace, '.gitea/ai-review/findings.json');
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, '{broken', 'utf8');
@@ -110,7 +117,9 @@ describe('json helpers', () => {
});
assert.deepEqual(result, { exists: true, valid: true, repaired: true });
assert.equal(fs.readFileSync(fullPath, 'utf8'), '[{"fixed":true}]\n');
const written = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
assert.deepEqual(written.findings, [{ fixed: true }]);
assert.deepEqual(written.excluded, []);
});
it('preserves a trailing newline returned by AI repair', async () => {
@@ -124,7 +133,9 @@ describe('json helpers', () => {
});
assert.deepEqual(result, { exists: true, valid: true, repaired: true });
assert.equal(fs.readFileSync(fullPath, 'utf8'), '[{"fixed":true}]\n');
const written = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
assert.deepEqual(written.findings, [{ fixed: true }]);
assert.deepEqual(written.excluded, []);
});
it('throws when AI repair fails', async () => {
@@ -163,7 +174,7 @@ describe('validateJSONArrayFile repair failure paths', () => {
fs.rmSync(workspace, { recursive: true, force: true });
});
it('overwrites the invalid file with the valid array returned by the repairer', async () => {
it('overwrites the invalid findings file with a wrapper built from the repaired array', async () => {
const fullPath = path.join(workspace, '.gitea/ai-review/findings.json');
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, '{ this is not json', 'utf8');
@@ -183,10 +194,10 @@ describe('validateJSONArrayFile repair failure paths', () => {
assert.equal(receivedOriginal, '{ this is not json');
assert.deepEqual(result, { exists: true, valid: true, repaired: true });
// file is overwritten with the repaired content, trailing newline appended (line 110)
const written = fs.readFileSync(fullPath, 'utf8');
assert.equal(written, '[{"id":1},{"id":2}]\n');
assert.deepEqual(JSON.parse(written), [{ id: 1 }, { id: 2 }]);
const written = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
assert.equal(typeof written.generatedAt, 'string');
assert.deepEqual(written.findings, [{ id: 1 }, { id: 2 }]);
assert.deepEqual(written.excluded, []);
});
it('throws when the repaired text is still invalid JSON and does NOT overwrite the original file', async () => {