import { describe, expect, it, vi } from "vitest"; import { buildLocalSyncSnapshot, cancelSyncIfVaultLocked, checkSyncPreflight, planManifestSync, } from "./sync-coordinator"; import { decodeSyncMessage } from "../shared/sync-wire"; import type { VaultEntry } from "../shared/types"; function entry(overrides: Partial = {}): VaultEntry { return { id: "entry-1", type: "login", title: "GitHub", username: "june", password: "secret", url: "https://github.com", createdAt: 1_000, modifiedAt: 1_000, ...overrides, }; } describe("sync coordinator", () => { it("blocks sync for free users before inspecting the vault", async () => { const getEntries = vi.fn(() => [entry()]); await expect( checkSyncPreflight({ isProUser: async () => false, isUnlocked: () => true, }) ).resolves.toEqual({ ok: false, error: "pro_required" }); expect(getEntries).not.toHaveBeenCalled(); }); it("blocks sync when the vault is locked", async () => { await expect( checkSyncPreflight({ isProUser: async () => true, isUnlocked: () => false, }) ).resolves.toEqual({ ok: false, error: "vault_locked" }); }); it("builds a local sync snapshot only after preflight passes", async () => { const snapshot = await buildLocalSyncSnapshot({ isProUser: async () => true, isUnlocked: () => true, getEntries: () => [entry({ id: "b" }), entry({ id: "a", modifiedAt: 2_000 })], }); expect(snapshot.entries).toHaveLength(2); expect(snapshot.manifest.map((item) => item.id)).toEqual(["a", "b"]); }); it("plans manifest sync without sending entry payloads", async () => { const plan = await planManifestSync( [{ id: "remote-only", modifiedAt: 1_500, hash: "remote" }], { isProUser: async () => true, isUnlocked: () => true, getEntries: () => [entry({ id: "local-only", modifiedAt: 2_000 })], } ); expect(plan.diff).toEqual({ requestFromRemote: ["remote-only"], offerToRemote: ["local-only"], conflicts: [], }); }); it("sends a stable sync error when a peer asks while the vault is locked", async () => { const sendSyncData = vi.fn(); const cancelled = await cancelSyncIfVaultLocked("conn-1", { isUnlocked: () => false, sendSyncData, }); expect(cancelled).toBe(true); expect(sendSyncData).toHaveBeenCalledWith("conn-1", expect.any(String)); expect(decodeSyncMessage(sendSyncData.mock.calls[0]![1])).toEqual({ type: "SYNC_ERROR", code: "vault_locked", message: "Vault is locked; unlock Peach before syncing.", }); }); it("does not cancel an unlocked vault sync", async () => { const sendSyncData = vi.fn(); expect( await cancelSyncIfVaultLocked("conn-1", { isUnlocked: () => true, sendSyncData, }) ).toBe(false); expect(sendSyncData).not.toHaveBeenCalled(); }); });