mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* ci: gate fork PR workflow auto-approval * ci: rename fork PR approval workflow * ci: normalize fork workflow paths Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): match action_required workflow runs Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): denylist tool config paths Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): retry action_required workflow lookup Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): restrict fork workflow approvals to target PR Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): keep polling fork workflow approvals Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): revalidate fork workflow approvals before approving Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): poll longer for first fork approval run Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): make fork approval poll budget configurable Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): drop stale fork approval runs Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): deny dotted tsconfig variants in fork approvals Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): run fork approval regression in guard Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): refresh Nix pnpm deps hash Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * test(web): mock useI18n in reattach restore test Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): accept status-only fork approvals Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): rerun fork approval on retarget Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): ignore base tip churn in PR association Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): broaden pending approval run fetch Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): skip non-retarget fork approval edits Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): checkout visual comment workflow head Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): paginate workflow approval run lookup Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): harden fork workflow follow-ups Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): honor full post-appearance settling window Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): validate manual visual comment checkout Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode)
725 lines
20 KiB
TypeScript
725 lines
20 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
hasPullApprovalStateDrift,
|
|
isDeniedChangedPath,
|
|
isPendingApprovalRun,
|
|
listPendingApprovalRuns,
|
|
runTargetsPullRequest,
|
|
waitForPendingApprovalRuns,
|
|
} from "./approve-fork-pr-workflows.ts";
|
|
|
|
test("isPendingApprovalRun matches approval-gated fork PR runs from GitHub's captured payload shape", () => {
|
|
const pull = {
|
|
number: 2683,
|
|
state: "open",
|
|
changed_files: 1,
|
|
head: {
|
|
sha: "734076155c44e569304856590019cea54506fdab",
|
|
repo: { full_name: "someone/open-design" },
|
|
},
|
|
base: {
|
|
ref: "main",
|
|
sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
const run = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
|
|
assert.equal(isPendingApprovalRun(run, pull), true);
|
|
});
|
|
|
|
test("isPendingApprovalRun also accepts action_required runs reported only in status", () => {
|
|
const pull = {
|
|
number: 2683,
|
|
state: "open",
|
|
changed_files: 1,
|
|
head: {
|
|
sha: "734076155c44e569304856590019cea54506fdab",
|
|
repo: { full_name: "someone/open-design" },
|
|
},
|
|
base: {
|
|
ref: "main",
|
|
sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
const run = {
|
|
id: 26273463770,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "action_required",
|
|
conclusion: null,
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
|
|
assert.equal(isPendingApprovalRun(run, pull), true);
|
|
});
|
|
|
|
test("isPendingApprovalRun rejects runs outside the allowlist or without action_required state", () => {
|
|
const pull = {
|
|
number: 2683,
|
|
state: "open",
|
|
changed_files: 1,
|
|
head: {
|
|
sha: "734076155c44e569304856590019cea54506fdab",
|
|
repo: { full_name: "someone/open-design" },
|
|
},
|
|
base: {
|
|
ref: "main",
|
|
sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
assert.equal(
|
|
isPendingApprovalRun(
|
|
{
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "success",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
},
|
|
pull,
|
|
),
|
|
false,
|
|
);
|
|
|
|
assert.equal(
|
|
isPendingApprovalRun(
|
|
{
|
|
id: 26273463770,
|
|
name: "Visual PR Comment",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/visual-pr-comment.yml@main",
|
|
pull_requests: [],
|
|
},
|
|
pull,
|
|
),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test("runTargetsPullRequest accepts empty run.pull_requests only when the head SHA maps to this one open PR", () => {
|
|
const pull = {
|
|
number: 2683,
|
|
state: "open",
|
|
changed_files: 1,
|
|
head: {
|
|
sha: "734076155c44e569304856590019cea54506fdab",
|
|
repo: { full_name: "someone/open-design" },
|
|
},
|
|
base: {
|
|
ref: "main",
|
|
sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
const run = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
|
|
assert.equal(runTargetsPullRequest(run, pull, [pull]), true);
|
|
});
|
|
|
|
test("runTargetsPullRequest rejects ambiguous empty run.pull_requests associations", () => {
|
|
const pull = {
|
|
number: 2683,
|
|
state: "open",
|
|
changed_files: 1,
|
|
head: {
|
|
sha: "734076155c44e569304856590019cea54506fdab",
|
|
repo: { full_name: "someone/open-design" },
|
|
},
|
|
base: {
|
|
ref: "main",
|
|
sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
const otherPull = {
|
|
...pull,
|
|
number: 3001,
|
|
base: {
|
|
ref: "release",
|
|
sha: "8db117d728f967d108f6fdd64cb8d921d057f7f6",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
const run = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
|
|
assert.equal(runTargetsPullRequest(run, pull, [pull, otherPull]), false);
|
|
});
|
|
|
|
test("runTargetsPullRequest rejects runs that GitHub already associates to a different PR", () => {
|
|
const pull = {
|
|
number: 2683,
|
|
state: "open",
|
|
changed_files: 1,
|
|
head: {
|
|
sha: "734076155c44e569304856590019cea54506fdab",
|
|
repo: { full_name: "someone/open-design" },
|
|
},
|
|
base: {
|
|
ref: "main",
|
|
sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
const run = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [
|
|
{
|
|
number: 3001,
|
|
head: pull.head,
|
|
base: {
|
|
ref: "release",
|
|
sha: "8db117d728f967d108f6fdd64cb8d921d057f7f6",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
assert.equal(runTargetsPullRequest(run, pull, [pull]), false);
|
|
});
|
|
|
|
test("runTargetsPullRequest approves only the run that GitHub associates to the current PR when two PRs share a head SHA", () => {
|
|
const pull = {
|
|
number: 2683,
|
|
state: "open",
|
|
changed_files: 1,
|
|
head: {
|
|
sha: "734076155c44e569304856590019cea54506fdab",
|
|
repo: { full_name: "someone/open-design" },
|
|
},
|
|
base: {
|
|
ref: "main",
|
|
sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
const otherPull = {
|
|
...pull,
|
|
number: 3001,
|
|
base: {
|
|
ref: "release",
|
|
sha: "8db117d728f967d108f6fdd64cb8d921d057f7f6",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
const currentPrRun = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: pull.head.sha,
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [pull],
|
|
};
|
|
|
|
const otherPrRun = {
|
|
...currentPrRun,
|
|
id: 26273463770,
|
|
pull_requests: [otherPull],
|
|
};
|
|
|
|
assert.equal(runTargetsPullRequest(currentPrRun, pull, [pull, otherPull]), true);
|
|
assert.equal(runTargetsPullRequest(otherPrRun, pull, [pull, otherPull]), false);
|
|
});
|
|
|
|
test("runTargetsPullRequest ignores base tip churn for the same PR association", () => {
|
|
const pull = {
|
|
number: 2683,
|
|
state: "open",
|
|
changed_files: 1,
|
|
head: {
|
|
sha: "734076155c44e569304856590019cea54506fdab",
|
|
repo: { full_name: "someone/open-design" },
|
|
},
|
|
base: {
|
|
ref: "main",
|
|
sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
const run = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: pull.head.sha,
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [
|
|
{
|
|
number: pull.number,
|
|
head: pull.head,
|
|
base: {
|
|
...pull.base,
|
|
sha: "08a88a65482123629ebda5a090c71533bd6b8a88",
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
assert.equal(runTargetsPullRequest(run, pull, [pull]), true);
|
|
});
|
|
|
|
test("listPendingApprovalRuns paginates all pull_request runs for the head SHA and filters action_required client-side", async () => {
|
|
const pull = {
|
|
number: 2683,
|
|
state: "open",
|
|
changed_files: 1,
|
|
head: {
|
|
sha: "734076155c44e569304856590019cea54506fdab",
|
|
repo: { full_name: "someone/open-design" },
|
|
},
|
|
base: {
|
|
ref: "main",
|
|
sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
const requestedPaths: string[] = [];
|
|
const pendingRuns = await listPendingApprovalRuns("nexu-io/open-design", pull, {
|
|
loadWorkflowRunsResponsePage: async (path) => {
|
|
requestedPaths.push(path);
|
|
if (path.endsWith("page=1")) {
|
|
return {
|
|
workflow_runs: Array.from({ length: 100 }, (_, index) => ({
|
|
id: 26273463600 + index,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "success",
|
|
head_sha: pull.head.sha,
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
})),
|
|
};
|
|
}
|
|
|
|
return {
|
|
workflow_runs: [
|
|
{
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: pull.head.sha,
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
},
|
|
{
|
|
id: 26273463770,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "success",
|
|
head_sha: pull.head.sha,
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
},
|
|
],
|
|
};
|
|
},
|
|
loadPullRequestsForHeadSha: async () => [pull],
|
|
});
|
|
|
|
assert.deepEqual(requestedPaths, [
|
|
"/repos/nexu-io/open-design/actions/runs?event=pull_request&head_sha=734076155c44e569304856590019cea54506fdab&per_page=100&page=1",
|
|
"/repos/nexu-io/open-design/actions/runs?event=pull_request&head_sha=734076155c44e569304856590019cea54506fdab&per_page=100&page=2",
|
|
]);
|
|
assert.equal(requestedPaths.some((path) => path.includes("status=action_required")), false);
|
|
assert.deepEqual(
|
|
pendingRuns.map((run) => run.id),
|
|
[26273463769],
|
|
);
|
|
});
|
|
|
|
test("hasPullApprovalStateDrift ignores base tip churn but still rejects base retargeting and head drift", () => {
|
|
const pull = {
|
|
number: 2683,
|
|
state: "open",
|
|
changed_files: 1,
|
|
head: {
|
|
sha: "734076155c44e569304856590019cea54506fdab",
|
|
repo: { full_name: "someone/open-design" },
|
|
},
|
|
base: {
|
|
ref: "main",
|
|
sha: "4cd93a5c7a7b0db1961c854e55f8e0e6b1b45542",
|
|
repo: { full_name: "nexu-io/open-design" },
|
|
},
|
|
};
|
|
|
|
assert.equal(hasPullApprovalStateDrift(pull, pull), false);
|
|
assert.equal(
|
|
hasPullApprovalStateDrift(pull, {
|
|
...pull,
|
|
base: { ...pull.base, sha: "08a88a65482123629ebda5a090c71533bd6b8a88" },
|
|
}),
|
|
false,
|
|
);
|
|
assert.equal(hasPullApprovalStateDrift(pull, { ...pull, draft: true }), true);
|
|
assert.equal(hasPullApprovalStateDrift(pull, { ...pull, state: "closed" }), true);
|
|
assert.equal(
|
|
hasPullApprovalStateDrift(pull, {
|
|
...pull,
|
|
head: { ...pull.head, sha: "08a88a65482123629ebda5a090c71533bd6b8a88" },
|
|
}),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
hasPullApprovalStateDrift(pull, {
|
|
...pull,
|
|
base: { ...pull.base, ref: "release" },
|
|
}),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test("isDeniedChangedPath blocks common tool config files under allowlisted source trees", () => {
|
|
assert.equal(isDeniedChangedPath("apps/web/vitest.config.ts"), true);
|
|
assert.equal(isDeniedChangedPath("apps/web/vite.config.ts"), true);
|
|
assert.equal(isDeniedChangedPath("apps/web/playwright.config.ts"), true);
|
|
assert.equal(isDeniedChangedPath("apps/web/tsconfig.sidecar.json"), true);
|
|
assert.equal(isDeniedChangedPath("apps/daemon/tsconfig.tests.json"), true);
|
|
assert.equal(isDeniedChangedPath("packages/contracts/tsconfig.tests.json"), true);
|
|
assert.equal(isDeniedChangedPath("apps/web/src/app/page.tsx"), false);
|
|
});
|
|
|
|
test("waitForPendingApprovalRuns retries until action_required runs appear and keeps polling through the retry window", async () => {
|
|
const run = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
|
|
const batches = [[], [], [run]];
|
|
const sleeps: number[] = [];
|
|
let now = 0;
|
|
|
|
const pendingRuns = await waitForPendingApprovalRuns(
|
|
async () => batches.shift() ?? [run],
|
|
async (ms) => {
|
|
sleeps.push(ms);
|
|
now += ms;
|
|
},
|
|
() => now,
|
|
{ settlingWindowMs: 9_000 },
|
|
);
|
|
|
|
assert.deepEqual(pendingRuns, [run]);
|
|
assert.deepEqual(sleeps, [3000, 3000, 3000, 3000, 3000]);
|
|
});
|
|
|
|
test("waitForPendingApprovalRuns keeps polling and returns the latest eligible run snapshot across the retry window", async () => {
|
|
const ciRun = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
const visualRun = {
|
|
id: 26273463770,
|
|
name: "Visual PR Verify",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/visual-pr-verify.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
|
|
const batches = [[ciRun], [ciRun], [ciRun, visualRun], [ciRun, visualRun]];
|
|
const sleeps: number[] = [];
|
|
let now = 0;
|
|
|
|
const pendingRuns = await waitForPendingApprovalRuns(
|
|
async () => batches.shift() ?? [ciRun, visualRun],
|
|
async (ms) => {
|
|
sleeps.push(ms);
|
|
now += ms;
|
|
},
|
|
() => now,
|
|
{ settlingWindowMs: 9_000 },
|
|
);
|
|
|
|
assert.deepEqual(pendingRuns, [ciRun, visualRun]);
|
|
assert.deepEqual(sleeps, [3000, 3000, 3000, 3000, 3000]);
|
|
});
|
|
|
|
test("waitForPendingApprovalRuns drops runs that disappear in later polls", async () => {
|
|
const staleRun = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
const survivingRun = {
|
|
...staleRun,
|
|
id: 26273463770,
|
|
name: "Visual PR Verify",
|
|
path: ".github/workflows/visual-pr-verify.yml@main",
|
|
};
|
|
|
|
const batches = [[staleRun], [staleRun, survivingRun], [survivingRun], [survivingRun]];
|
|
let now = 0;
|
|
|
|
const pendingRuns = await waitForPendingApprovalRuns(
|
|
async () => batches.shift() ?? [survivingRun],
|
|
async (ms) => {
|
|
now += ms;
|
|
},
|
|
() => now,
|
|
{ settlingWindowMs: 6_000 },
|
|
);
|
|
|
|
assert.deepEqual(pendingRuns, [survivingRun]);
|
|
});
|
|
|
|
test("waitForPendingApprovalRuns keeps polling until the run set is stable, even when another eligible run appears after the old three-poll budget", async () => {
|
|
const ciRun = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
const visualRun = {
|
|
id: 26273463770,
|
|
name: "Visual PR Verify",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/visual-pr-verify.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
|
|
const batches = [[ciRun], [ciRun], [ciRun], [ciRun], [ciRun], [ciRun, visualRun], [ciRun, visualRun]];
|
|
const sleeps: number[] = [];
|
|
let now = 0;
|
|
|
|
const pendingRuns = await waitForPendingApprovalRuns(
|
|
async () => batches.shift() ?? [ciRun, visualRun],
|
|
async (ms) => {
|
|
sleeps.push(ms);
|
|
now += ms;
|
|
},
|
|
() => now,
|
|
{
|
|
firstAppearanceTimeoutMs: 30_000,
|
|
settlingWindowMs: 15_000,
|
|
},
|
|
);
|
|
|
|
assert.deepEqual(pendingRuns, [ciRun, visualRun]);
|
|
assert.equal(sleeps.length, 10);
|
|
});
|
|
|
|
test("waitForPendingApprovalRuns gives late first appearances their own full settling window", async () => {
|
|
const ciRun = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
const visualRun = {
|
|
id: 26273463770,
|
|
name: "Visual PR Verify",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/visual-pr-verify.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
|
|
const batches = [[], [], [], [ciRun], [ciRun], [ciRun, visualRun], [ciRun, visualRun], [ciRun, visualRun]];
|
|
const sleeps: number[] = [];
|
|
let now = 0;
|
|
|
|
const pendingRuns = await waitForPendingApprovalRuns(
|
|
async () => batches.shift() ?? [ciRun, visualRun],
|
|
async (ms) => {
|
|
sleeps.push(ms);
|
|
now += ms;
|
|
},
|
|
() => now,
|
|
{
|
|
firstAppearanceTimeoutMs: 12_000,
|
|
settlingWindowMs: 9_000,
|
|
},
|
|
);
|
|
|
|
assert.deepEqual(pendingRuns, [ciRun, visualRun]);
|
|
assert.deepEqual(sleeps, [3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000]);
|
|
});
|
|
|
|
test("waitForPendingApprovalRuns keeps polling until the first run appears, even after the old short retry budget", async () => {
|
|
const run = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
|
|
const batches = [[], [], [], [], [run]];
|
|
const sleeps: number[] = [];
|
|
let now = 0;
|
|
|
|
const pendingRuns = await waitForPendingApprovalRuns(
|
|
async () => batches.shift() ?? [run],
|
|
async (ms) => {
|
|
sleeps.push(ms);
|
|
now += ms;
|
|
},
|
|
() => now,
|
|
{
|
|
firstAppearanceTimeoutMs: 15_000,
|
|
settlingWindowMs: 6_000,
|
|
},
|
|
);
|
|
|
|
assert.deepEqual(pendingRuns, [run]);
|
|
assert.deepEqual(sleeps, [3000, 3000, 3000, 3000, 3000, 3000]);
|
|
});
|
|
|
|
test("waitForPendingApprovalRuns accepts a configurable longer polling budget before the first run appears", async () => {
|
|
const run = {
|
|
id: 26273463769,
|
|
name: "CI",
|
|
event: "pull_request",
|
|
status: "completed",
|
|
conclusion: "action_required",
|
|
head_sha: "734076155c44e569304856590019cea54506fdab",
|
|
path: ".github/workflows/ci.yml@main",
|
|
pull_requests: [],
|
|
};
|
|
|
|
const batches = [[], [], [], [], [], [run]];
|
|
const sleeps: number[] = [];
|
|
let now = 0;
|
|
|
|
const pendingRuns = await waitForPendingApprovalRuns(
|
|
async () => batches.shift() ?? [run],
|
|
async (ms) => {
|
|
sleeps.push(ms);
|
|
now += ms;
|
|
},
|
|
() => now,
|
|
{
|
|
firstAppearanceTimeoutMs: 18_000,
|
|
settlingWindowMs: 3_000,
|
|
},
|
|
);
|
|
|
|
assert.deepEqual(pendingRuns, [run]);
|
|
assert.deepEqual(sleeps, [3000, 3000, 3000, 3000, 3000, 3000]);
|
|
});
|
|
|
|
test("waitForPendingApprovalRuns stops after the first-appearance timeout when no runs arrive", async () => {
|
|
let calls = 0;
|
|
const sleeps: number[] = [];
|
|
let now = 0;
|
|
|
|
const pendingRuns = await waitForPendingApprovalRuns(
|
|
async () => {
|
|
calls += 1;
|
|
return [];
|
|
},
|
|
async (ms) => {
|
|
sleeps.push(ms);
|
|
now += ms;
|
|
},
|
|
() => now,
|
|
{
|
|
firstAppearanceTimeoutMs: 9_000,
|
|
},
|
|
);
|
|
|
|
assert.deepEqual(pendingRuns, []);
|
|
assert.equal(calls, 4);
|
|
assert.equal(sleeps.length, 3);
|
|
assert.equal(now, 9_000);
|
|
});
|