import { describe, it, expect } from "vitest"; import { licenseReducer, initialLicenseState } from "./LicenseContext"; import type { Edition, LicenseInfo } from "../services/licenseService"; const makeInfo = (edition: Exclude): LicenseInfo => ({ edition, email: "max@example.com", features: [], machine_limit: 3, issued_at: 1, expires_at: 0, }); // The CWE-703 retry loop arms exclusively on `status === "error"`, and its // LOAD_START clears `error`. A rejected key must therefore never re-enter the // load lifecycle: it would arm the auto refresh, which would wipe the very // message the user is reading (~1s after submitting an invalid key). const premiumReady = licenseReducer( licenseReducer(initialLicenseState, { type: "LOAD_START" }), { type: "LOAD_DONE", edition: "premium", info: makeInfo("premium") }, ); describe("licenseReducer — load lifecycle (CWE-703)", () => { it("LOAD_ERROR preserves the last-known edition and info", () => { const errored = licenseReducer(premiumReady, { type: "LOAD_ERROR", error: "ipc down" }); expect(errored.status).toBe("error"); expect(errored.error).toBe("ipc down"); expect(errored.edition).toBe("premium"); expect(errored.info).toEqual(makeInfo("premium")); }); it("LOAD_START clears the load error but not a pending validation error", () => { const rejected = licenseReducer( licenseReducer(premiumReady, { type: "VALIDATE_START" }), { type: "VALIDATE_ERROR", error: "invalid key" }, ); const refreshed = licenseReducer(rejected, { type: "LOAD_START" }); expect(refreshed.status).toBe("loading"); expect(refreshed.error).toBeNull(); expect(refreshed.validationError).toBe("invalid key"); }); }); describe("licenseReducer — key validation is orthogonal to the load lifecycle", () => { it("VALIDATE_ERROR on a ready license keeps status ready (retry never arms)", () => { const validating = licenseReducer(premiumReady, { type: "VALIDATE_START" }); expect(validating.status).toBe("ready"); expect(validating.validating).toBe(true); const rejected = licenseReducer(validating, { type: "VALIDATE_ERROR", error: "invalid key" }); expect(rejected.status).toBe("ready"); expect(rejected.validating).toBe(false); expect(rejected.validationError).toBe("invalid key"); expect(rejected.edition).toBe("premium"); expect(rejected.info).toEqual(makeInfo("premium")); }); it("VALIDATE_ERROR during a boot error keeps status error (load retry keeps running)", () => { const bootError = licenseReducer( licenseReducer(initialLicenseState, { type: "LOAD_START" }), { type: "LOAD_ERROR", error: "boot fail" }, ); const rejected = licenseReducer( licenseReducer(bootError, { type: "VALIDATE_START" }), { type: "VALIDATE_ERROR", error: "invalid key" }, ); expect(rejected.status).toBe("error"); expect(rejected.error).toBe("boot fail"); expect(rejected.validationError).toBe("invalid key"); expect(rejected.edition).toBe("free"); }); it("VALIDATE_START clears the previous validation error", () => { const rejected = licenseReducer( licenseReducer(premiumReady, { type: "VALIDATE_START" }), { type: "VALIDATE_ERROR", error: "invalid key" }, ); const resubmit = licenseReducer(rejected, { type: "VALIDATE_START" }); expect(resubmit.validating).toBe(true); expect(resubmit.validationError).toBeNull(); }); it("VALIDATE_DONE resets the full state, recovering from a boot error", () => { const bootError = licenseReducer( licenseReducer(initialLicenseState, { type: "LOAD_START" }), { type: "LOAD_ERROR", error: "boot fail" }, ); const done = licenseReducer( licenseReducer(bootError, { type: "VALIDATE_START" }), { type: "VALIDATE_DONE", info: makeInfo("base") }, ); expect(done).toEqual({ status: "ready", edition: "base", info: makeInfo("base"), error: null, validating: false, validationError: null, revoked: false, }); }); }); // A `license_revoked` answer stays known for the whole session (#347, D13): the card // that receives it unmounts when the user leaves Settings → Users or switches profile, // this state does not. Only a different key clears it. describe("licenseReducer — revocation lasts the session", () => { const revoked = licenseReducer(premiumReady, { type: "LICENSE_REVOKED" }); it("LICENSE_REVOKED flags the stored key without touching the edition", () => { expect(revoked.revoked).toBe(true); expect(revoked.edition).toBe("premium"); expect(revoked.status).toBe("ready"); }); it("survives a reload of the license and a load error", () => { const reloaded = licenseReducer( licenseReducer(revoked, { type: "LOAD_START" }), { type: "LOAD_DONE", edition: "premium", info: makeInfo("premium") }, ); expect(reloaded.revoked).toBe(true); const failed = licenseReducer(licenseReducer(revoked, { type: "LOAD_START" }), { type: "LOAD_ERROR", error: "boot fail", }); expect(failed.revoked).toBe(true); }); it("survives a refused key", () => { const refused = licenseReducer(licenseReducer(revoked, { type: "VALIDATE_START" }), { type: "VALIDATE_ERROR", error: '{"code":"invalid_prefix"}', }); expect(refused.revoked).toBe(true); }); it("stays when the same key is entered again", () => { const same = licenseReducer(licenseReducer(revoked, { type: "VALIDATE_START" }), { type: "VALIDATE_DONE", info: makeInfo("premium"), }); expect(same.revoked).toBe(true); }); it("clears when a different key is stored", () => { const other: LicenseInfo = { ...makeInfo("premium"), issued_at: 2 }; const replaced = licenseReducer(licenseReducer(revoked, { type: "VALIDATE_START" }), { type: "VALIDATE_DONE", info: other, }); expect(replaced.revoked).toBe(false); }); });