Files
PaperClipAI/tests/runner-e2e/workflow-target.test.ts
T
DottaandPaperclip 4d554f0821 test(runner): set default inputs in workflow target fixture (#19602)
## Thinking Path

> - Paperclip manages work performed by AI agents.
> - Its Runner support tests verify workflow authorization and target
pinning.
> - The workflow now passes two Boolean inputs to its shell guard.
> - The target fixture runs that guard with `set -u` but omits those
inputs.
> - Five cases stop on an unset variable before they reach their
assertions.
> - This change supplies the workflow's normal false defaults in the
fixture.
> - The production guard and all target-pinning assertions remain
unchanged.

## Linked Issues or Issue Description

Refs #19537, #19576.

**What happened?**

The required Runner support suite fails five `workflow-target.test.ts`
cases with `IMAGE_ONLY: unbound variable`. The real workflow supplies
`IMAGE_ONLY` and `TESTOPS_REQUESTED`. The test fixture does not supply
either value.

**Expected behavior**

The fixture uses the normal workflow defaults and reaches the target
authorization assertions.

**Steps to reproduce**

Run `pnpm exec vitest run --config tests/runner-e2e/vitest.config.ts
workflow-target.test.ts` on master
`4938a930d66846094810cbed45d2fd33c0d14c2d`.

## What Changed

- Set `IMAGE_ONLY` and `TESTOPS_REQUESTED` to `false` in the target
fixture's child environment.

## Verification

- RED: the unchanged current-master fixture had five failures and four
passes. Bash stopped on `IMAGE_ONLY: unbound variable` before target
authorization ran (220ms).
- GREEN: all nine cases pass after the one-line fixture repair (168ms).
Existing checks still reject a custom bounded target and preserve the
queued commit for scheduled and bounded manual runs.
- Local evidence uses the actual test and workflow files with installed
Vitest 5.0.3 and a temporary focused config. It omits the unrelated
provider-bundle global setup. No provider, external service, or paid
workflow runs.
- The [original required CI
failure](https://github.com/paperclipai/paperclip/actions/runs/42288042001/job/128134318743)
has the same five Bash failures. Its evidence is retained; no unchanged
retry was used.
- Whitespace checks pass. Exact head
`f1d178ad483a49f8dd087a1c6d22bf43c06a39e4` has 56 successful checks,
three expected skips, one neutral advisory status and successful Snyk.
No check is pending or failed. The [full Runner support
suite](https://github.com/paperclipai/paperclip/actions/runs/42289536842/job/128139308510),
typecheck, build and [aggregate
verification](https://github.com/paperclipai/paperclip/actions/runs/42289536842/job/128145908574)
pass. Greptile and Paperclip Review both rate this exact head 5/5, with
no findings or unresolved threads.
- Environment: macOS arm64, Node 26.4.0, Vitest 5.0.3.

## Risks

Low risk. This changes only fixture inputs for normal target selection.
It does not change the production workflow guard, branch policy,
credentials, paid execution, timeouts or assertions. Product
documentation needs no update for test-only default inputs.

## Model Used

OpenAI Codex, GPT-6 family, with repository analysis, code editing and
tool execution. The exact runtime model ID and context window are not
exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub references)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes (not
needed: fixture-only default inputs)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-06 14:52:38 -06:00

83 lines
4.7 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { mkdtemp, readFile, rm, access } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
const queuedSha = "a".repeat(40);
const advancedSha = "b".repeat(40);
async function resolveTarget(event: string, cadence: string, branch = "master") {
const workflow = await readFile(new URL("../../.github/workflows/runner-full-stack-e2e.yml", import.meta.url), "utf8");
const step = workflow.split(" - name: Resolve requested repository branch to an immutable commit\n")[1]!.split("\n - name:")[0]!;
const script = step.split(" run: |\n")[1]!.replace(/^ {10}/gm, "");
const directory = await mkdtemp(path.join(os.tmpdir(), "eval-workflow-target-"));
try {
const output = path.join(directory, "output");
const result = spawnSync("bash", ["-c", `gh() { printf '%s\\n' "$MOCK_BRANCH_SHA"; }\n${script}`], {
encoding: "utf8", env: { ...process.env, EVENT_NAME: event, EVAL_CADENCE: cadence,
IMAGE_ONLY: "false", TESTOPS_REQUESTED: "false",
TARGET_BRANCH: branch, DEFAULT_BRANCH: "master", GITHUB_SHA: queuedSha,
MOCK_BRANCH_SHA: advancedSha, GITHUB_OUTPUT: output, REPOSITORY: "paperclipai/paperclip" },
});
return { status: result.status, stderr: result.stderr,
output: await readFile(output, "utf8").catch(() => "") };
} finally { await rm(directory, { recursive: true, force: true }); }
}
describe("paid workflow target authorization", () => {
it.each([["schedule", "manual"], ["workflow_dispatch", "daily"], ["workflow_dispatch", "weekly"]])(
"pins %s/%s to the queued revision when the branch advances", async (event, cadence) => {
const result = await resolveTarget(event, cadence);
expect(result.status, result.stderr).toBe(0);
expect(result.output).toContain(`sha=${queuedSha}\n`);
expect(result.output).not.toContain(advancedSha);
});
it("rejects custom bounded targets before emitting a commit", async () => {
const result = await resolveTarget("workflow_dispatch", "daily", "development");
expect(result.status).toBe(1);
expect(result.output).toBe("");
expect(result.stderr).toContain("default-branch commit");
});
it("still resolves explicit manual targets at authorization time", async () => {
const result = await resolveTarget("workflow_dispatch", "manual", "development");
expect(result.status, result.stderr).toBe(0);
expect(result.output).toContain(`sha=${advancedSha}\n`);
expect(result.output).toContain("ref=refs/heads/development\n");
});
});
describe("qualification image dependency correction", () => {
it.each([true, false])("applies the workflow correction only to its own source: %s", async sameSource => {
const workflow = await readFile(new URL("../../.github/workflows/docker-runner-check.yml", import.meta.url), "utf8");
const step = workflow.split(" - name: Apply reviewed qualification lock corrections\n")[1]!.split("\n - ")[0]!;
const script = step.split(" run: |\n")[1]!.replace(/^ {10}/gm, "");
const directory = await mkdtemp(path.join(os.tmpdir(), "eval-image-lock-target-"));
try {
const marker = path.join(directory, "called");
const result = spawnSync("bash", ["-c", `node() { touch "$CALLED"; }\n${script}`], {
cwd: directory, encoding: "utf8", env: { ...process.env, GITHUB_SHA: queuedSha,
TARGET_SHA: sameSource ? queuedSha : advancedSha, CALLED: marker },
});
expect(result.status, result.stderr).toBe(0);
expect(await access(marker).then(() => true, () => false)).toBe(sameSource);
} finally { await rm(directory, { recursive: true, force: true }); }
});
});
describe("release image capacity prerequisite", () => {
it.each([34, 35])("checks the unchanged disk threshold for every image route: %s GiB", async available => {
const workflow = await readFile(new URL("../../.github/workflows/docker-runner-check.yml", import.meta.url), "utf8");
const step = workflow.split(" - name: Prepare hosted image build capacity\n")[1]!.split("\n - ")[0]!;
expect(step).toContain("if: inputs.build_eval_image || inputs.publish_eval_image\n");
expect(step).not.toContain("release_run_id");
const script = step.split(" run: |\n")[1]!.replace(/^ {10}/gm, "");
const result = spawnSync("bash", ["-c", `sudo() { test "$*" = "rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc"; }; df() { printf 'Avail\\n%sG\\n' "$AVAILABLE"; }; ${script}`], {
encoding: "utf8", env: { PATH: process.env.PATH, AVAILABLE: String(available) },
});
expect(result.status, result.stderr).toBe(available >= 35 ? 0 : 1);
});
});