import { describe, it, expect, beforeEach, afterEach, vi, } from 'vitest'; const part = (value) => Buffer.from(JSON.stringify(value)).toString('base64url'); const jwt = (claims) => `${part({ alg: 'RS256' })}.${part(claims)}.sig`; const goodToken = () => jwt({ exp: Math.floor(Date.now() / 1000) + 60 }); const expiredToken = () => jwt({ exp: Math.floor(Date.now() / 1000) - 60 }); async function loadRequest({ oidc = false, keycloak = false, renew = vi.fn(), basic = { headers: { Authorization: 'Basic abc' } }, } = {}) { vi.resetModules(); vi.doMock('@/utils/auth/Auth', () => ({ makeBasicAuthHeaders: vi.fn(() => basic) })); vi.doMock('@/utils/auth/OidcAuth', () => ({ isOidcEnabled: vi.fn(() => oidc), getOidcAuth: vi.fn(() => ({ renewForApiRequest: renew })), })); vi.doMock('@/utils/auth/KeycloakAuth', () => ({ isKeycloakEnabled: vi.fn(() => keycloak), getKeycloakAuth: vi.fn(() => ({ renewForApiRequest: renew })), })); vi.doMock('@/utils/logging/CoolConsole', () => ({ statusMsg: vi.fn(), statusErrorMsg: vi.fn(), })); return (await import('@/utils/request')).default; } describe('request auth headers', () => { beforeEach(() => { vi.restoreAllMocks(); localStorage.getItem.mockReset(); global.fetch = vi.fn(() => Promise.resolve(new Response('{"ok":true}', { status: 200 }))); }); afterEach(() => { vi.doUnmock('@/utils/auth/Auth'); vi.doUnmock('@/utils/auth/OidcAuth'); vi.doUnmock('@/utils/auth/KeycloakAuth'); vi.doUnmock('@/utils/logging/CoolConsole'); }); it('keeps Basic fallback for non-SSO local requests', async () => { localStorage.getItem.mockReturnValue(null); const request = await loadRequest(); await request.get('/status-check'); expect(fetch).toHaveBeenCalledWith('/status-check', expect.objectContaining({ headers: { Authorization: 'Basic abc' }, })); }); it('does not fall back to Basic when OIDC is enabled and bearer is unusable', async () => { localStorage.getItem.mockReturnValue(expiredToken()); const request = await loadRequest({ oidc: true }); await request.get('/status-check'); expect(fetch).toHaveBeenCalledWith('/status-check', expect.objectContaining({ headers: {}, })); }); it('sends bearer for SSO when a usable token exists', async () => { const token = goodToken(); localStorage.getItem.mockReturnValue(token); const request = await loadRequest({ oidc: true }); await request.get('/status-check'); expect(fetch).toHaveBeenCalledWith('/status-check', expect.objectContaining({ headers: { Authorization: `Bearer ${token}` }, })); }); it('renews once and retries a local SSO request after 401', async () => { let token = expiredToken(); const fresh = goodToken(); localStorage.getItem.mockImplementation(() => token); fetch .mockResolvedValueOnce(new Response('Unauthorized', { status: 401 })) .mockResolvedValueOnce(new Response('{"ok":true}', { status: 200 })); const renew = vi.fn(async () => { token = fresh; return true; }); const request = await loadRequest({ oidc: true, renew }); await request.get('/status-check'); expect(renew).toHaveBeenCalledTimes(1); expect(fetch).toHaveBeenNthCalledWith(1, '/status-check', expect.objectContaining({ headers: {}, })); expect(fetch).toHaveBeenNthCalledWith(2, '/status-check', expect.objectContaining({ headers: { Authorization: `Bearer ${fresh}` }, })); }); it('does not renew again when the retried request still returns 401', async () => { localStorage.getItem.mockImplementation(() => expiredToken()); fetch .mockResolvedValueOnce(new Response('Unauthorized', { status: 401 })) .mockResolvedValueOnce(new Response('Unauthorized', { status: 401 })); const renew = vi.fn(async () => true); const request = await loadRequest({ oidc: true, renew }); await expect(request.get('/status-check')).rejects.toThrow(); expect(renew).toHaveBeenCalledTimes(1); expect(fetch).toHaveBeenCalledTimes(2); }); it('does not retry when renewal fails', async () => { localStorage.getItem.mockImplementation(() => expiredToken()); fetch.mockResolvedValueOnce(new Response('Unauthorized', { status: 401 })); const renew = vi.fn(async () => false); const request = await loadRequest({ oidc: true, renew }); await expect(request.get('/status-check')).rejects.toThrow(); expect(renew).toHaveBeenCalledTimes(1); expect(fetch).toHaveBeenCalledTimes(1); }); it('never attaches a bearer or renews for external requests', async () => { localStorage.getItem.mockReturnValue(goodToken()); const renew = vi.fn(); fetch.mockResolvedValueOnce(new Response('Unauthorized', { status: 401 })); const request = await loadRequest({ oidc: true, renew }); await expect(request.get('https://api.example.com/data')).rejects.toThrow(); expect(renew).not.toHaveBeenCalled(); expect(fetch).toHaveBeenCalledWith('https://api.example.com/data', expect.objectContaining({ headers: {}, })); }); });