File size: 3,857 Bytes
f778c12 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | // Cron scratch register tests cover cron scratch command option validation.
import { Command } from "commander";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { defaultRuntime } from "../../runtime.js";
const callGatewayFromCli = vi.fn();
vi.mock("../gateway-rpc.js", async () => {
const actual = await vi.importActual<typeof import("../gateway-rpc.js")>("../gateway-rpc.js");
return {
...actual,
callGatewayFromCli: (...args: Parameters<typeof actual.callGatewayFromCli>) =>
callGatewayFromCli(...args),
};
});
const { registerCronScratchCommand } = await import("./register.cron-scratch.js");
function createCronProgram(): Command {
const program = new Command();
program.exitOverride();
registerCronScratchCommand(program);
return program;
}
describe("cron scratch command", () => {
beforeEach(() => {
callGatewayFromCli.mockReset();
callGatewayFromCli.mockImplementation(async (method: string) => {
if (method === "cron.scratch.get") {
return {
scratch: { content: "note", revision: 2, updatedAtMs: 1 },
currentRevision: 2,
maxBytes: 1024,
};
}
return { ok: true, scratch: null, currentRevision: 3, maxBytes: 1024 };
});
});
afterEach(() => {
vi.restoreAllMocks();
});
it.each([
["without --json", ["--set", "new note"]],
["with --json", ["--unset", "--json"]],
])("prints the write result as JSON %s", async (_label, args) => {
const stdoutWrite = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
const writeJson = vi.spyOn(defaultRuntime, "writeJson").mockImplementation(() => {});
await createCronProgram().parseAsync(["scratch", "job-1", ...args], { from: "user" });
expect(writeJson).toHaveBeenCalledWith({
ok: true,
scratch: null,
currentRevision: 3,
maxBytes: 1024,
});
expect(stdoutWrite).not.toHaveBeenCalled();
});
it("documents the read/write JSON split", () => {
const scratch = createCronProgram().commands.find((command) => command.name() === "scratch");
const jsonOption = scratch?.options.find((option) => option.long === "--json");
expect(jsonOption?.description).toBe(
"Output scratch plus revision metadata as JSON; writes return JSON by default",
);
expect(jsonOption?.defaultValue).toBeUndefined();
});
it.each(["0x2", "1e2", "2.5", "-1", "2a"])(
"rejects non-decimal --expected-revision %j",
async (revision) => {
const errorSpy = vi.spyOn(defaultRuntime, "error").mockImplementation(() => {});
try {
await expect(
createCronProgram().parseAsync(
["scratch", "job-1", "--set", "x", "--expected-revision", revision],
{ from: "user" },
),
).rejects.toMatchObject({ name: "ExitError", code: 1 });
expect(errorSpy).toHaveBeenCalledWith(
expect.stringContaining("--expected-revision must be a non-negative integer"),
);
const setCalls = callGatewayFromCli.mock.calls.filter(
([method]) => method === "cron.scratch.set",
);
expect(setCalls).toHaveLength(0);
} finally {
errorSpy.mockRestore();
}
},
);
it.each([
["0", 0],
["42", 42],
])(
"passes decimal --expected-revision %j through to the CAS write",
async (revision, expectedRevision) => {
vi.spyOn(process.stdout, "write").mockImplementation(() => true);
await createCronProgram().parseAsync(
["scratch", "job-1", "--set", "x", "--expected-revision", revision],
{ from: "user" },
);
const setCall = callGatewayFromCli.mock.calls.find(
([method]) => method === "cron.scratch.set",
);
expect(setCall?.[2]).toMatchObject({ expectedRevision });
},
);
});
|