Files
bot-agents/workflow_test.ts
T
Danny Kim 7bae0d7237
Check / deno (pull_request) Successful in 36s
Centralize encrypted bot profiles
2026-09-24 05:00:02 +09:00

115 lines
4.3 KiB
TypeScript

import { assert, assertEquals } from "jsr:@std/assert@1";
import { parse } from "jsr:@std/yaml@1";
type Mapping = Record<string, unknown>;
const mapping = (value: unknown, at: string): Mapping => {
assert(
typeof value === "object" && value !== null && !Array.isArray(value),
at,
);
return value as Mapping;
};
const sequence = (value: unknown, at: string): Mapping[] => {
assert(Array.isArray(value), at);
return value.map((entry, index) => mapping(entry, `${at}[${index}]`));
};
async function yaml(path: string): Promise<Mapping> {
return mapping(parse(await Deno.readTextFile(path)), path);
}
async function yamlFiles(directory = "."): Promise<string[]> {
const result: string[] = [];
for await (const entry of Deno.readDir(directory)) {
if (entry.name === ".git" || entry.name === ".superpowers") continue;
const path = directory === "." ? entry.name : `${directory}/${entry.name}`;
if (entry.isDirectory) result.push(...await yamlFiles(path));
if (entry.isFile && /\.ya?ml$/.test(entry.name)) result.push(path);
}
return result;
}
Deno.test("all committed YAML parses", async () => {
const files = await yamlFiles();
assert(files.length > 0);
for (const file of files) await yaml(file);
});
Deno.test("the action exposes only the master key and scopes it to preparation", async () => {
const action = await yaml("action.yml");
assertEquals(Object.keys(mapping(action.inputs, "inputs")), ["master-key"]);
const steps = sequence(mapping(action.runs, "runs").steps, "runs.steps");
const checkout = steps.find((step) => step.uses === "actions/checkout@v4");
const prepare = steps.find((step) => step.id === "prepare");
const agent = steps.find((step) => step.id === "agent");
const post = steps.find((step) => step.id === "post");
const source = steps.find((step) => step.id === "source");
assert(checkout && source && prepare && agent && post);
assertEquals(
mapping(checkout.with, "checkout.with").token,
"${{ gitea.token }}",
);
assertEquals(
mapping(prepare.env, "prepare.env").BOT_AGENTS_MASTER_KEY,
"${{ inputs.master-key }}",
);
assertEquals(JSON.stringify(agent).includes("BOT_AGENTS_MASTER_KEY"), false);
assertEquals(JSON.stringify(post).includes("BOT_AGENTS_MASTER_KEY"), false);
assertEquals(
mapping(agent.env, "agent.env").GITEA_TOKEN,
"${{ gitea.token }}",
);
assert(String(post.if).includes("always()"));
for (const step of [prepare, agent, post]) {
assert(String(step.run).includes("steps.source.outputs.path"));
}
});
Deno.test("the repository uses its trusted main action", async () => {
const workflow = await yaml(".gitea/workflows/agents.yml");
const job = mapping(mapping(workflow.jobs, "jobs").agent, "jobs.agent");
const permissions = mapping(job.permissions, "jobs.agent.permissions");
assertEquals(permissions, {
code: "write",
issues: "write",
"pull-requests": "write",
});
const steps = sequence(job.steps, "jobs.agent.steps");
const local = steps.findIndex((step) => step.uses === "./");
assertEquals(local, -1);
const trusted = steps.findIndex((step) =>
step.uses === "https://git.capsulizers.com/commons/bot-agents@main"
);
assert(trusted >= 0);
assertEquals(
mapping(steps[trusted].with, "trusted.with")["master-key"],
"${{ secrets.BOT_AGENTS_MASTER_KEY }}",
);
assertEquals(JSON.stringify(workflow).includes("gitea.actor != 'bot'"), true);
});
Deno.test("CI checks every phase and the root isolation suite", async () => {
const workflow = await yaml(".gitea/workflows/check.yml");
const job = mapping(mapping(workflow.jobs, "jobs").deno, "jobs.deno");
const commands = sequence(job.steps, "jobs.deno.steps")
.map((step) => typeof step.run === "string" ? step.run : "")
.join("\n");
for (const entrypoint of ["prepare.ts", "run.ts", "post.ts"]) {
assert(commands.includes(entrypoint), entrypoint);
}
assert(commands.includes("deno test --allow-all"));
});
Deno.test("uses the standard Skills installer without custom discovery", async () => {
const runner = await Deno.readTextFile("run.ts");
assert(runner.includes("skills add obra/superpowers"));
try {
await Deno.stat("superpowers.ts");
assert(false, "superpowers.ts should be removed");
} catch (error) {
assert(error instanceof Deno.errors.NotFound);
}
});