import { describe, it, expect, vi, afterEach } from 'vitest' import { fetchSiteVisitorSession } from './useSiteVisitorSession' /** * The anonymous session probe is the common case and MUST be silent — no * console.error / console.warn — whether the server answers with the new * `200 { visitor: null }` contract or an old server's `401`. A red console * error on every signed-out page load is the console-noise this fixes. */ function installConsoleSpies() { return { error: vi.spyOn(console, 'error').mockImplementation(() => {}), warn: vi.spyOn(console, 'warn').mockImplementation(() => {}), } } function expectSilent(spies: ReturnType) { expect(spies.error).not.toHaveBeenCalled() expect(spies.warn).not.toHaveBeenCalled() } afterEach(() => { vi.unstubAllGlobals() vi.restoreAllMocks() }) describe('fetchSiteVisitorSession — anonymous probe is silent', () => { it('new server 200 {visitor:null} → signed out, no console noise', async () => { const spies = installConsoleSpies() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ visitor: null }) }), ) const result = await fetchSiteVisitorSession() expect(result.visitor).toBeNull() expect(result.authenticated).toBe(false) expectSilent(spies) }) it('old server 401 → signed out, no console noise', async () => { const spies = installConsoleSpies() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: false, status: 401, json: async () => ({ error: 'Not authenticated' }), }), ) const result = await fetchSiteVisitorSession() expect(result.visitor).toBeNull() expect(result.authenticated).toBe(false) expectSilent(spies) }) it('network error → signed out, no console noise', async () => { const spies = installConsoleSpies() vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('offline'))) const result = await fetchSiteVisitorSession() expect(result.visitor).toBeNull() expect(result.authenticated).toBe(false) expectSilent(spies) }) }) describe('fetchSiteVisitorSession — a misroute is NOT an anonymous visit (C-298 layer 2)', () => { /** * The default base is the RELATIVE `/api/v1`. On a host whose Front Door config has no * `/api/v1/*` route the catch-all answers with the SPA shell at HTTP 200 — which used to * be indistinguishable from "signed out", the reason KEPT ran with all nine login routes * dead for ~2 weeks (C-261) and the reason it is still live on www.nateduff.com. */ const SPA_SHELL = '
' function htmlResponse() { const headers = new Headers({ 'content-type': 'text/html; charset=utf-8' }) const build = (): Response => ({ ok: true, status: 200, headers, clone: () => build(), text: async () => SPA_SHELL, json: async () => JSON.parse(SPA_SHELL), }) as unknown as Response return build() } it('200 text/html → signed out for the UI, apiUnreachable=true, and LOUD on the console', async () => { const spies = installConsoleSpies() vi.stubGlobal('fetch', vi.fn().mockResolvedValue(htmlResponse())) const result = await fetchSiteVisitorSession() // The page still renders signed-out (a login button that cannot work is worse)… expect(result.visitor).toBeNull() expect(result.authenticated).toBe(false) // …but the caller can tell the difference, and the console is not silent. expect(result.apiUnreachable).toBe(true) expect(spies.error).toHaveBeenCalledTimes(1) const message = String(spies.error.mock.calls[0][0]) expect(message).toContain('/api/v1/site-auth/session') expect(message).toContain('text/html') expect(message).toContain('bodyClass=spa-html') }) it('the ordinary anonymous paths never set apiUnreachable', async () => { const spies = installConsoleSpies() vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ visitor: null }) }), ) const result = await fetchSiteVisitorSession() expect(result.apiUnreachable).toBeFalsy() expectSilent(spies) }) }) describe('fetchSiteVisitorSession — authenticated shapes', () => { it('200 {visitor:{...}} → returns the visitor', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ visitor: { id: 'g-1', email: 'v@example.com', name: 'Visitor', createdAt: '2026-01-01T00:00:00Z' }, }), }), ) const result = await fetchSiteVisitorSession() expect(result.authenticated).toBe(true) expect(result.visitor?.email).toBe('v@example.com') expect(result.visitor?.name).toBe('Visitor') }) it('contracts-spec shape {authenticated,user:{...}} → returns the visitor', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ authenticated: true, user: { email: 'u@example.com', name: 'User' } }), }), ) const result = await fetchSiteVisitorSession() expect(result.authenticated).toBe(true) expect(result.visitor?.email).toBe('u@example.com') }) it('probes the slug-free /site-auth/session path with credentials', async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ visitor: null }) }) vi.stubGlobal('fetch', fetchMock) await fetchSiteVisitorSession() expect(fetchMock).toHaveBeenCalledTimes(1) const [url, init] = fetchMock.mock.calls[0] expect(url).toBe('/api/v1/site-auth/session') expect(init.credentials).toBe('include') }) })