167 lines
4.1 KiB
JavaScript
167 lines
4.1 KiB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { apiRequest, buildKitchenApiUrl, getPath } from '../../src/api/client.js';
|
|
|
|
function createStore(overrides = {}) {
|
|
return {
|
|
config: {
|
|
baseUrl: '',
|
|
database: 'kitchen-db',
|
|
...(overrides.config || {}),
|
|
},
|
|
session: {
|
|
applicationKey: 'app-key',
|
|
hasValidated: false,
|
|
state: 'pending_validation',
|
|
...(overrides.session || {}),
|
|
},
|
|
activeKitchen: {
|
|
id: 'kitchen-1',
|
|
...(overrides.activeKitchen || {}),
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('api/client', () => {
|
|
let authFailureSpy;
|
|
|
|
beforeEach(() => {
|
|
authFailureSpy = vi.fn();
|
|
globalThis.window = {
|
|
location: {
|
|
origin: 'https://app.local',
|
|
},
|
|
__loncApp: {
|
|
handleAuthFailure: authFailureSpy,
|
|
},
|
|
};
|
|
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
vi.unstubAllGlobals();
|
|
delete globalThis.window;
|
|
});
|
|
|
|
it('returns configured path constants', () => {
|
|
expect(getPath('items')).toBe('kitchen/items');
|
|
expect(getPath('userApplication')).toBe('user/application/');
|
|
expect(getPath('changes')).toBe('kitchen/changes');
|
|
});
|
|
|
|
it('builds database-scoped kitchen urls with encoded query values', () => {
|
|
const store = createStore({
|
|
config: {
|
|
baseUrl: 'https://api.example.com',
|
|
database: 'my db',
|
|
},
|
|
});
|
|
|
|
const url = buildKitchenApiUrl(store, 'kitchen/items/grouped', {
|
|
search_name: 'Milk + eggs',
|
|
expanded: true,
|
|
ignored: '',
|
|
});
|
|
|
|
expect(url).toBe(
|
|
'https://api.example.com/my%20db/kitchen/items/grouped?search_name=Milk+%2B+eggs&expanded=true',
|
|
);
|
|
});
|
|
|
|
it('sends json request data and auth header through fetch', async () => {
|
|
const store = createStore();
|
|
const fetchSpy = vi.fn(async () =>
|
|
new Response(JSON.stringify({ ok: true }), {
|
|
status: 200,
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
},
|
|
}),
|
|
);
|
|
vi.stubGlobal('fetch', fetchSpy);
|
|
|
|
const payload = await apiRequest(store, getPath('items'), {
|
|
method: 'POST',
|
|
body: {
|
|
name: 'Rice',
|
|
},
|
|
query: {
|
|
label: true,
|
|
},
|
|
});
|
|
|
|
expect(payload).toEqual({ ok: true });
|
|
|
|
const [url, request] = fetchSpy.mock.calls[0];
|
|
expect(url).toBe('/kitchen-db/kitchen/items?label=true');
|
|
expect(request.method).toBe('POST');
|
|
expect(request.body).toBe('{"name":"Rice"}');
|
|
expect(request.headers.get('Accept')).toBe('application/json');
|
|
expect(request.headers.get('Content-Type')).toBe('application/json');
|
|
expect(request.headers.get('Authorization')).toBe('Bearer app-key');
|
|
});
|
|
|
|
it('normalizes api error payload into ApiRequestError details', async () => {
|
|
const store = createStore();
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () =>
|
|
new Response(
|
|
JSON.stringify({
|
|
errors: {
|
|
name: ['Required'],
|
|
quantity: 'Must be positive',
|
|
},
|
|
}),
|
|
{
|
|
status: 400,
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
},
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
await expect(apiRequest(store, getPath('items'))).rejects.toMatchObject({
|
|
name: 'ApiRequestError',
|
|
status: 400,
|
|
details: {
|
|
name: 'Required',
|
|
quantity: 'Must be positive',
|
|
},
|
|
});
|
|
});
|
|
|
|
it('triggers auth failure handler after validated session receives auth errors', async () => {
|
|
const store = createStore({
|
|
session: {
|
|
applicationKey: 'app-key',
|
|
hasValidated: true,
|
|
state: 'connected',
|
|
},
|
|
});
|
|
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () =>
|
|
new Response(JSON.stringify({ detail: 'Unauthorized' }), {
|
|
status: 401,
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
},
|
|
}),
|
|
),
|
|
);
|
|
|
|
await expect(apiRequest(store, getPath('items'))).rejects.toMatchObject({
|
|
name: 'ApiRequestError',
|
|
status: 401,
|
|
});
|
|
|
|
expect(authFailureSpy).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|