From 938db793a71afda1acd81bb86b83df5bba411172 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 7 Aug 2026 16:42:22 +0000 Subject: [PATCH] =?UTF-8?q?test(review-resolve):=20=E6=9B=B4=E6=96=B0=20wr?= =?UTF-8?q?apper=20=E8=88=87=E6=A8=A1=E5=9E=8B=E9=A9=97=E8=AD=89=E6=B8=AC?= =?UTF-8?q?=E8=A9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/comments.test.js | 24 +++++++++++------- src/test/config.test.js | 10 ++++++++ src/test/findings.test.js | 15 ++++++++--- src/test/json.test.js | 53 +++++++++++++++++++++++---------------- 4 files changed, 68 insertions(+), 34 deletions(-) diff --git a/src/test/comments.test.js b/src/test/comments.test.js index b7f7656..7c5081f 100644 --- a/src/test/comments.test.js +++ b/src/test/comments.test.js @@ -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(() => { diff --git a/src/test/config.test.js b/src/test/config.test.js index 5286a23..cfe1e36 100644 --- a/src/test/config.test.js +++ b/src/test/config.test.js @@ -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(); diff --git a/src/test/findings.test.js b/src/test/findings.test.js index 71edada..d17fed6 100644 --- a/src/test/findings.test.js +++ b/src/test/findings.test.js @@ -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); diff --git a/src/test/json.test.js b/src/test/json.test.js index 22672c8..9afa0ec 100644 --- a/src/test/json.test.js +++ b/src/test/json.test.js @@ -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 () => {