12345678910import { beforeEach, describe, expect, it, vi } from "vitest";1112type StorageShape = Record<string, unknown>;1314describe("codex handlers", () => {15 let storage: StorageShape;1617 beforeEach(() => {18 vi.resetModules();19 vi.doUnmock("./vault");20 vi.doUnmock("./license");21 storage = {};2223 const chromeMock = {24 storage: {25 local: {26 get: vi.fn(async (key?: string | string[]) => {27 if (!key) return storage;28 if (typeof key === "string") return { [key]: storage[key] };29 return key.reduce<Record<string, unknown>>((acc, k) => {30 acc[k] = storage[k];31 return acc;32 }, {});33 }),34 set: vi.fn(async (next: Record<string, unknown>) => {35 Object.assign(storage, next);36 }),37 remove: vi.fn(async (keys: string | string[]) => {38 const list = Array.isArray(keys) ? keys : [keys];39 for (const k of list) delete storage[k];40 }),41 },42 },43 } as unknown as typeof chrome;44 (globalThis as { chrome?: typeof chrome }).chrome = chromeMock;45 });4647 it("handleGetCodexVaultData rejects free users with pro_required", async () => {48 vi.doMock("./license", () => ({ isProUser: vi.fn(async () => false) }));49 vi.doMock("./vault", () => ({50 isUnlocked: () => true,51 getEntries: () => [],52 getRecoveryWords: async () => null,53 }));54 const { handleGetCodexVaultData } = await import("./codex-handlers");55 const result = await handleGetCodexVaultData();56 expect(result).toEqual({ ok: false, error: "pro_required" });57 });5859 it("handleGetCodexVaultData returns VAULT_LOCKED for locked vault", async () => {60 vi.doMock("./license", () => ({ isProUser: vi.fn(async () => true) }));61 vi.doMock("./vault", () => ({62 isUnlocked: () => false,63 getEntries: () => {64 throw new Error("vault locked");65 },66 getRecoveryWords: async () => {67 throw new Error("vault locked");68 },69 }));70 const { handleGetCodexVaultData } = await import("./codex-handlers");71 const result = await handleGetCodexVaultData();72 expect(result.ok).toBe(false);73 expect(result.error).toMatch(/^VAULT_LOCKED/);74 });7576 it("handleEstimateCodexPages returns >=1 page for a real slotA blob", async () => {77 vi.doMock("./license", () => ({ isProUser: vi.fn(async () => true) }));78 vi.doMock("./vault", () => ({})); 79 80 81 storage.peach_vault_a = {82 version: 1,83 salt: "00".repeat(16),84 nonce: "00".repeat(12),85 86 ciphertext: "ab".repeat(1024),87 tag: "00".repeat(16),88 };89 storage.peach_vault_b = {90 version: 1,91 salt: "11".repeat(16),92 nonce: "11".repeat(12),93 ciphertext: "cd".repeat(1024),94 tag: "11".repeat(16),95 };96 const { handleEstimateCodexPages } = await import("./codex-handlers");97 const result = await handleEstimateCodexPages();98 expect(result.ok).toBe(true);99 expect(typeof result.pages).toBe("number");100 expect(result.pages!).toBeGreaterThanOrEqual(1);101 });102103 it("handleRestoreFromCodex rejects an invalid blob with INVALID_VAULT_BLOB", async () => {104 vi.doMock("./license", () => ({ isProUser: vi.fn(async () => false) }));105 const unlockFromCodex = vi.fn();106 vi.doMock("./vault", () => ({107 unlockFromCodex,108 isUnlocked: () => false,109 getEntries: () => [],110 }));111 vi.doMock("../shared/native", () => ({112 isNativePlatform: () => false,113 nativePlugin: () => null,114 }));115116 const { handleRestoreFromCodex } = await import("./codex-handlers");117 const result = await handleRestoreFromCodex({118 vaultBlob: "this-is-not-valid-base64url-json!!!",119 timestamp: 0,120 verifyCode: "PX-AAAA-BBBB",121 vaultFingerprint: "0".repeat(16),122 entryCount: 0,123 masterPassphrase: "ignored",124 });125 expect(result).toEqual({ ok: false, error: "INVALID_VAULT_BLOB" });126 127 128 expect(unlockFromCodex).not.toHaveBeenCalled();129 });130131 it("handleRestoreFromCodex passes through stringified slot payloads without double-encoding", async () => {132 vi.doMock("./license", () => ({ isProUser: vi.fn(async () => false) }));133 const unlockFromCodex = vi.fn(async () => ({ ok: true as const, entryCount: 1 }));134 vi.doMock("./vault", () => ({135 unlockFromCodex,136 isUnlocked: () => false,137 getEntries: () => [],138 }));139 vi.doMock("../shared/native", () => ({140 isNativePlatform: () => false,141 nativePlugin: () => null,142 }));143144 const payload = {145 vaultBlob: Buffer.from(146 JSON.stringify({147 a: "{\"version\":1,\"salt\":\"deadbeef\",\"nonce\":\"cafebabe\",\"ciphertext\":\"00\",\"tag\":\"11\"}",148 b: null,149 }),150 "utf8",151 ).toString("base64url"),152 timestamp: 1,153 verifyCode: "PX-AAAA-BBBB",154 vaultFingerprint: "0123456789ABCDEF0123456789ABCDEF",155 entryCount: 1,156 masterPassphrase: "correct horse battery staple",157 };158159 const { handleRestoreFromCodex } = await import("./codex-handlers");160 const result = await handleRestoreFromCodex(payload);161162 expect(result).toEqual({163 ok: true,164 data: { entryCount: 1, verifyCode: "PX-AAAA-BBBB" },165 });166 expect(unlockFromCodex).toHaveBeenCalledWith({167 slotA:168 "{\"version\":1,\"salt\":\"deadbeef\",\"nonce\":\"cafebabe\",\"ciphertext\":\"00\",\"tag\":\"11\"}",169 slotB: null,170 timestamp: 1,171 masterPassphrase: "correct horse battery staple",172 });173 });174});