93 lines
3.2 KiB
TypeScript
93 lines
3.2 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { installOpsBrowserBoundary } from '@/lib/ops/api-fetch';
|
|
|
|
const AUTH_STORAGE_KEY = 'works-square.operations.auth.v1';
|
|
|
|
describe('installOpsBrowserBoundary', () => {
|
|
let nativeFetch: ReturnType<typeof vi.fn>;
|
|
let cleanup: (() => void) | undefined;
|
|
|
|
beforeEach(() => {
|
|
vi.stubEnv('NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE', 'ops');
|
|
vi.stubEnv('NEXT_PUBLIC_OPENMAIC_BASE_PATH', '/learning-ops');
|
|
window.localStorage.clear();
|
|
nativeFetch = vi.fn(async () => new Response(null, { status: 204 }));
|
|
window.fetch = nativeFetch as unknown as typeof window.fetch;
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup?.();
|
|
cleanup = undefined;
|
|
window.localStorage.clear();
|
|
vi.unstubAllEnvs();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('maps root-relative API requests to the Ops basePath and adds the Works bearer token', async () => {
|
|
window.localStorage.setItem(
|
|
AUTH_STORAGE_KEY,
|
|
JSON.stringify({
|
|
accessToken: 'works-access-token',
|
|
expiresAt: Date.now() + 60_000,
|
|
}),
|
|
);
|
|
cleanup = installOpsBrowserBoundary();
|
|
|
|
await window.fetch('/api/server-providers', {
|
|
method: 'POST',
|
|
headers: { 'X-Request-Id': 'request-1' },
|
|
});
|
|
|
|
expect(nativeFetch).toHaveBeenCalledTimes(1);
|
|
const [input, init] = nativeFetch.mock.calls[0] as [RequestInfo | URL, RequestInit];
|
|
expect(input).toBe('/learning-ops/api/server-providers');
|
|
expect(init.method).toBe('POST');
|
|
const headers = new Headers(init.headers);
|
|
expect(headers.get('Authorization')).toBe('Bearer works-access-token');
|
|
expect(headers.get('X-Request-Id')).toBe('request-1');
|
|
});
|
|
|
|
it('leaves external URLs and unrelated root-relative paths unchanged', async () => {
|
|
cleanup = installOpsBrowserBoundary();
|
|
|
|
await window.fetch('https://example.test/api/courses');
|
|
await window.fetch('/courses/course-1', { method: 'GET' });
|
|
|
|
expect(nativeFetch).toHaveBeenNthCalledWith(1, 'https://example.test/api/courses', {});
|
|
expect(nativeFetch).toHaveBeenNthCalledWith(2, '/courses/course-1', { method: 'GET' });
|
|
});
|
|
|
|
it('restores unwrapped fetch behavior when cleaned up', async () => {
|
|
cleanup = installOpsBrowserBoundary();
|
|
const wrappedFetch = window.fetch;
|
|
|
|
cleanup();
|
|
cleanup = undefined;
|
|
|
|
expect(window.fetch).not.toBe(wrappedFetch);
|
|
await window.fetch('/api/server-providers');
|
|
expect(nativeFetch).toHaveBeenCalledTimes(1);
|
|
expect(nativeFetch.mock.calls[0]?.[0]).toBe('/api/server-providers');
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
label: 'expired session',
|
|
value: JSON.stringify({ accessToken: 'expired-token', expiresAt: Date.now() - 1 }),
|
|
},
|
|
{ label: 'malformed session JSON', value: '{not-json' },
|
|
{ label: 'session without a string token', value: JSON.stringify({ accessToken: 123 }) },
|
|
])('does not add Authorization for a $label', async ({ value }) => {
|
|
window.localStorage.setItem(AUTH_STORAGE_KEY, value);
|
|
cleanup = installOpsBrowserBoundary();
|
|
|
|
await window.fetch('/api/server-providers');
|
|
|
|
const [, init] = nativeFetch.mock.calls[0] as [RequestInfo | URL, RequestInit];
|
|
expect(new Headers(init.headers).has('Authorization')).toBe(false);
|
|
});
|
|
});
|