import { describe, expect, spyOn, test } from "bun:test"; import { runCommand } from "citty"; import { listAuditEvents } from "../../command/audit"; import { main } from "../../command/main"; import { getOperation, listOperations } from "../../command/operations"; import { addUser, createApiKey, deleteUser, listApiKeys, listUsers, revokeApiKey, revokeUserSessions, setUserDisabled, updateUser, users, } from "../../command/users"; import type { ApiRequestInit } from "../../lib/api"; import * as api from "../../lib/api"; type Call = { path: string; init?: ApiRequestInit }; function requestReturning(result: T, calls: Call[]) { return async (path: string, init?: ApiRequestInit): Promise => { calls.push({ path, init }); return result as unknown as R; }; } const user = { username: "alice", roles: ["admin"], disabled: false, updatedAt: "2026-09-02T10:00:00.000Z", }; describe("user administration commands", () => { test("lists and creates users through authenticated API routes", async () => { const calls: Call[] = []; expect( await listUsers(requestReturning({ items: [user] }, calls)), ).toContain("alice"); expect( await addUser( "alice", "secret", ["admin"], requestReturning(user, calls), ), ).toContain("admin"); expect(calls).toEqual([ { path: "/users", init: undefined }, { path: "/users", init: { method: "POST", json: { username: "alice", password: "secret", roles: ["admin"] }, }, }, ]); }); test("updates roles, passwords, and enabled state with PATCH", async () => { const calls: Call[] = []; const request = requestReturning(user, calls); await updateUser( "alice/example", { roles: ["operator"], password: "new" }, request, ); await setUserDisabled("alice", true, request); await setUserDisabled("alice", false, request); expect(calls).toEqual([ { path: "/users/alice%2Fexample", init: { method: "PATCH", json: { roles: ["operator"], password: "new" }, }, }, { path: "/users/alice", init: { method: "PATCH", json: { disabled: true } }, }, { path: "/users/alice", init: { method: "PATCH", json: { disabled: false } }, }, ]); }); test("requires confirmation for deletion and supports session revocation", async () => { const calls: Call[] = []; const request = requestReturning(undefined, calls); expect(await deleteUser("alice", false, request)).toBe( "Deletion cancelled", ); expect(await deleteUser("alice", true, request)).toBe("Deleted user alice"); expect( await revokeUserSessions( "alice", requestReturning({ username: "alice", revoked: 2 }, calls), ), ).toBe("Revoked 2 sessions for alice"); expect(calls).toEqual([ { path: "/users/alice", init: { method: "DELETE" } }, { path: "/users/alice/sessions/revoke", init: { method: "POST" }, }, ]); }); test("manages API keys below the user route without leaking token in lists", async () => { const calls: Call[] = []; const key = { id: "key-identifier-123", username: "alice", capabilities: ["kubernetes:write"] as const, expiresAt: "2026-12-01T00:00:00.000Z", disabled: false, token: "shown-once-token", }; expect( await listApiKeys( "alice/example", requestReturning({ items: [{ ...key, token: undefined }] }, calls), ), ).not.toContain(key.token); await createApiKey( "alice", { capabilities: ["kubernetes:write"] }, async (path: string, init?: ApiRequestInit): Promise => { calls.push({ path, init }); return (init ? key : user) as T; }, ); expect( await revokeApiKey( "alice", key.id, false, requestReturning(undefined, calls), ), ).toContain("cancelled"); await revokeApiKey( "alice", key.id, true, requestReturning(undefined, calls), ); expect(calls).toEqual([ { path: "/users/alice%2Fexample/keys", init: undefined }, { path: "/users/alice", init: undefined }, { path: "/users/alice/keys", init: { method: "POST", json: { capabilities: ["kubernetes:write"] } }, }, { path: "/users/alice/keys/key-identifier-123", init: { method: "DELETE" }, }, ]); }); test("lists all users' keys without a username and filters with one", async () => { const calls: Call[] = []; const request = async ( path: string, init?: ApiRequestInit, ): Promise => { calls.push({ path, init }); if (path === "/users") return { items: [user, { ...user, username: "bob" }] } as T; return { items: [ { id: `${path.includes("bob") ? "bob" : "alice"}-key`, username: path.includes("bob") ? "bob" : "alice", capabilities: ["kubernetes:read"], disabled: false, }, ], } as T; }; const all = await listApiKeys(undefined, request); expect(all).toContain("alice-key"); expect(all).toContain("bob-key"); expect(all).toContain("never"); expect(calls.map((call) => call.path)).toEqual([ "/users", "/users/alice/keys", "/users/bob/keys", ]); calls.length = 0; expect(await listApiKeys("alice", request)).not.toContain("bob-key"); expect(calls.map((call) => call.path)).toEqual(["/users/alice/keys"]); }); test("key creation requires an existing active user", async () => { const calls: Call[] = []; const body = { capabilities: ["kubernetes:read" as const] }; await expect( createApiKey("missing", body, async (path) => { calls.push({ path }); throw new Error("User 'missing' not found"); }), ).rejects.toThrow("User 'missing' not found"); expect(calls).toEqual([{ path: "/users/missing" }]); await expect( createApiKey( "alice", body, requestReturning({ ...user, disabled: true }, calls), ), ).rejects.toThrow("user 'alice' is inactive"); expect(calls.at(-1)?.path).toBe("/users/alice"); }); test("creates non-expiring and finite API keys with the requested POST bodies", async () => { const calls: Call[] = []; const key = { id: "key-identifier-123", username: "alice", capabilities: ["kubernetes:read"] as const, disabled: false, token: "shown-once-token", }; const request = async ( path: string, init?: ApiRequestInit, ): Promise => { calls.push({ path, init }); return (init ? key : user) as T; }; await createApiKey( "alice", { capabilities: ["kubernetes:read"], workspace: "team/shop" }, request, ); const expiresAt = "2026-12-01T00:00:00.000Z"; await createApiKey( "alice", { capabilities: ["kubernetes:read"], workspace: "team/shop", expiresAt, }, request, ); expect(calls).toEqual([ { path: "/users/alice", init: undefined }, { path: "/users/alice/keys", init: { method: "POST", json: { capabilities: ["kubernetes:read"], workspace: "team/shop" }, }, }, { path: "/users/alice", init: undefined }, { path: "/users/alice/keys", init: { method: "POST", json: { capabilities: ["kubernetes:read"], workspace: "team/shop", expiresAt, }, }, }, ]); expect(calls[1]?.init?.json).not.toHaveProperty("expiresAt"); }); test("key command help declares username, capability examples, and non-expiry", async () => { const commands = (users.subCommands as Record)?.keys; if (!commands || typeof commands === "function") throw new Error("Missing keys command"); const subCommands = await Promise.resolve(commands.subCommands); const create = subCommands?.create; const ls = subCommands?.ls; if ( !create || typeof create === "function" || !ls || typeof ls === "function" ) throw new Error("Missing key subcommands"); expect(create.args?.username).toMatchObject({ type: "positional", required: true, }); expect(create.args?.capabilities?.description).toContain( "kubernetes:read,kubernetes:write", ); expect(create.args?.["expires-days"]?.description).toContain("none"); expect(create.args?.workspace?.alias).toBe("w"); expect(create.args?.["expires-days"]?.alias).toBe("e"); expect(create.meta?.description).toContain("--expireDays=none"); expect(ls.meta?.description).toContain("optional positional username"); await expect( create.run?.({ args: { _: [], username: "alice", capabilities: "invalid", "expires-days": "90", }, } as never), ).rejects.toThrow("kubernetes:read,kubernetes:write"); await expect( create.run?.({ args: { _: [], username: "alice", capabilities: "kubernetes:read", "expires-days": "NaN", }, } as never), ).rejects.toThrow("1 to 365, or none"); }); test("parses both API key creation forms and retains username position", async () => { const calls: Call[] = []; const request = spyOn(api, "apiRequest").mockImplementation((async ( path: string, init?: ApiRequestInit, ) => { calls.push({ path, init }); return ( init ? { id: "fake-key-id-123456", username: "dmgnr", capabilities: init.json && (init.json as { capabilities: string[] }).capabilities, workspace: "kuber-server", token: "fake-token-never-real", disabled: false, } : { username: "dmgnr", roles: ["admin"], disabled: false } ) as T; }) as typeof api.apiRequest); const log = spyOn(console, "log").mockImplementation(() => {}); const keyCommand = (users.subCommands as Record).keys; const create = keyCommand.subCommands.create; try { await runCommand(users, { rawArgs: [ "keys", "create", "--capabilities", "kubernetes:write", "--workspace", "kuber-server", "--expires-days", "none", "dmgnr", ], }); expect(calls[1]?.path).toBe("/users/dmgnr/keys"); expect(calls[1]?.init?.json).toEqual({ capabilities: ["kubernetes:write"], workspace: "kuber-server", }); calls.length = 0; await runCommand(users, { rawArgs: [ "keys", "create", "--capabilities", "kubernetes:read", "--expires-days", "30", "dmgnr", ], }); expect( (calls[1]?.init?.json as { expiresAt: string }).expiresAt, ).toBeString(); calls.length = 0; await runCommand(users, { rawArgs: [ "keys", "create", "-w", "kuber-server", "-e", "none", "dmgnr", "kubernetes:write,kubernetes:read", ], }); expect(calls[1]?.path).toBe("/users/dmgnr/keys"); expect(calls[1]?.init?.json).toEqual({ capabilities: ["kubernetes:write", "kubernetes:read"], workspace: "kuber-server", }); await expect( create.run?.({ args: { username: "dmgnr" } } as never), ).rejects.toThrow("Provide capabilities"); await expect( create.run?.({ args: { username: "dmgnr", capabilities: "kubernetes:read", _: ["dmgnr", "kubernetes:write"], }, } as never), ).rejects.toThrow("not both"); await expect( create.run?.({ args: { username: "dmgnr", capabilities: "kubernetes:read", "expires-days": "0", }, } as never), ).rejects.toThrow("1 to 365, or none"); calls.length = 0; await runCommand(users, { rawArgs: [ "keys", "create", "dmgnr", "--capabilities", "kubernetes:read", ], }); expect( (calls[1]?.init?.json as { expiresAt: string }).expiresAt, ).toBeString(); } finally { request.mockRestore(); log.mockRestore(); } }); }); const operation = { metadata: { name: "operation-1", creationTimestamp: "2026-09-02T10:00:00.000Z", }, spec: { workspaceId: "team/shop", action: "restart" }, status: { state: "succeeded", result: { deployments: ["web"] } }, }; describe("operations and audit commands", () => { test("lists filtered operations and gets operation details", async () => { const calls: Call[] = []; const listing = await listOperations( "team/shop", requestReturning({ items: [operation] }, calls), ); const detail = await getOperation( "operation/1", requestReturning(operation, calls), ); expect(listing).toContain("restart"); expect(detail).toContain('Result: {"deployments":["web"]}'); expect(calls).toEqual([ { path: "/operations?workspaceId=team%2Fshop", init: undefined }, { path: "/operations/operation%2F1", init: undefined }, ]); }); test("lists audit events with an optional workspace filter", async () => { const calls: Call[] = []; const output = await listAuditEvents( "team/shop", requestReturning( { items: [ { metadata: { name: "audit-1", creationTimestamp: "2026-09-02T10:00:00.000Z", }, spec: { actor: { username: "alice" }, action: "workspace.restart", workspaceId: "team/shop", outcome: "success", }, }, ], }, calls, ), ); expect(output).toContain("alice"); expect(output).toContain("workspace.restart"); expect(calls).toEqual([ { path: "/audit?workspaceId=team%2Fshop", init: undefined }, ]); }); test("registers administration command groups", async () => { const subCommands = await Promise.resolve(main.subCommands); expect(Object.keys(subCommands ?? {})).toEqual( expect.arrayContaining(["users", "operations", "audit"]), ); }); });