Files
code-review/app/comments.test.js
T

187 lines
6.9 KiB
JavaScript

import { describe, it, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { saveFindings, parseLocation, postNewCriticalComments } from './comments.js';
import { FINDINGS_PATH } from './config.js';
describe('saveFindings', () => {
const tempDirs = [];
const makeTempDir = prefix => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
tempDirs.push(dir);
return dir;
};
it('writes findings to workspace and mirror dirs when provided', () => {
const workspace = makeTempDir('findings-ws-');
const mirrorDir = makeTempDir('findings-mirror-');
const findings = [{ level: 'warning', role: 'Leo', location: 'file.js:1', suggestion: 'test' }];
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');
});
it('writes only to workspace when mirrorDir is omitted', () => {
const workspace = makeTempDir('findings-ws-');
const findings = [{ level: 'info', role: 'Maya', location: 'file.js:2', suggestion: 'note' }];
saveFindings(workspace, findings);
const workspaceText = fs.readFileSync(path.join(workspace, FINDINGS_PATH), 'utf8');
assert.equal(workspaceText, JSON.stringify(findings, null, 2) + '\n');
});
it('does not duplicate writes when mirrorDir matches workspace', () => {
const workspace = makeTempDir('findings-same-');
const findings = [];
const writeCalls = [];
const originalWriteFileSync = fs.writeFileSync;
fs.writeFileSync = (...args) => {
writeCalls.push(args[0]);
return originalWriteFileSync(...args);
};
try {
saveFindings(workspace, findings, workspace);
} finally {
fs.writeFileSync = originalWriteFileSync;
}
assert.equal(writeCalls.length, 1);
assert.equal(writeCalls[0], path.join(workspace, FINDINGS_PATH));
});
it('writes an empty JSON array 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');
});
afterEach(() => {
while (tempDirs.length > 0) {
fs.rmSync(tempDirs.pop(), { recursive: true, force: true });
}
});
});
describe('parseLocation', () => {
it('parses file and single line', () => {
assert.deepEqual(parseLocation('app/preflight.js:19'), { file: 'app/preflight.js', line: 19 });
});
it('uses the start line for a line range', () => {
assert.deepEqual(parseLocation('app/preflight.js:70-82'), { file: 'app/preflight.js', line: 70 });
});
it('returns null when there is no line number', () => {
assert.equal(parseLocation('app/preflight.test.js'), null);
});
it('returns null when multiple files are listed', () => {
assert.equal(parseLocation('Dockerfile, app/git.js, app/gitea.js'), null);
});
it('returns null for non-string input', () => {
assert.equal(parseLocation(undefined), null);
});
});
describe('postNewCriticalComments', () => {
const critical = { level: 'critical', role: 'Rex', location: 'app/preflight.js:19', suggestion: '修這個', is_new: true };
it('posts an inline review comment annotating file/line with level/role/suggestion', async () => {
const inlineCalls = [];
const issueCalls = [];
await postNewCriticalComments([critical], {
postInline: async (args) => { inlineCalls.push(args); },
postIssue: async (body) => { issueCalls.push(body); },
});
assert.equal(inlineCalls.length, 1);
assert.equal(issueCalls.length, 0);
assert.equal(inlineCalls[0].path, 'app/preflight.js');
assert.equal(inlineCalls[0].line, 19);
assert.match(inlineCalls[0].body, /等級/);
assert.match(inlineCalls[0].body, /.*Rex/s);
assert.match(inlineCalls[0].body, /.*/s);
});
it('falls back to a normal comment when the location has no line number', async () => {
const inlineCalls = [];
const issueCalls = [];
await postNewCriticalComments([{ ...critical, location: 'app/preflight.js' }], {
postInline: async (args) => { inlineCalls.push(args); },
postIssue: async (body) => { issueCalls.push(body); },
});
assert.equal(inlineCalls.length, 0);
assert.equal(issueCalls.length, 1);
assert.match(issueCalls[0], /嚴重問題/);
});
it('falls back to a normal comment when the inline post fails', async () => {
const issueCalls = [];
await postNewCriticalComments([critical], {
postInline: async () => { throw new Error('line not in diff'); },
postIssue: async (body) => { issueCalls.push(body); },
});
assert.equal(issueCalls.length, 1);
assert.match(issueCalls[0], /嚴重問題/);
});
it('only posts for new critical findings', async () => {
const inlineCalls = [];
const issueCalls = [];
await postNewCriticalComments([
{ ...critical, is_new: false },
{ level: 'warning', role: 'Leo', location: 'a.js:1', suggestion: 'x', is_new: true },
], {
postInline: async (args) => { inlineCalls.push(args); },
postIssue: async (body) => { issueCalls.push(body); },
});
assert.equal(inlineCalls.length, 0);
assert.equal(issueCalls.length, 0);
});
it('posts nothing when given an empty findings array', async () => {
const inlineCalls = [];
const issueCalls = [];
await postNewCriticalComments([], {
postInline: async (args) => { inlineCalls.push(args); },
postIssue: async (body) => { issueCalls.push(body); },
});
assert.equal(inlineCalls.length, 0);
assert.equal(issueCalls.length, 0);
});
it('handles multiple criticals, posting inline where possible and degrading the rest', async () => {
const inlineCalls = [];
const issueCalls = [];
const findings = [
{ ...critical, location: 'app/a.js:10', suggestion: 'A' }, // 有行號、inline 成功
{ ...critical, location: 'app/b.js', suggestion: 'B' }, // 無行號 → 降級為一般 comment
{ ...critical, location: 'app/c.js:20', suggestion: 'C' }, // inline 拋錯 → 降級為一般 comment
];
await postNewCriticalComments(findings, {
postInline: async (args) => {
if (args.path === 'app/c.js') throw new Error('line not in diff');
inlineCalls.push(args);
},
postIssue: async (body) => { issueCalls.push(body); },
});
assert.equal(inlineCalls.length, 1);
assert.equal(inlineCalls[0].path, 'app/a.js');
assert.equal(inlineCalls[0].line, 10);
assert.equal(issueCalls.length, 2);
assert.ok(issueCalls.every(b => /嚴重問題/.test(b)));
});
});