auth.test.ts (5829B)
1 import { describe, it, expect, vi, beforeEach } from "vitest"; 2 import { useAuthStore } from "./auth"; 3 4 beforeEach(() => { 5 useAuthStore.setState({ 6 authenticated: false, 7 configured: false, 8 loading: false, 9 error: null, 10 }); 11 vi.restoreAllMocks(); 12 }); 13 14 describe("auth store", () => { 15 it("has initial unauthenticated state", () => { 16 const state = useAuthStore.getState(); 17 expect(state.authenticated).toBe(false); 18 expect(state.configured).toBe(false); 19 expect(state.loading).toBe(false); 20 expect(state.error).toBeNull(); 21 }); 22 23 it("login success updates state to authenticated", async () => { 24 vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ 25 ok: true, json: async () => ({}), 26 })); 27 await useAuthStore.getState().login("correct-password"); 28 expect(useAuthStore.getState().authenticated).toBe(true); 29 expect(useAuthStore.getState().error).toBeNull(); 30 expect(useAuthStore.getState().loading).toBe(false); 31 }); 32 33 it("login sets loading to true during request", async () => { 34 let resolveFetch!: (v: unknown) => void; 35 vi.stubGlobal("fetch", vi.fn().mockReturnValue( 36 new Promise((resolve) => { resolveFetch = resolve; }), 37 )); 38 const loginPromise = useAuthStore.getState().login("test"); 39 expect(useAuthStore.getState().loading).toBe(true); 40 resolveFetch({ ok: true, json: async () => ({}) }); 41 await loginPromise; 42 expect(useAuthStore.getState().loading).toBe(false); 43 }); 44 45 it("login failure sets error message from Error instance", async () => { 46 vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("Invalid password"))); 47 await useAuthStore.getState().login("wrong-password"); 48 expect(useAuthStore.getState().authenticated).toBe(false); 49 expect(useAuthStore.getState().error).toBe("Invalid password"); 50 expect(useAuthStore.getState().loading).toBe(false); 51 }); 52 53 it("login failure sets generic error for non-Error rejection", async () => { 54 vi.stubGlobal("fetch", vi.fn().mockRejectedValue("string error")); 55 await useAuthStore.getState().login("wrong-password"); 56 expect(useAuthStore.getState().authenticated).toBe(false); 57 expect(useAuthStore.getState().error).toBe("Login failed"); 58 expect(useAuthStore.getState().loading).toBe(false); 59 }); 60 61 it("login clears previous error", async () => { 62 useAuthStore.setState({ error: "old error" }); 63 vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ 64 ok: true, json: async () => ({}), 65 })); 66 await useAuthStore.getState().login("test"); 67 expect(useAuthStore.getState().error).toBeNull(); 68 }); 69 70 it("logout clears authenticated state", async () => { 71 useAuthStore.setState({ authenticated: true }); 72 vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ 73 ok: true, json: async () => ({}), 74 })); 75 await useAuthStore.getState().logout(); 76 expect(useAuthStore.getState().authenticated).toBe(false); 77 }); 78 79 it("logout sets unauthenticated even on API error", async () => { 80 useAuthStore.setState({ authenticated: true }); 81 vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("network error"))); 82 // logout has finally block, so it should set authenticated to false 83 // even if the API call fails 84 try { 85 await useAuthStore.getState().logout(); 86 } catch { 87 // The error may propagate, but finally still runs 88 } 89 expect(useAuthStore.getState().authenticated).toBe(false); 90 }); 91 92 it("checkStatus updates configured and authenticated", async () => { 93 vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ 94 ok: true, json: async () => ({ configured: true, authenticated: false }), 95 })); 96 await useAuthStore.getState().checkStatus(); 97 expect(useAuthStore.getState().configured).toBe(true); 98 expect(useAuthStore.getState().authenticated).toBe(false); 99 expect(useAuthStore.getState().loading).toBe(false); 100 }); 101 102 it("checkStatus handles configured + authenticated", async () => { 103 vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ 104 ok: true, json: async () => ({ configured: true, authenticated: true }), 105 })); 106 await useAuthStore.getState().checkStatus(); 107 expect(useAuthStore.getState().configured).toBe(true); 108 expect(useAuthStore.getState().authenticated).toBe(true); 109 }); 110 111 it("checkStatus sets loading to true during request", async () => { 112 let resolveFetch!: (v: unknown) => void; 113 vi.stubGlobal("fetch", vi.fn().mockReturnValue( 114 new Promise((resolve) => { resolveFetch = resolve; }), 115 )); 116 const checkPromise = useAuthStore.getState().checkStatus(); 117 expect(useAuthStore.getState().loading).toBe(true); 118 resolveFetch({ ok: true, json: async () => ({ configured: false, authenticated: false }) }); 119 await checkPromise; 120 expect(useAuthStore.getState().loading).toBe(false); 121 }); 122 123 it("checkStatus handles error by setting loading to false", async () => { 124 vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("network error"))); 125 await useAuthStore.getState().checkStatus(); 126 expect(useAuthStore.getState().loading).toBe(false); 127 expect(useAuthStore.getState().configured).toBe(false); 128 expect(useAuthStore.getState().authenticated).toBe(false); 129 }); 130 131 it("checkStatus handles non-Error rejection", async () => { 132 vi.stubGlobal("fetch", vi.fn().mockRejectedValue("string error")); 133 await useAuthStore.getState().checkStatus(); 134 expect(useAuthStore.getState().loading).toBe(false); 135 }); 136 137 it("login sends password in body", async () => { 138 const mockFetch = vi.fn().mockResolvedValue({ 139 ok: true, json: async () => ({}), 140 }); 141 vi.stubGlobal("fetch", mockFetch); 142 await useAuthStore.getState().login("my-password"); 143 const callBody = JSON.parse(mockFetch.mock.calls[0][1].body); 144 expect(callBody.password).toBe("my-password"); 145 }); 146 });