Files
hyperframes/packages/cli/src/commands/auth/status-user.test.ts
T

122 lines
4.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { writeStore } from "../../auth/store.js";
import { setupTempAuthEnv, type EnvFixture } from "../../auth/_test-utils.js";
import { consumeCommandResult } from "../../utils/commandResult.js";
// Mock only AuthClient so the live /v3/users/me probe is controllable;
// keep the real store/resolver/user helpers so the test exercises the
// actual on-disk persisted-user-block surfacing.
// - apiReject: throw ErrApi on getCurrentUser (simulates an API blip)
// - user: the live identity returned on success
const probeState = vi.hoisted(
() =>
({ apiReject: false, user: { email: "live@example.com" } }) as {
apiReject: boolean;
user: Record<string, unknown>;
},
);
vi.mock("../../auth/index.js", async (orig) => {
const actual = await orig<typeof import("../../auth/index.js")>();
class MockAuthClient {
async getCurrentUser(): Promise<Record<string, unknown>> {
if (probeState.apiReject) {
const { ErrApi } = await import("../../auth/errors.js");
throw ErrApi(503, "service unavailable");
}
return probeState.user;
}
}
return { ...actual, AuthClient: MockAuthClient };
});
describe("auth status — persisted user block surface", () => {
let envFixture: EnvFixture;
let stdout: string[];
beforeEach(async () => {
envFixture = await setupTempAuthEnv("hf-status-");
probeState.apiReject = false;
probeState.user = { email: "live@example.com" };
stdout = [];
consumeCommandResult();
vi.spyOn(console, "log").mockImplementation((...args: unknown[]) => {
stdout.push(args.join(" "));
});
vi.spyOn(console, "error").mockImplementation(() => {});
});
afterEach(async () => {
consumeCommandResult();
vi.restoreAllMocks();
await envFixture.restore();
});
async function runStatus(asJson: boolean): Promise<number> {
const cmd = (await import("./status.js")).default;
await (cmd.run as (ctx: { args: Record<string, unknown> }) => Promise<void>)({
args: { json: asJson },
});
return consumeCommandResult().exitCode;
}
function lastJson(): Record<string, unknown> {
return JSON.parse(stdout[stdout.length - 1] ?? "{}");
}
it("surfaces the persisted user block (with resolved display_name) for a file credential", async () => {
await writeStore({
api_key: "hg_x",
user: { email: "jane@example.com", first_name: "Jane", last_name: "Doe", username: "jdoe" },
});
const code = await runStatus(true);
expect(code).toBe(0);
const payload = lastJson();
expect(payload["source"]).toBe("file_json");
expect(payload["persisted_user"]).toEqual({
email: "jane@example.com",
first_name: "Jane",
last_name: "Doe",
username: "jdoe",
display_name: "jane@example.com",
});
});
it("backwards-compat: a file credential with no user block reports persisted_user: null", async () => {
await writeStore({ api_key: "hg_legacy" });
const code = await runStatus(true);
expect(code).toBe(0);
const payload = lastJson();
expect(payload["persisted_user"]).toBeNull();
// The live `user` field (from the API probe) is unchanged / additive.
expect(payload["user"]).toEqual({ email: "live@example.com" });
});
it("skips the persisted block for an env-sourced credential (could be a different key)", async () => {
// Seed a file-side user block, then resolve via the env key — the
// active credential is env, so the file block must NOT be surfaced.
await writeStore({ api_key: "hg_file", user: { email: "file-user@example.com" } });
process.env["HEYGEN_API_KEY"] = "hg_env_key";
const code = await runStatus(true);
expect(code).toBe(0);
const payload = lastJson();
expect(payload["source"]).toBe("env");
expect(payload["persisted_user"]).toBeNull();
});
it("falls back to the cached identity in human output when the live probe fails", async () => {
await writeStore({ api_key: "hg_x", user: { email: "cached@example.com" } });
probeState.apiReject = true;
const code = await runStatus(false);
expect(code).toBe(1); // API failure → non-zero exit
const text = stdout.join("\n");
expect(text).toContain("API check failed");
expect(text).toContain("cached@example.com");
expect(text).toContain("cached");
});
});