1import { describe, expect, it, vi } from "vite-plus/test";
2import type { EnvironmentId } from "@t3tools/contracts";
3
4import {
5 browserProfileRemovalAvailable,
6 clearBrowserProfileData,
7 importFailureReason,
8} from "./IntegrationsSettings";
9
10const environmentId = "environment-a" as EnvironmentId;
11const secondEnvironmentId = "environment-b" as EnvironmentId;
12
13describe("clearBrowserProfileData", () => {
14 it("waits for cookie and cache cleanup", async () => {
15 const clearCookies = vi.fn().mockResolvedValue(undefined);
16 const clearCache = vi.fn().mockResolvedValue(undefined);
17
18 await clearBrowserProfileData({ clearCookies, clearCache }, [environmentId], "profile-a");
19
20 expect(clearCookies).toHaveBeenCalledWith(environmentId, "profile-a");
21 expect(clearCache).toHaveBeenCalledWith(environmentId, "profile-a");
22 });
23
24 it("clears every known environment before succeeding", async () => {
25 const clearCookies = vi.fn().mockResolvedValue(undefined);
26 const clearCache = vi.fn().mockResolvedValue(undefined);
27
28 await clearBrowserProfileData(
29 { clearCookies, clearCache },
30 [environmentId, secondEnvironmentId],
31 "profile-a",
32 );
33
34 expect(clearCookies.mock.calls).toEqual([
35 [environmentId, "profile-a"],
36 [secondEnvironmentId, "profile-a"],
37 ]);
38 expect(clearCache.mock.calls).toEqual([
39 [environmentId, "profile-a"],
40 [secondEnvironmentId, "profile-a"],
41 ]);
42 });
43
44 it("propagates cleanup failures", async () => {
45 const failure = new Error("clear failed");
46 await expect(
47 clearBrowserProfileData(
48 {
49 clearCookies: vi.fn().mockRejectedValue(failure),
50 clearCache: vi.fn().mockResolvedValue(undefined),
51 },
52 [environmentId],
53 "profile-a",
54 ),
55 ).rejects.toBe(failure);
56 });
57
58 it("does not report success without an environment or bridge", async () => {
59 const bridge = {
60 clearCookies: vi.fn().mockResolvedValue(undefined),
61 clearCache: vi.fn().mockResolvedValue(undefined),
62 };
63
64 await expect(clearBrowserProfileData(bridge, [], "profile-a")).rejects.toThrow();
65 await expect(clearBrowserProfileData(null, [environmentId], "profile-a")).rejects.toThrow();
66 expect(bridge.clearCookies).not.toHaveBeenCalled();
67 expect(bridge.clearCache).not.toHaveBeenCalled();
68 });
69});
70
71describe("clearBrowserProfileData with server browsers", () => {
72 it("clears server-hosted profiles without a desktop bridge", async () => {
73 const clear = vi.fn().mockResolvedValue(undefined);
74 await clearBrowserProfileData(null, [environmentId, secondEnvironmentId], "profile-a", {
75 environmentIds: [secondEnvironmentId],
76 clear,
77 });
78 expect(clear).toHaveBeenCalledExactlyOnceWith(secondEnvironmentId, "profile-a");
79 });
80
81 it("clears both copies and fails when the server copy fails", async () => {
82 const bridge = {
83 clearCookies: vi.fn().mockResolvedValue(undefined),
84 clearCache: vi.fn().mockResolvedValue(undefined),
85 };
86 const failure = new Error("server failed");
87 await expect(
88 clearBrowserProfileData(bridge, [environmentId], "profile-a", {
89 environmentIds: [environmentId],
90 clear: vi.fn().mockRejectedValue(failure),
91 }),
92 ).rejects.toBe(failure);
93 expect(bridge.clearCookies).toHaveBeenCalledWith(environmentId, "profile-a");
94 });
95});
96
97describe("browserProfileRemovalAvailable", () => {
98 it("accepts a server browser in place of the desktop bridge", () => {
99 expect(browserProfileRemovalAvailable(false, true, 1, 1)).toBe(true);
100 });
101
102 it("requires a ready non-empty catalog and desktop bridge", () => {
103 expect(browserProfileRemovalAvailable(true, true, 1)).toBe(true);
104 expect(browserProfileRemovalAvailable(true, true, 0)).toBe(false);
105 expect(browserProfileRemovalAvailable(true, false, 1)).toBe(false);
106 expect(browserProfileRemovalAvailable(false, true, 1)).toBe(false);
107 });
108});
109
110// Mirrors `BrowserImportFailedError.message`, which IPC flattens to a string
111// before the renderer sees it.
112const failure = (reason: string) => ({
113 message: `Importing cookies from safari failed: ${reason}.`,
114});
115
116describe("importFailureReason", () => {
117 it("recovers the reason token from the flattened message", () => {
118 expect(importFailureReason(failure("needsFullDiskAccess"))).toBe("needsFullDiskAccess");
119 expect(importFailureReason(failure("browserRunning"))).toBe("browserRunning");
120 expect(importFailureReason(failure("readFailed"))).toBe("readFailed");
121 expect(importFailureReason(failure("keychainUnavailable"))).toBe("keychainUnavailable");
122 // A settings write that fails after the cookies landed is its own case,
123 // not a read failure over a database that was in fact read.
124 expect(importFailureReason(failure("profileNotSaved"))).toBe("profileNotSaved");
125 expect(importFailureReason(failure("profileLimitReached"))).toBe("profileLimitReached");
126 });
127
128 it("falls back to readFailed for anything it cannot classify", () => {
129 expect(importFailureReason(new Error("something else entirely"))).toBe("readFailed");
130 expect(importFailureReason(undefined)).toBe("readFailed");
131 });
132});