Files
2026-10-07 08:59:16 +00:00

129 lines
4.1 KiB
TypeScript

import { afterEach, describe, expect, spyOn, test } from "bun:test";
import { rm } from "node:fs/promises";
import { runCommand } from "citty";
import { KuberApiError, type ApiRequestInit } from "../../lib/api";
import { getTrustPath, readTrust, updateTrust } from "../../lib/trust";
import {
grantTrust,
revokeTrust,
statusTrust,
trust,
} from "../../command/trust";
const originalConfig = process.env.XDG_CONFIG_HOME;
const identity = { project: "demo", fingerprint: "a".repeat(64) };
afterEach(async () => {
const path = getTrustPath();
if (originalConfig === undefined) delete process.env.XDG_CONFIG_HOME;
else process.env.XDG_CONFIG_HOME = originalConfig;
await rm(path.slice(0, path.lastIndexOf("/")), {
recursive: true,
force: true,
});
});
function requester(
calls: Array<{ path: string; init?: ApiRequestInit }>,
response: unknown = undefined,
) {
return async <T>(path: string, init?: ApiRequestInit): Promise<T> => {
calls.push({ path, init });
return response as T;
};
}
describe("trust command", () => {
test("citty dispatch of bare trust runs the parent grant handler", async () => {
await expect(runCommand(trust, { rawArgs: [] })).rejects.toThrow();
});
test.each(["status", "revoke"])(
"citty dispatch of %s does not run the parent grant handler",
async (subcommand) => {
const command = {
...trust,
subCommands: Object.fromEntries(
Object.entries(trust.subCommands!).map(([name, child]) => [
name,
{ ...child, run: async () => {} },
]),
),
};
// Without a provided app context, a parent grant would reject in
// current(). Successful dispatch proves citty did not grant afterward.
await expect(
runCommand(command, { rawArgs: [subcommand] }),
).resolves.toBeDefined();
},
);
test("grants, reports, and revokes the current namespace fingerprint", async () => {
process.env.XDG_CONFIG_HOME = `/tmp/kuber-trust-command-${crypto.randomUUID()}`;
const calls: Array<{ path: string; init?: ApiRequestInit }> = [];
const output = spyOn(console, "log").mockImplementation(() => {});
await grantTrust(identity, requester(calls));
expect(calls).toEqual([
{
path: "/workspaces/demo/trust",
init: { method: "POST", json: { fingerprint: identity.fingerprint } },
},
]);
expect(await readTrust()).toEqual([identity]);
calls.length = 0;
await statusTrust(
identity,
requester(calls, { fingerprints: [identity.fingerprint] }),
);
expect(calls).toEqual([
{ path: "/workspaces/demo/trust", init: undefined },
]);
expect(output.mock.calls.map(([line]) => line)).toEqual([
"Trusted this directory for namespace demo",
"Namespace: demo",
"Local: trusted",
"Server: registered",
]);
calls.length = 0;
await revokeTrust(identity, requester(calls));
expect(calls).toEqual([
{
path: `/workspaces/demo/trust?fingerprint=${identity.fingerprint}`,
init: { method: "DELETE" },
},
]);
expect(await readTrust()).toEqual([]);
output.mockRestore();
});
test("removes local trust when the server registration is already absent", async () => {
process.env.XDG_CONFIG_HOME = `/tmp/kuber-trust-command-${crypto.randomUUID()}`;
await updateTrust(() => [identity]);
const output = spyOn(console, "log").mockImplementation(() => {});
await revokeTrust(identity, async () => {
throw new KuberApiError("not found", 404);
});
expect(await readTrust()).toEqual([]);
expect(output).toHaveBeenCalledWith("Revoked trust for namespace demo");
output.mockRestore();
});
test("removes local trust before reporting a remote revoke failure", async () => {
process.env.XDG_CONFIG_HOME = `/tmp/kuber-trust-command-${crypto.randomUUID()}`;
await updateTrust(() => [identity]);
await expect(
revokeTrust(identity, async () => {
throw new KuberApiError("unavailable", 503);
}),
).rejects.toThrow("Removed local trust for namespace demo");
expect(await readTrust()).toEqual([]);
});
});