Skip to content

Commit fdaa672

Browse files
committed
test: verify Planner auto-merge guards against stale and unrelated changes
1 parent c57f91b commit fdaa672

4 files changed

Lines changed: 52 additions & 3 deletions

File tree

‎.github/workflows/mvp-ci.yml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ jobs:
5151

5252
- name: Test Planner dependency updater
5353
working-directory: ASAPQuery-backend
54-
run: python3 -m unittest discover -s scripts/ci -p 'test_*.py'
54+
run: |
55+
python3 -m unittest discover -s scripts/ci -p 'test_*.py'
56+
node scripts/ci/test_planner_merge.js
5557
5658
- name: Check workspace formatting
5759
working-directory: ASAPQuery-backend

‎.github/workflows/planner-main-merge.yml‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ jobs:
2828
if (prs.data.length !== 1) return;
2929
const {data: pr} = await github.rest.pulls.get({owner, repo, pull_number: prs.data[0].number});
3030
if (pr.head.repo.full_name !== `${owner}/${repo}` || pr.head.sha !== run.head_sha || pr.draft) return;
31-
const testedPr = run.pull_requests.find(p => p.number === pr.number);
32-
if (!testedPr || testedPr.base.sha !== pr.base.sha) {
31+
const {data: comparison} = await github.rest.repos.compareCommitsWithBasehead({
32+
owner, repo, basehead: `main...${run.head_sha}`
33+
});
34+
if (comparison.merge_base_commit.sha !== comparison.base_commit.sha) {
3335
core.info('Main changed since this CI run; wait for the next updater refresh.');
3436
return;
3537
}

‎scripts/ci/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ then run the same checks as MVP CI. Updater unit tests run with:
2424

2525
```sh
2626
python3 -m unittest discover -s scripts/ci -p 'test_*.py'
27+
node scripts/ci/test_planner_merge.js
2728
```
2829

2930
The sync script and scheduled workflow are shared with PR #765, at the same paths

‎scripts/ci/test_planner_merge.js‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Execute the privileged workflow handler with mocked GitHub responses.
2+
const fs = require('node:fs');
3+
const path = require('node:path');
4+
const workflow = fs.readFileSync(path.join(__dirname, '../../.github/workflows/planner-main-merge.yml'), 'utf8');
5+
const script = workflow.split(' script: |\n')[1].split('\n').map(line => line.slice(12)).join('\n');
6+
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
7+
const handler = new AsyncFunction('github', 'context', 'core', script);
8+
9+
async function verifyMergeGuards() {
10+
for (const scenario of ['valid', 'stale-head', 'foreign-repo', 'draft', 'stale-main', 'extra-file', 'empty-diff', 'no-pr']) {
11+
let merged = false;
12+
let failed = false;
13+
const pr = {
14+
number: 12,
15+
head: {repo: {full_name: scenario === 'foreign-repo' ? 'else/repo' : 'o/r'}, sha: scenario === 'stale-head' ? 'old' : 'new'},
16+
draft: scenario === 'draft',
17+
};
18+
const github = {
19+
rest: {
20+
pulls: {
21+
list: async () => ({data: scenario === 'no-pr' ? [] : [pr]}),
22+
get: async () => ({data: pr}),
23+
listFiles: () => {},
24+
merge: async args => {
25+
if (args.sha !== 'new' || args.pull_number !== 12) throw Error('Wrong merge target');
26+
merged = true;
27+
},
28+
},
29+
repos: {compareCommitsWithBasehead: async () => ({data: {
30+
merge_base_commit: {sha: scenario === 'stale-main' ? 'old' : 'base'},
31+
base_commit: {sha: 'base'},
32+
}})},
33+
},
34+
paginate: async () => scenario === 'empty-diff' ? [] : [{filename: scenario === 'extra-file' ? 'other.rs' : 'Cargo.toml'}],
35+
};
36+
await handler(github, {repo: {owner: 'o', repo: 'r'}, payload: {workflow_run: {head_sha: 'new'}}}, {
37+
info: () => {}, setFailed: () => { failed = true; },
38+
});
39+
if (merged !== (scenario === 'valid')) throw Error(`Unexpected merge: ${scenario}`);
40+
if (failed !== ['extra-file', 'empty-diff'].includes(scenario)) throw Error(`Unexpected failure: ${scenario}`);
41+
}
42+
}
43+
44+
verifyMergeGuards().catch(error => { console.error(error); process.exitCode = 1; });

0 commit comments

Comments
 (0)