fix(json/comments): validateJSONArrayFile 先驗證再寫入避免毀損原檔、findingRow 對空值防呆
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fbb74092cb
commit
402630fa69
@@ -140,6 +140,85 @@ describe('json helpers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateJSONArrayFile repair failure paths', () => {
|
||||
let workspace;
|
||||
|
||||
beforeEach(() => {
|
||||
workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'json-test-repair-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(workspace, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('overwrites the invalid file with the valid array returned by the repairer', 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');
|
||||
|
||||
let receivedOriginal;
|
||||
const result = await validateJSONArrayFile(
|
||||
fullPath,
|
||||
'.gitea/ai-review/findings.json',
|
||||
async (passedPath, passedLabel, original) => {
|
||||
// repairer is called with (fullPath, label, original) per json.js line 109
|
||||
assert.equal(passedPath, fullPath);
|
||||
assert.equal(passedLabel, '.gitea/ai-review/findings.json');
|
||||
receivedOriginal = original;
|
||||
return '[{"id":1},{"id":2}]';
|
||||
}
|
||||
);
|
||||
|
||||
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 }]);
|
||||
});
|
||||
|
||||
it('throws when the repaired text is still invalid JSON and does NOT overwrite the original file', async () => {
|
||||
const fullPath = path.join(workspace, '.gitea/ai-review/findings.json');
|
||||
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
||||
const originalBytes = '{ still broken';
|
||||
fs.writeFileSync(fullPath, originalBytes, 'utf8');
|
||||
|
||||
const stillInvalid = 'not a json array either';
|
||||
await assert.rejects(
|
||||
() => validateJSONArrayFile(
|
||||
fullPath,
|
||||
'.gitea/ai-review/findings.json',
|
||||
async () => stillInvalid
|
||||
),
|
||||
SyntaxError
|
||||
);
|
||||
|
||||
// json.js validates the repaired text in-memory BEFORE writing, so an invalid
|
||||
// repair throws without corrupting the file — the original bytes are preserved.
|
||||
const after = fs.readFileSync(fullPath, 'utf8');
|
||||
assert.equal(after, originalBytes);
|
||||
});
|
||||
|
||||
it('propagates an error thrown by the repairer', async () => {
|
||||
const fullPath = path.join(workspace, '.gitea/ai-review/findings.json');
|
||||
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
||||
fs.writeFileSync(fullPath, '{ broken', 'utf8');
|
||||
|
||||
const boom = new Error('repairer exploded');
|
||||
await assert.rejects(
|
||||
() => validateJSONArrayFile(
|
||||
fullPath,
|
||||
'.gitea/ai-review/findings.json',
|
||||
async () => { throw boom; }
|
||||
),
|
||||
/repairer exploded/
|
||||
);
|
||||
|
||||
// repairer threw before any write happened, so the original bytes remain untouched (line 109)
|
||||
assert.equal(fs.readFileSync(fullPath, 'utf8'), '{ broken');
|
||||
});
|
||||
});
|
||||
|
||||
describe('repairJSONArrayWithAI', () => {
|
||||
it('returns a clean JSON array string parseable by the caller', async () => {
|
||||
const repaired = await repairJSONArrayWithAI(
|
||||
|
||||
Reference in New Issue
Block a user