import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { CustomModuleRequestError, checkCustomModuleUrl, createCustomModule, deleteCustomModule, getCustomModule, listCustomModules, setCustomModulePreload, updateCustomModule, } from './custom-modules-api'; const { mockFetch } = vi.hoisted(() => ({ mockFetch: vi.fn() })); beforeEach(() => { mockFetch.mockReset(); vi.stubGlobal('fetch', mockFetch); }); afterEach(() => { vi.unstubAllGlobals(); }); describe('checkCustomModuleUrl', () => { it('nimmt eine https-Adresse an', () => { expect(checkCustomModuleUrl('https://a.de')).toBe('ok'); }); it('lehnt http und Unparsbares als notHttps ab', () => { expect(checkCustomModuleUrl('http://a.de')).toBe('notHttps'); expect(checkCustomModuleUrl('kaputt')).toBe('notHttps'); expect(checkCustomModuleUrl('javascript:alert(1)')).toBe('notHttps'); expect(checkCustomModuleUrl('')).toBe('notHttps'); }); it('erkennt Zugangsdaten in der Adresse', () => { expect(checkCustomModuleUrl('https://u:p@a.de')).toBe('credentials'); expect(checkCustomModuleUrl('https://u@a.de')).toBe('credentials'); }); }); describe('custom-modules-api', () => { it('listCustomModules ruft GET /custom-modules mit credentials include', async () => { mockFetch.mockResolvedValue(new Response(JSON.stringify([{ id: 'a' }]), { status: 200 })); const list = await listCustomModules(); expect(list).toEqual([{ id: 'a' }]); const [url, init] = mockFetch.mock.calls[0]; expect(String(url)).toMatch(/\/custom-modules$/); expect(init.credentials).toBe('include'); }); it('getCustomModule liefert null bei 404', async () => { mockFetch.mockResolvedValue(new Response('{}', { status: 404 })); await expect(getCustomModule('x')).resolves.toBeNull(); }); it('getCustomModule liefert die Zeile bei 200', async () => { mockFetch.mockResolvedValue(new Response(JSON.stringify({ id: 'x' }), { status: 200 })); await expect(getCustomModule('x')).resolves.toEqual({ id: 'x' }); }); it('createCustomModule schickt POST mit JSON und wirft bei Fehler mit Servermeldung', async () => { mockFetch.mockResolvedValue( new Response(JSON.stringify({ message: ['Nur https'] }), { status: 400 }), ); const input = { name: 'a', url: 'http://a.de', category: 'fleet' }; const err = await createCustomModule(input).catch((e) => e); expect(err).toBeInstanceOf(CustomModuleRequestError); expect(err.status).toBe(400); expect(err.message).toBe('Nur https'); const [, init] = mockFetch.mock.calls[0]; expect(init.method).toBe('POST'); expect(JSON.parse(init.body)).toEqual(input); }); it('updateCustomModule schickt PATCH, deleteCustomModule DELETE', async () => { mockFetch.mockResolvedValue(new Response('{}', { status: 200 })); await updateCustomModule('x', { name: 'n' }); expect(mockFetch.mock.calls[0][1].method).toBe('PATCH'); expect(String(mockFetch.mock.calls[0][0])).toMatch(/\/custom-modules\/x$/); await deleteCustomModule('x'); expect(mockFetch.mock.calls[1][1].method).toBe('DELETE'); }); }); describe('setCustomModulePreload (quick-261008-who)', () => { it('ruft PUT /custom-modules/:id/preload mit JSON-Koerper und credentials include', async () => { mockFetch.mockResolvedValue({ ok: true, status: 200, json: async () => ({ id: 'a b', preload: true, preloadCount: 1 }), }); const res = await setCustomModulePreload('a b', true); expect(res).toEqual({ id: 'a b', preload: true, preloadCount: 1 }); const [url, init] = mockFetch.mock.calls[0]; expect(String(url)).toMatch(/\/custom-modules\/a%20b\/preload$/); expect(init.method).toBe('PUT'); expect(init.credentials).toBe('include'); expect(JSON.parse(init.body)).toEqual({ preload: true }); }); it('409 bleibt als CustomModuleRequestError mit Status erkennbar', async () => { mockFetch.mockResolvedValue({ ok: false, status: 409, json: async () => ({ message: 'Grenze' }), }); await expect(setCustomModulePreload('a', true)).rejects.toMatchObject({ status: 409, message: 'Grenze', }); await expect(setCustomModulePreload('a', true)).rejects.toBeInstanceOf( CustomModuleRequestError, ); }); });