Skip to content
File

Blob: tests/client/stores/auth-store.dom.test.ts

typescript151 lines
1import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2import { createUser } from "@tests/client/util/fixtures";
3import { SESSION_MODES, STORAGE_KEYS } from "@/client/lib/constants";
4 
5let useAuthStore: typeof import("@/client/stores/auth-store").useAuthStore;
6let selectIsAuthenticated: typeof import("@/client/stores/auth-store").selectIsAuthenticated;
7let selectHasLocalSession: typeof import("@/client/stores/auth-store").selectHasLocalSession;
8 
9beforeEach(async () => {
10 localStorage.clear();
11 vi.resetModules();
12 const mod = await import("@/client/stores/auth-store");
13 useAuthStore = mod.useAuthStore;
14 selectIsAuthenticated = mod.selectIsAuthenticated;
15 selectHasLocalSession = mod.selectHasLocalSession;
16});
17 
18afterEach(() => {
19 vi.restoreAllMocks();
20 localStorage.clear();
21});
22 
23describe("auth-store", () => {
24 describe("initial state", () => {
25 it("starts with ANONYMOUS session mode and no auth", () => {
26 const state = useAuthStore.getState();
27 expect(state.sessionMode).toBe(SESSION_MODES.ANONYMOUS);
28 expect(state.accessToken).toBeNull();
29 expect(state.refreshState).toBe("idle");
30 expect(selectIsAuthenticated(state)).toBe(false);
31 expect(selectHasLocalSession(state)).toBe(false);
32 });
33 
34 it("rehydrates cached user from localStorage", async () => {
35 const user = createUser();
36 localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify({ version: 1, value: user }));
37 vi.resetModules();
38 const mod = await import("@/client/stores/auth-store");
39 expect(mod.useAuthStore.getState().user).toEqual(user);
40 expect(mod.useAuthStore.getState().sessionMode).toBe(SESSION_MODES.LOCAL_ONLY);
41 });
42 
43 it("handles malformed user JSON gracefully", async () => {
44 localStorage.setItem(STORAGE_KEYS.USER, "{bad-json");
45 vi.resetModules();
46 const mod = await import("@/client/stores/auth-store");
47 expect(mod.useAuthStore.getState().user).toBeNull();
48 });
49 });
50 
51 describe("setAuth", () => {
52 it("sets AUTHENTICATED mode with correct derived flags", () => {
53 const user = createUser();
54 useAuthStore.getState().setAuth("tok-123", user);
55 
56 const state = useAuthStore.getState();
57 expect(state.accessToken).toBe("tok-123");
58 expect(state.user).toEqual(user);
59 expect(state.sessionMode).toBe(SESSION_MODES.AUTHENTICATED);
60 expect(selectIsAuthenticated(state)).toBe(true);
61 expect(selectHasLocalSession(state)).toBe(true);
62 });
63 
64 it("persists user to localStorage", () => {
65 const user = createUser();
66 useAuthStore.getState().setAuth("tok-123", user);
67 expect(JSON.parse(localStorage.getItem(STORAGE_KEYS.USER)!)).toEqual({ version: 1, value: user });
68 });
69 
70 it("does not throw when localStorage writes fail", () => {
71 const user = createUser();
72 vi.spyOn(localStorage, "setItem").mockImplementation(() => {
73 throw new Error("quota");
74 });
75 expect(() => useAuthStore.getState().setAuth("tok-123", user)).not.toThrow();
76 });
77 });
78 
79 describe("markExpired", () => {
80 it("clears token but retains user", () => {
81 const user = createUser();
82 useAuthStore.getState().setAuth("tok-123", user);
83 useAuthStore.getState().markExpired();
84 
85 const state = useAuthStore.getState();
86 expect(state.accessToken).toBeNull();
87 expect(state.user).toEqual(user);
88 expect(state.sessionMode).toBe(SESSION_MODES.EXPIRED);
89 expect(selectIsAuthenticated(state)).toBe(false);
90 expect(selectHasLocalSession(state)).toBe(true);
91 });
92 });
93 
94 describe("markLocalOnly", () => {
95 it("clears token but keeps the cached user for local-only flows", () => {
96 const user = createUser();
97 useAuthStore.getState().setAuth("tok-123", user);
98 useAuthStore.getState().markLocalOnly();
99 
100 const state = useAuthStore.getState();
101 expect(state.accessToken).toBeNull();
102 expect(state.user).toEqual(user);
103 expect(state.sessionMode).toBe(SESSION_MODES.LOCAL_ONLY);
104 expect(selectIsAuthenticated(state)).toBe(false);
105 expect(selectHasLocalSession(state)).toBe(true);
106 });
107 });
108 
109 describe("clearAuth", () => {
110 it("resets to ANONYMOUS and removes all persisted data", () => {
111 const user = createUser();
112 useAuthStore.getState().setAuth("tok-123", user);
113 useAuthStore.getState().clearAuth();
114 
115 const state = useAuthStore.getState();
116 expect(state.accessToken).toBeNull();
117 expect(state.user).toBeNull();
118 expect(state.sessionMode).toBe(SESSION_MODES.ANONYMOUS);
119 expect(selectIsAuthenticated(state)).toBe(false);
120 expect(selectHasLocalSession(state)).toBe(false);
121 expect(localStorage.getItem(STORAGE_KEYS.USER)).toBeNull();
122 expect(localStorage.getItem(STORAGE_KEYS.CACHED_DOCS)).toBeNull();
123 });
124 });
125 
126 describe("setSessionMode", () => {
127 it.each([
128 [SESSION_MODES.AUTHENTICATED, true, true],
129 [SESSION_MODES.LOCAL_ONLY, false, true],
130 [SESSION_MODES.EXPIRED, false, true],
131 [SESSION_MODES.ANONYMOUS, false, false],
132 ] as const)("mode %s -> isAuthenticated=%s, hasLocalSession=%s", (mode, expectAuth, expectLocal) => {
133 useAuthStore.getState().setSessionMode(mode);
134 
135 const state = useAuthStore.getState();
136 expect(state.sessionMode).toBe(mode);
137 expect(selectIsAuthenticated(state)).toBe(expectAuth);
138 expect(selectHasLocalSession(state)).toBe(expectLocal);
139 });
140 });
141 
142 describe("setRefreshState", () => {
143 it("tracks the non-persisted refresh lifecycle", () => {
144 useAuthStore.getState().setRefreshState("refreshing");
145 expect(useAuthStore.getState().refreshState).toBe("refreshing");
146 useAuthStore.getState().setRefreshState("idle");
147 expect(useAuthStore.getState().refreshState).toBe("idle");
148 });
149 });
150});