fix: dual-review round-2 — partial diffs cap the verdict, bounded fallback chain, URL-safe paths
Codex adversarial review of the fallback chain found 1 P1 + 3 P2:
- P1: head-only and incomplete per-commit fallbacks could present a SUBSET
diff as a full passing review. fetchDiff now returns {diff, partial, note};
review.js caps partial reviews at 'warn' (same contract as truncation) and
labels the status 'partial review (<reason>)'.
- getRepo default-branch lookup swallows only 404; auth/5xx propagate.
- per-commit reconstruction dedupes + validates shas (40-hex) and hard-caps
at 20 requests; capped/short coverage marks the result partial; the
all-sources-failed message is bounded to 1500 chars.
- gitea.js URL-encodes every webhook-supplied path segment (owner/repo/ref/
sha) — branch names with '/' or '#' can no longer distort request paths.
Tests: 61 pass (2 new: sha cap/dedupe, getRepo 401 propagation).
Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
+54
-11
@@ -18,7 +18,8 @@ const BEFORE = 'b'.repeat(40);
|
||||
test('PR jobs use the PR diff', async () => {
|
||||
const g = giteaStub();
|
||||
const out = await fetchDiff({ gitea: g, job: { owner: 'o', repo: 'r', prIndex: 5, sha: HEAD, before: null } });
|
||||
assert.equal(out, 'PRDIFF');
|
||||
assert.equal(out.diff, 'PRDIFF');
|
||||
assert.equal(out.partial, false);
|
||||
assert.deepEqual(g.calls, [['pr', 'o', 'r', 5]]);
|
||||
});
|
||||
|
||||
@@ -32,7 +33,8 @@ test('push uses ONE compare diff over the full before...after range', async () =
|
||||
commitShas: Array.from({ length: 25 }, (_, i) => String(i).padStart(40, '0'))
|
||||
}
|
||||
});
|
||||
assert.equal(out, 'RANGEDIFF');
|
||||
assert.equal(out.diff, 'RANGEDIFF');
|
||||
assert.equal(out.partial, false);
|
||||
assert.deepEqual(g.calls, [['compare', 'o', 'r', BEFORE, HEAD]]);
|
||||
});
|
||||
|
||||
@@ -42,7 +44,8 @@ test('new-branch push (before = zero sha) falls back to head commit diff', async
|
||||
gitea: g,
|
||||
job: { owner: 'o', repo: 'r', prIndex: null, sha: HEAD, before: '0'.repeat(40), commitShas: [HEAD] }
|
||||
});
|
||||
assert.equal(out, 'HEADDIFF');
|
||||
assert.equal(out.diff, 'HEADDIFF');
|
||||
assert.equal(out.partial, false);
|
||||
assert.deepEqual(g.calls, [['commit', 'o', 'r', HEAD]]);
|
||||
});
|
||||
|
||||
@@ -52,7 +55,8 @@ test('missing before also falls back to head commit diff', async () => {
|
||||
gitea: g,
|
||||
job: { owner: 'o', repo: 'r', prIndex: null, sha: HEAD, before: null, commitShas: [HEAD] }
|
||||
});
|
||||
assert.equal(out, 'HEADDIFF');
|
||||
assert.equal(out.diff, 'HEADDIFF');
|
||||
assert.equal(out.partial, false);
|
||||
assert.deepEqual(g.calls, [['commit', 'o', 'r', HEAD]]);
|
||||
});
|
||||
|
||||
@@ -84,7 +88,8 @@ test('compare 404 falls back to default-branch compare', async () => {
|
||||
gitea: g,
|
||||
job: { owner: 'o', repo: 'r', prIndex: null, sha: HEAD, before: BEFORE, defaultBranch: 'main' }
|
||||
});
|
||||
assert.equal(out, 'RANGEDIFF');
|
||||
assert.equal(out.diff, 'RANGEDIFF');
|
||||
assert.equal(out.partial, false);
|
||||
assert.deepEqual(g.calls.at(-1), ['compare', 'o', 'r', 'main', HEAD]);
|
||||
});
|
||||
|
||||
@@ -99,7 +104,8 @@ test('compare 404 without defaultBranch on the job looks it up via getRepo', asy
|
||||
gitea: g,
|
||||
job: { owner: 'o', repo: 'r', prIndex: null, sha: HEAD, before: BEFORE }
|
||||
});
|
||||
assert.equal(out, 'RANGEDIFF');
|
||||
assert.equal(out.diff, 'RANGEDIFF');
|
||||
assert.equal(out.partial, false);
|
||||
assert.deepEqual(g.calls.at(-1), ['compare', 'o', 'r', 'develop', HEAD]);
|
||||
});
|
||||
|
||||
@@ -120,7 +126,8 @@ test('both compares 404 -> API commit list -> concatenated per-commit diffs', as
|
||||
gitea: g,
|
||||
job: { owner: 'o', repo: 'r', prIndex: null, sha: HEAD, before: BEFORE, defaultBranch: 'main' }
|
||||
});
|
||||
assert.equal(out, 'DIFF(c)\nDIFF(d)');
|
||||
assert.equal(out.diff, 'DIFF(c)\nDIFF(d)');
|
||||
assert.equal(out.partial, false);
|
||||
assert.deepEqual(g.calls.filter(c => c[0] === 'commit'), [['commit', 'o', 'r', C1], ['commit', 'o', 'r', C2]]);
|
||||
});
|
||||
|
||||
@@ -142,7 +149,8 @@ test('API compare 404 too -> per-commit diffs from the webhook commitShas', asyn
|
||||
defaultBranch: 'main', commitShas: [C1, HEAD]
|
||||
}
|
||||
});
|
||||
assert.equal(out, 'DIFF(c)\nDIFF(a)');
|
||||
assert.equal(out.diff, 'DIFF(c)\nDIFF(a)');
|
||||
assert.equal(out.partial, false);
|
||||
});
|
||||
|
||||
test('per-commit fallback stops fetching past maxBytes (review truncates after)', async () => {
|
||||
@@ -159,7 +167,9 @@ test('per-commit fallback stops fetching past maxBytes (review truncates after)'
|
||||
});
|
||||
// first two diffs exceed the cap (20 > 15) -> third commit never fetched
|
||||
assert.equal(fetched.length, 2);
|
||||
assert.equal(out, 'x'.repeat(10) + '\n' + 'x'.repeat(10));
|
||||
assert.equal(out.diff, 'x'.repeat(10) + '\n' + 'x'.repeat(10));
|
||||
// byte-capped reconstruction covered 2/3 commits -> partial, verdict capped
|
||||
assert.equal(out.partial, true);
|
||||
});
|
||||
|
||||
test('every source 404 -> throws listing every URL tried (then fail-open)', async () => {
|
||||
@@ -190,7 +200,9 @@ test('PR diff 404 falls back to the head commit diff', async () => {
|
||||
const g = giteaStub();
|
||||
g.getPrDiff = (o, r, i) => Promise.reject(nf(`/api/v1/repos/o/r/pulls/${i}.diff`));
|
||||
const out = await fetchDiff({ gitea: g, job: { owner: 'o', repo: 'r', prIndex: 7, sha: HEAD, before: null } });
|
||||
assert.equal(out, 'HEADDIFF');
|
||||
assert.equal(out.diff, 'HEADDIFF');
|
||||
// head-only PR fallback reviews a subset -> partial
|
||||
assert.equal(out.partial, true);
|
||||
assert.deepEqual(g.calls, [['commit', 'o', 'r', HEAD]]);
|
||||
});
|
||||
|
||||
@@ -201,5 +213,36 @@ test('empty compare body is treated as a miss, not a reviewable diff', async ()
|
||||
gitea: g,
|
||||
job: { owner: 'o', repo: 'r', prIndex: null, sha: HEAD, before: BEFORE, defaultBranch: 'main' }
|
||||
});
|
||||
assert.equal(out, 'RANGEDIFF');
|
||||
assert.equal(out.diff, 'RANGEDIFF');
|
||||
});
|
||||
|
||||
test('per-commit fallback dedupes, validates, and caps the sha list at 20', async () => {
|
||||
const g = giteaStub();
|
||||
// 25 unique shas + 1 dup + 1 garbage entry: only the first 20 valid uniques fetch
|
||||
const shas = Array.from({ length: 25 }, (_, i) => String(i).padStart(40, 'f'.charCodeAt ? '0' : '0'));
|
||||
const payload = [...shas, shas[0], 'not-a-sha'];
|
||||
g.getCompareDiff = () => Promise.reject(nf('/o/r/compare/x...y.diff'));
|
||||
g.compare = () => Promise.reject(nf('/api/v1/repos/o/r/compare/x...y'));
|
||||
const fetched = [];
|
||||
g.getCommitDiff = (o, r, sha) => { fetched.push(sha); return Promise.resolve('D'); };
|
||||
const out = await fetchDiff({
|
||||
gitea: g,
|
||||
job: { owner: 'o', repo: 'r', prIndex: null, sha: HEAD, before: BEFORE, defaultBranch: 'main', commitShas: payload }
|
||||
});
|
||||
assert.equal(fetched.length, 20);
|
||||
assert.equal(out.partial, true); // 20/25 covered
|
||||
});
|
||||
|
||||
test('getRepo auth failure propagates instead of being swallowed', async () => {
|
||||
const g = giteaStub();
|
||||
g.getCompareDiff = (o, r, before) => before === BEFORE
|
||||
? Promise.reject(nf('/o/r/compare/x...y.diff'))
|
||||
: Promise.resolve('RANGEDIFF');
|
||||
const authErr = new Error('gitea GET /api/v1/repos/o/r -> 401: unauthorized');
|
||||
authErr.status = 401;
|
||||
g.getRepo = () => Promise.reject(authErr);
|
||||
await assert.rejects(
|
||||
() => fetchDiff({ gitea: g, job: { owner: 'o', repo: 'r', prIndex: null, sha: HEAD, before: BEFORE } }),
|
||||
/401: unauthorized/
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user