feat: harden self-managed reconciliation
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { afterEach, describe, expect, spyOn, test } from "bun:test";
|
||||
import { rm } from "node:fs/promises";
|
||||
import { KuberApiError, type ApiRequestInit } from "../../lib/api";
|
||||
import { getTrustPath, readTrust, updateTrust } from "../../lib/trust";
|
||||
import { grantTrust, revokeTrust, statusTrust } 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("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([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user