refactor(cli): dedupe device role validation for token ops

This commit is contained in:
Peter Steinberger
2026-02-19 00:28:51 +00:00
parent c8bdefd8b4
commit ac44190952
2 changed files with 82 additions and 14 deletions

View File

@@ -192,3 +192,64 @@ describe("devices cli clear", () => {
);
});
});
describe("devices cli tokens", () => {
afterEach(() => {
callGateway.mockReset();
withProgress.mockClear();
runtime.log.mockReset();
runtime.error.mockReset();
runtime.exit.mockReset();
});
it("rotates a token for a device role", async () => {
callGateway.mockResolvedValueOnce({ ok: true });
await runDevicesCommand([
"rotate",
"--device",
"device-1",
"--role",
"main",
"--scope",
"messages:send",
"--scope",
"messages:read",
]);
expect(callGateway).toHaveBeenCalledWith(
expect.objectContaining({
method: "device.token.rotate",
params: {
deviceId: "device-1",
role: "main",
scopes: ["messages:send", "messages:read"],
},
}),
);
});
it("revokes a token for a device role", async () => {
callGateway.mockResolvedValueOnce({ ok: true });
await runDevicesCommand(["revoke", "--device", "device-1", "--role", "main"]);
expect(callGateway).toHaveBeenCalledWith(
expect.objectContaining({
method: "device.token.revoke",
params: {
deviceId: "device-1",
role: "main",
},
}),
);
});
it("rejects blank device or role values", async () => {
await runDevicesCommand(["rotate", "--device", " ", "--role", "main"]);
expect(callGateway).not.toHaveBeenCalled();
expect(runtime.error).toHaveBeenCalledWith("--device and --role required");
expect(runtime.exit).toHaveBeenCalledWith(1);
});
});

View File

@@ -110,6 +110,19 @@ function formatTokenSummary(tokens: DeviceTokenSummary[] | undefined) {
return parts.join(", ");
}
function resolveRequiredDeviceRole(
opts: DevicesRpcOpts,
): { deviceId: string; role: string } | null {
const deviceId = String(opts.device ?? "").trim();
const role = String(opts.role ?? "").trim();
if (deviceId && role) {
return { deviceId, role };
}
defaultRuntime.error("--device and --role required");
defaultRuntime.exit(1);
return null;
}
export function registerDevicesCli(program: Command) {
const devices = program.command("devices").description("Device pairing and auth tokens");
@@ -318,16 +331,13 @@ export function registerDevicesCli(program: Command) {
.requiredOption("--role <role>", "Role name")
.option("--scope <scope...>", "Scopes to attach to the token (repeatable)")
.action(async (opts: DevicesRpcOpts) => {
const deviceId = String(opts.device ?? "").trim();
const role = String(opts.role ?? "").trim();
if (!deviceId || !role) {
defaultRuntime.error("--device and --role required");
defaultRuntime.exit(1);
const required = resolveRequiredDeviceRole(opts);
if (!required) {
return;
}
const result = await callGatewayCli("device.token.rotate", opts, {
deviceId,
role,
deviceId: required.deviceId,
role: required.role,
scopes: Array.isArray(opts.scope) ? opts.scope : undefined,
});
defaultRuntime.log(JSON.stringify(result, null, 2));
@@ -341,16 +351,13 @@ export function registerDevicesCli(program: Command) {
.requiredOption("--device <id>", "Device id")
.requiredOption("--role <role>", "Role name")
.action(async (opts: DevicesRpcOpts) => {
const deviceId = String(opts.device ?? "").trim();
const role = String(opts.role ?? "").trim();
if (!deviceId || !role) {
defaultRuntime.error("--device and --role required");
defaultRuntime.exit(1);
const required = resolveRequiredDeviceRole(opts);
if (!required) {
return;
}
const result = await callGatewayCli("device.token.revoke", opts, {
deviceId,
role,
deviceId: required.deviceId,
role: required.role,
});
defaultRuntime.log(JSON.stringify(result, null, 2));
}),