This commit is contained in:
+66
-22
@@ -9,13 +9,18 @@ import { inheritedEnvironment, makeTempDir, runCommand } from "#testing";
|
||||
type Mapping = Map<string, unknown>;
|
||||
|
||||
const mapping = (value: unknown, at: string): Mapping => {
|
||||
assert(typeof value === "object" && value !== null && !Array.isArray(value), at);
|
||||
assert(
|
||||
typeof value === "object" && value !== null && !Array.isArray(value),
|
||||
at,
|
||||
);
|
||||
return new Map(Object.entries(value));
|
||||
};
|
||||
|
||||
const sequence = (value: unknown, at: string): Mapping[] => {
|
||||
assert(Array.isArray(value), at);
|
||||
return value.map((entry: unknown, index) => mapping(entry, `${at}[${index}]`));
|
||||
return value.map((entry: unknown, index) =>
|
||||
mapping(entry, `${at}[${index}]`),
|
||||
);
|
||||
};
|
||||
|
||||
async function yaml(path: string): Promise<Mapping> {
|
||||
@@ -49,8 +54,13 @@ test("all committed YAML parses", async () => {
|
||||
|
||||
test("the action exposes only the master key and scopes it to preparation", async () => {
|
||||
const action = await yaml("action.yml");
|
||||
expect([...mapping(action.get("inputs"), "inputs").keys()]).toStrictEqual(["master-key"]);
|
||||
const steps = sequence(mapping(action.get("runs"), "runs").get("steps"), "runs.steps");
|
||||
expect([...mapping(action.get("inputs"), "inputs").keys()]).toStrictEqual([
|
||||
"master-key",
|
||||
]);
|
||||
const steps = sequence(
|
||||
mapping(action.get("runs"), "runs").get("steps"),
|
||||
"runs.steps",
|
||||
);
|
||||
for (const step of steps) {
|
||||
const run = step.get("run");
|
||||
if (typeof run === "string") {
|
||||
@@ -62,13 +72,21 @@ test("the action exposes only the master key and scopes it to preparation", asyn
|
||||
const agent = findStep(steps, "id", "agent");
|
||||
const post = findStep(steps, "id", "post");
|
||||
findStep(steps, "id", "source");
|
||||
expect(mapping(checkout.get("with"), "checkout.with").get("token")).toBe("${{ gitea.token }}");
|
||||
expect(mapping(prepare.get("env"), "prepare.env").get("BOT_AGENTS_MASTER_KEY")).toBe(
|
||||
"${{ inputs.master-key }}",
|
||||
expect(mapping(checkout.get("with"), "checkout.with").get("token")).toBe(
|
||||
"${{ gitea.token }}",
|
||||
);
|
||||
expect(
|
||||
mapping(prepare.get("env"), "prepare.env").get("BOT_AGENTS_MASTER_KEY"),
|
||||
).toBe("${{ inputs.master-key }}");
|
||||
expect(
|
||||
JSON.stringify(Object.fromEntries(agent)).includes("BOT_AGENTS_MASTER_KEY"),
|
||||
).toBe(false);
|
||||
expect(
|
||||
JSON.stringify(Object.fromEntries(post)).includes("BOT_AGENTS_MASTER_KEY"),
|
||||
).toBe(false);
|
||||
expect(mapping(agent.get("env"), "agent.env").get("GITEA_TOKEN")).toBe(
|
||||
"${{ gitea.token }}",
|
||||
);
|
||||
expect(JSON.stringify(Object.fromEntries(agent)).includes("BOT_AGENTS_MASTER_KEY")).toBe(false);
|
||||
expect(JSON.stringify(Object.fromEntries(post)).includes("BOT_AGENTS_MASTER_KEY")).toBe(false);
|
||||
expect(mapping(agent.get("env"), "agent.env").get("GITEA_TOKEN")).toBe("${{ gitea.token }}");
|
||||
assert(String(post.get("if")).includes("always()"));
|
||||
for (const step of [prepare, agent, post]) {
|
||||
assert(String(step.get("run")).includes("steps.source.outputs.path"));
|
||||
@@ -83,7 +101,11 @@ test("the source protection script stages immutable action files", async () => {
|
||||
environment.set("ACTION_PATH", process.cwd());
|
||||
environment.set("GITHUB_OUTPUT", outputFile);
|
||||
environment.set("RUNNER_TEMP", temp);
|
||||
const result = await runCommand("/bin/bash", ["scripts/protect-source.sh"], environment);
|
||||
const result = await runCommand(
|
||||
"/bin/bash",
|
||||
["scripts/protect-source.sh"],
|
||||
environment,
|
||||
);
|
||||
assert(result.success, result.stderr);
|
||||
|
||||
const output = await readFile(outputFile, "utf8");
|
||||
@@ -116,9 +138,9 @@ test("the source protection script stages immutable action files", async () => {
|
||||
test("module-relative runtime assets exist", async () => {
|
||||
const module = new URL("../src/run/index.ts", import.meta.url);
|
||||
const source = await readFile(module, "utf8");
|
||||
const assets = [...source.matchAll(/new URL\("(?<asset>[^"]+)", import\.meta\.url\)/gu)].map(
|
||||
([, asset = ""]) => new URL(asset, module),
|
||||
);
|
||||
const assets = [
|
||||
...source.matchAll(/new URL\("(?<asset>[^"]+)", import\.meta\.url\)/gu),
|
||||
].map(([, asset = ""]) => new URL(asset, module));
|
||||
const stats = await Promise.all(
|
||||
assets.map(async (asset) => {
|
||||
const info = await stat(asset);
|
||||
@@ -132,7 +154,10 @@ test("module-relative runtime assets exist", async () => {
|
||||
|
||||
test("the repository uses its trusted main action", async () => {
|
||||
const workflow = await yaml(".gitea/workflows/agents.yml");
|
||||
const job = mapping(mapping(workflow.get("jobs"), "jobs").get("agent"), "jobs.agent");
|
||||
const job = mapping(
|
||||
mapping(workflow.get("jobs"), "jobs").get("agent"),
|
||||
"jobs.agent",
|
||||
);
|
||||
const permissions = mapping(job.get("permissions"), "jobs.agent.permissions");
|
||||
expect(Object.fromEntries(permissions)).toStrictEqual({
|
||||
code: "write",
|
||||
@@ -140,17 +165,30 @@ test("the repository uses its trusted main action", async () => {
|
||||
"pull-requests": "write",
|
||||
});
|
||||
const steps = sequence(job.get("steps"), "jobs.agent.steps");
|
||||
expect(steps.findIndex((step) => step.get("uses") === "./")).toStrictEqual(-1);
|
||||
const trusted = findStep(steps, "uses", "https://git.capsulizers.com/commons/bot-agents@main");
|
||||
expect(steps.findIndex((step) => step.get("uses") === "./")).toStrictEqual(
|
||||
-1,
|
||||
);
|
||||
const trusted = findStep(
|
||||
steps,
|
||||
"uses",
|
||||
"https://git.capsulizers.com/commons/bot-agents@main",
|
||||
);
|
||||
expect(mapping(trusted.get("with"), "trusted.with").get("master-key")).toBe(
|
||||
"${{ secrets.BOT_AGENTS_MASTER_KEY }}",
|
||||
);
|
||||
expect(JSON.stringify(Object.fromEntries(workflow)).includes("gitea.actor != 'bot'")).toBe(true);
|
||||
expect(
|
||||
JSON.stringify(Object.fromEntries(workflow)).includes(
|
||||
"gitea.actor != 'bot'",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("CI checks every phase and the root isolation suite", async () => {
|
||||
const workflow = await yaml(".gitea/workflows/check.yml");
|
||||
const job = mapping(mapping(workflow.get("jobs"), "jobs").get("check"), "jobs.check");
|
||||
const job = mapping(
|
||||
mapping(workflow.get("jobs"), "jobs").get("check"),
|
||||
"jobs.check",
|
||||
);
|
||||
const commands = sequence(job.get("steps"), "jobs.check.steps")
|
||||
.map((step) => {
|
||||
const run = step.get("run");
|
||||
@@ -169,7 +207,8 @@ test("uses the standard Skills installer without custom discovery", async () =>
|
||||
assert(runner.includes("skills add obra/superpowers"));
|
||||
const removed = await stat("superpowers.ts").then(
|
||||
() => false,
|
||||
(error: unknown) => error instanceof Error && "code" in error && error.code === "ENOENT",
|
||||
(error: unknown) =>
|
||||
error instanceof Error && "code" in error && error.code === "ENOENT",
|
||||
);
|
||||
assert(removed, "superpowers.ts should be removed");
|
||||
});
|
||||
@@ -191,8 +230,13 @@ test("profile preparation does not receive provider credentials", async () => {
|
||||
});
|
||||
|
||||
test("central profiles fall back from Codex to Claude to OpenCode", async () => {
|
||||
const credentials: unknown = JSON.parse(await readFile("profiles.json", "utf8"));
|
||||
const profiles = sequence(mapping(credentials, "profiles.json").get("profiles"), "profiles");
|
||||
const credentials: unknown = JSON.parse(
|
||||
await readFile("profiles.json", "utf8"),
|
||||
);
|
||||
const profiles = sequence(
|
||||
mapping(credentials, "profiles.json").get("profiles"),
|
||||
"profiles",
|
||||
);
|
||||
expect(profiles.map((profile) => profile.get("type"))).toStrictEqual([
|
||||
"CODEX",
|
||||
"CLAUDE",
|
||||
|
||||
Reference in New Issue
Block a user