UNPKG

@equinor/fusion-framework-vite-plugin-api-service

Version:

Vite plugin for proxying service discovery and mocking

239 lines (196 loc) 7.36 kB
import { describe, it, expect, afterAll, vi, afterEach } from 'vitest'; import { createServer, type ViteDevServer } from 'vite'; import nock from 'nock'; import { createProxyHandler, DEFAULT_VALUES, plugin as apiServicePlugin, type ApiRoute, } from '../src'; describe('API Service Vite Plugin - Middleware Route', () => { let server: ViteDevServer | undefined; const startServer = async (plugin) => { server = await createServer({ plugins: [plugin], server: { port: 0, // Dynamic port assignment host: 'localhost', // Explicitly bind to localhost }, }); await server.listen(); return { serverUrl: `http://localhost:${server.config.server.port}`, }; }; // Clean up after all tests afterEach(async () => { await server?.close(); nock.cleanAll(); }); describe('Routing', () => { it('should return mocked response for <default_api_proxy>/api/mock', async () => { const mockData = { message: 'Mocked response' }; const { serverUrl } = await startServer( apiServicePlugin({ routes: [ { match: '/api/mock', middleware: (req, res) => { expect(req.url).toBe('/api/mock'); res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(mockData)); }, }, ], }), ); const response = await fetch(new URL(`${DEFAULT_VALUES.API_PATH}/api/mock`, serverUrl)); const data = await response.json(); expect(response.status).toBe(200); expect(data).toEqual(mockData); }); it('should proxy request to external service', async () => { const mockData = { id: 1, title: 'Mocked proxied response' }; nock('http://localhost:3001').get('/api/posts').reply(200, mockData); const { serverUrl } = await startServer( apiServicePlugin({ routes: [ { match: '/api/posts', proxy: { target: 'http://localhost:3001', }, }, ], }), ); const response = await fetch(new URL(`${DEFAULT_VALUES.API_PATH}/api/posts`, serverUrl)); const data = await response.json(); expect(response.status).toBe(200); expect(data).toEqual(mockData); }); it('should transform proxy response', async () => { const mockData = { id: 1, title: 'Mocked proxied response' }; nock('http://localhost:3001').get('/api/posts').reply(200, mockData); const responseTransformerMock = vi.fn((data) => { return { ...data, processed: true }; }); const { serverUrl } = await startServer( apiServicePlugin({ routes: [ { match: '/api/posts', proxy: { target: 'http://localhost:3001', transformResponse: responseTransformerMock, }, }, ], }), ); const response = await fetch(new URL(`${DEFAULT_VALUES.API_PATH}/api/posts`, serverUrl)); expect(responseTransformerMock).toHaveBeenCalledTimes(1); const data = await response.json(); expect(response.status).toBe(200); expect(data).toEqual({ ...mockData, processed: true }); }); }); describe('Proxy handler', () => { type Service = { name: string; uri: string }; const serviceDiscoveryUrl = 'http://localhost:666'; const serviceProxyRoute = DEFAULT_VALUES.API_PATH; const setupMockServices = (number: number): Array<Service> => { const mockServices: Array<Service> = Array.from({ length: number }).map((_, index) => ({ name: `api_${index}`, uri: `http://localhost:300${index}`, })); // set up mock service discovery nock(serviceDiscoveryUrl).get('/services').reply(200, mockServices); // set up mock data for each service for (const service of mockServices) { nock(service.uri).get('/api/posts').reply(200, { service: service.name }); } return mockServices; }; const serviceDiscoveryHandler = (data: Service[]) => { const routes = [] as ApiRoute[]; const services = [] as Service[]; for (const service of data) { const url = new URL(service.uri); const proxyUrl = `${serviceProxyRoute}/${service.name}`; services.push({ ...service, uri: proxyUrl }); routes.push({ match: (path) => path.startsWith(`/${service.name}`), proxy: { target: url.origin, rewrite: (path) => { return path.replace(`/${service.name}`, ''); }, }, }); } return { data: services, routes }; }; it('should generate routes from proxy handler', async () => { const numberOfMockServices = 3; // Mock services setupMockServices(numberOfMockServices); const { serverUrl } = await startServer( apiServicePlugin({ proxyHandler: createProxyHandler(serviceDiscoveryUrl, serviceDiscoveryHandler), }), ); const services = await fetch( new URL(`${DEFAULT_VALUES.SERVICES_PATH}/services`, serverUrl), ).then((res) => res.json()); expect(services.length).toBe(numberOfMockServices); for (const service of services) { const endpoint = new URL(`${service.uri}/api/posts`, serverUrl); expect(endpoint).toMatchObject({ pathname: `${serviceProxyRoute}/${service.name}/api/posts`, }); const response = await fetch(endpoint).then((res) => res.json()); expect(response).toEqual({ service: service.name }); } }); it('should allow setting proxy route', async () => { const proxyRoute = '/@custom-services'; setupMockServices(1); const { serverUrl } = await startServer( apiServicePlugin({ proxyHandler: createProxyHandler(serviceDiscoveryUrl, serviceDiscoveryHandler, { route: proxyRoute, }), }), ); const request = fetch(new URL(`${proxyRoute}/services`, serverUrl)); await expect(request).resolves.toMatchObject({ status: 200 }); }); it('should allow adding route configuration', async () => { const mockProxyRoutes = setupMockServices(1); const { serverUrl } = await startServer( apiServicePlugin({ proxyHandler: createProxyHandler(serviceDiscoveryUrl, serviceDiscoveryHandler), routes: [ { match: '/api/posts/:id', middleware: (req, res) => { res.end(`Custom route ${req.params?.id}`); }, }, ], }), ); // prime discovery await fetch(new URL(`${DEFAULT_VALUES.SERVICES_PATH}/services`, serverUrl)); const request = fetch( new URL(`${serviceProxyRoute}/${mockProxyRoutes[0].name}/api/posts`, serverUrl), ).then((res) => res.json()); await expect(request).resolves.toMatchObject({ service: mockProxyRoutes[0].name }); const customRoute = fetch(new URL(`${DEFAULT_VALUES.API_PATH}/api/posts/99`, serverUrl)).then( (res) => res.text(), ); expect(await customRoute).toBe('Custom route 99'); }); }); });