From 99b08acf2d53b8f8108df32f92b3a4ec0c7a34e9 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Sat, 9 Mar 2024 18:30:40 +0100 Subject: [PATCH 01/20] replace elysia with hono and zod --- bun.lockb | Bin 196805 -> 193485 bytes package.json | 8 +++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bun.lockb b/bun.lockb index 7f4b0b50710165d0cb05e36ca64bc86ac235b791..551128b002dc0eff3ffa293e66bdb070a13a4155 100755 GIT binary patch delta 38335 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 E06_YRz5oCK diff --git a/package.json b/package.json index 24b46b1..34da0f4 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,12 @@ "release": "bunx np" }, "dependencies": { - "@elysiajs/eden": "0.8.1", - "elysia": "0.8.17", + "@hono/zod-validator": "0.2.0", + "hono": "4.0.10", "minimatch": "9.0.3", "pino": "8.19.0", - "pino-pretty": "10.3.1" + "pino-pretty": "10.3.1", + "zod": "3.22.4" }, "devDependencies": { "bun-plugin-dts": "^0.2.1", @@ -43,6 +44,7 @@ "@typescript-eslint/object-curly-spacing": "off", "@typescript-eslint/naming-convention": "off", "new-cap": "off", + "unicorn/prevent-abbreviations": "off", "import/extensions": [ "error", "ignorePackages", From 2293d54589e5571330b624b35d5f39631d961c1f Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Sat, 9 Mar 2024 18:32:00 +0100 Subject: [PATCH 02/20] convert typebox schemas to zod --- src/schemas.ts | 42 +++++++++++++++++++++--------------------- src/types.ts | 6 +++--- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/schemas.ts b/src/schemas.ts index fb95946..89ed93d 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -1,26 +1,26 @@ -import { t } from 'elysia'; +import { z } from 'zod'; -export const MockedRouteDto = t.Object({ - pathPattern: t.String({ minLength: 1 }), - method: t.Optional( - t.Union([ - t.Literal('GET'), - t.Literal('POST'), - t.Literal('PUT'), - t.Literal('DELETE'), - t.Literal('PATCH'), - t.Literal('OPTIONS'), - t.Literal('HEAD'), - t.Literal('CONNECT'), - t.Literal('TRACE'), - t.Literal('ALL'), +export const MockedRouteDto = z.object({ + pathPattern: z.string().min(1), + method: z.optional( + z.union([ + z.literal('GET'), + z.literal('POST'), + z.literal('PUT'), + z.literal('DELETE'), + z.literal('PATCH'), + z.literal('OPTIONS'), + z.literal('HEAD'), + z.literal('CONNECT'), + z.literal('TRACE'), + z.literal('ALL'), ]), ), - response: t.Unknown(), - status: t.Optional(t.Number()), - headers: t.Optional(t.Record(t.String(), t.String())), - contentType: t.Optional(t.String()), - delay: t.Optional(t.Number()), + response: z.unknown(), + status: z.optional(z.number()), + headers: z.optional(z.record(z.string(), z.string())), + contentType: z.optional(z.string()), + delay: z.optional(z.number()), }); -export const MockedRoutesDto = t.Array(MockedRouteDto); +export const MockedRoutesDto = z.array(MockedRouteDto); diff --git a/src/types.ts b/src/types.ts index 2da51e0..75915d0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,5 @@ -import { type Static } from 'elysia'; +import type { z } from 'zod'; import { type MockedRouteDto, type MockedRoutesDto } from './schemas'; -export type MockedRoute = Static; -export type MockedRoutes = Static; +export type MockedRoute = z.infer; +export type MockedRoutes = z.infer; From 9d9c0657526c4e0f53453d095021a736328c3abf Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Sat, 9 Mar 2024 19:00:05 +0100 Subject: [PATCH 03/20] rewrite server from elysia to hono --- src/index.ts | 154 +++++++++++++++++++++++++++------------------------ 1 file changed, 83 insertions(+), 71 deletions(-) diff --git a/src/index.ts b/src/index.ts index d2d44ff..17ffdfb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,7 @@ -import { Elysia, t } from 'elysia'; +import { Hono } from 'hono'; +import { type StatusCode } from 'hono/utils/http-status'; +import { z } from 'zod'; +import { zValidator } from '@hono/zod-validator'; import { logger } from './logger'; import { addDefaultMockedRoutes, @@ -11,75 +14,76 @@ import { import { DEFAULT_SCOPE, MOCK_MIRROR_HEADER } from './const'; import { MockedRoutesDto } from './schemas'; -export const app = new Elysia() - - .get('/', () => "Hello 👋, I'm Mock Mirror") - - .group('/mock-mirror', (mockMirror) => - mockMirror - .post('/reset', () => { - resetRegistry(); - - logger.info('Registry has been reset'); - - return { - success: true, - }; - }) - - .post( - '/add', - ({ body }) => { - if (body.scope) { - addMockedRoutes({ - scope: body.scope, - routes: body.routes, - }); - } else { - addDefaultMockedRoutes(body.routes); - } - - return { - success: true, - }; - }, - { - body: t.Object({ - scope: t.Optional(t.String()), - routes: MockedRoutesDto, - }), - }, - ) - - .post( - '/clear-scope', - ({ body }) => { - clearScope(body.scope); - - logger.info(`Cleared scope: ${body.scope}`); - - return { - success: true, - }; - }, - { - body: t.Object({ - scope: t.String(), - }), - }, - ) - - .get('/stats', stats), +const mockMirrorRoutes = new Hono() + .post('/reset', (ctx) => { + resetRegistry(); + + logger.info('Registry has been reset'); + + return ctx.json({ + success: true, + }); + }) + .post( + '/add', + zValidator( + 'json', + z.object({ + scope: z.string().optional(), + routes: MockedRoutesDto, + }), + ), + (ctx) => { + const body = ctx.req.valid('json'); + + if (body.scope) { + addMockedRoutes({ + scope: body.scope, + routes: body.routes, + }); + } else { + addDefaultMockedRoutes(body.routes); + } + + return ctx.json({ + success: true, + }); + }, ) + .post( + '/clear-scope', + zValidator( + 'json', + z.object({ + scope: z.string(), + }), + ), + (ctx) => { + const body = ctx.req.valid('json'); + + clearScope(body.scope); + + logger.info(`Cleared scope: ${body.scope}`); + + return ctx.json({ + success: true, + }); + }, + ) + .get('/stats', (ctx) => ctx.json(stats())); - .all('*', async ({ path, headers, set, request }) => { - const scope = headers[MOCK_MIRROR_HEADER] ?? DEFAULT_SCOPE; +const app = new Hono() + .get('/', (ctx) => ctx.text("Hello 👋, I'm Mock Mirror")) + .route('/mock-mirror', mockMirrorRoutes) + .all('*', async (ctx) => { + const scope = ctx.req.header(MOCK_MIRROR_HEADER) ?? DEFAULT_SCOPE; + const { path, method } = ctx.req; if (!scope) { logger.warn(`No Mock Mirror scope header provided for: ${path}`); } - const found = findMatchingRoute({ scope, path, method: request.method }); + const found = findMatchingRoute({ scope, path, method }); if (found) { const headers = found.route.headers ?? {}; @@ -93,16 +97,24 @@ export const app = new Elysia() }); } - set.status = found.route.status; - set.headers = headers; + ctx.status((found.route.status ?? 200) as StatusCode); + + for (const [key, value] of Object.entries(headers)) { + ctx.header(key, value); + } - return found.route.response; + ctx.body('found.route.response'); } - set.status = 'Not Found'; - return 'Not Found'; - }) + ctx.status(404); + ctx.body('Not Found'); + }); + +logger.info(`🪞 Mock Mirror is running at http://localhost:${Bun.env.PORT ?? 3210}`); - .listen(Bun.env.PORT ?? 3210); +export default { + port: Bun.env.PORT ?? 3210, + fetch: app.fetch, +}; -logger.info(`🪞 Mock Mirror is running at ${app.server?.hostname}:${app.server?.port}`); +export type MockMirrorRoutesType = typeof mockMirrorRoutes; From 8e8037c5ed3df4873aa05fe9fca18bb5479471f4 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Sat, 9 Mar 2024 19:00:19 +0100 Subject: [PATCH 04/20] update dev script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 34da0f4..1ea5724 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test": "bun test", "lint": "xo", "build": "bunx tsc", - "dev": "bun run --watch src/index.ts", + "dev": "bun run --hot src/index.ts", "release": "bunx np" }, "dependencies": { @@ -45,6 +45,7 @@ "@typescript-eslint/naming-convention": "off", "new-cap": "off", "unicorn/prevent-abbreviations": "off", + "import/no-anonymous-default-export": "off", "import/extensions": [ "error", "ignorePackages", From 3a36c375f419003e86ec01440d667338519de077 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Sat, 9 Mar 2024 19:00:35 +0100 Subject: [PATCH 05/20] rewrite client to use hono client --- src/client.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/client.ts b/src/client.ts index 2623d28..eddf2bb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'node:crypto'; -import { edenTreaty } from '@elysiajs/eden'; +import { hc } from 'hono/client'; import type { MockedRoute, MockedRoutes } from './types'; -import type { app } from '.'; +import type { MockMirrorRoutesType } from '.'; export const createMockMirror = ({ mockMirrorUrl, @@ -10,24 +10,24 @@ export const createMockMirror = ({ mockMirrorUrl?: string; defaultRoutes?: MockedRoutes; }) => { - const api = edenTreaty(mockMirrorUrl ?? Bun.env.MOCK_MIRROR_URL ?? 'http://localhost:3210', {}); + const client = hc(mockMirrorUrl ?? Bun.env.MOCK_MIRROR_URL ?? 'http://localhost:3210'); if (defaultRoutes) { - void api['mock-mirror'].add.post({ routes: defaultRoutes }); + void client.add.$post({ json: { routes: defaultRoutes } }); } const getTools = ({ scope }: { scope: string }) => ({ async addRoute(route: MockedRoute) { - return api['mock-mirror'].add.post({ scope, routes: [route] }); + return client.add.$post({ json: { scope, routes: [route] } }); }, async addRoutes(routes: MockedRoutes) { - return api['mock-mirror'].add.post({ scope, routes }); + return client.add.$post({ json: { scope, routes } }); }, async clearScope() { - return api['mock-mirror']['clear-scope'].post({ scope }); + return client['clear-scope'].$post({ json: { scope } }); }, async reset() { - return api['mock-mirror'].reset.post(); + return client.reset.$post(); }, scope, }); From 9a505e8e77b4e7bc194b57908c3ff054dbda1b14 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Sun, 10 Mar 2024 09:07:52 +0100 Subject: [PATCH 06/20] export whole client --- src/client.ts | 17 ++++++++++------- src/index.ts | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/client.ts b/src/client.ts index eddf2bb..4073535 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,33 +1,36 @@ import { randomUUID } from 'node:crypto'; +import type { ClientRequestOptions } from 'hono'; import { hc } from 'hono/client'; import type { MockedRoute, MockedRoutes } from './types'; -import type { MockMirrorRoutesType } from '.'; +import type { App } from '.'; export const createMockMirror = ({ mockMirrorUrl, defaultRoutes, + options, }: { mockMirrorUrl?: string; defaultRoutes?: MockedRoutes; + options?: ClientRequestOptions; }) => { - const client = hc(mockMirrorUrl ?? Bun.env.MOCK_MIRROR_URL ?? 'http://localhost:3210'); + const client = hc(mockMirrorUrl ?? Bun.env.MOCK_MIRROR_URL ?? 'http://localhost:3210', options); if (defaultRoutes) { - void client.add.$post({ json: { routes: defaultRoutes } }); + void client['mock-mirror'].add.$post({ json: { routes: defaultRoutes } }); } const getTools = ({ scope }: { scope: string }) => ({ async addRoute(route: MockedRoute) { - return client.add.$post({ json: { scope, routes: [route] } }); + return client['mock-mirror'].add.$post({ json: { scope, routes: [route] } }); }, async addRoutes(routes: MockedRoutes) { - return client.add.$post({ json: { scope, routes } }); + return client['mock-mirror'].add.$post({ json: { scope, routes } }); }, async clearScope() { - return client['clear-scope'].$post({ json: { scope } }); + return client['mock-mirror']['clear-scope'].$post({ json: { scope } }); }, async reset() { - return client.reset.$post(); + return client['mock-mirror'].reset.$post(); }, scope, }); diff --git a/src/index.ts b/src/index.ts index 17ffdfb..1064d7b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -117,4 +117,4 @@ export default { fetch: app.fetch, }; -export type MockMirrorRoutesType = typeof mockMirrorRoutes; +export type App = typeof app; From 767b8ce9f7fc252611c9543fea74d9ae409a7dbb Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Sun, 10 Mar 2024 09:08:27 +0100 Subject: [PATCH 07/20] fix client tests --- src/client.test.ts | 99 ++++++++++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/src/client.test.ts b/src/client.test.ts index 6eac565..c7d28d5 100644 --- a/src/client.test.ts +++ b/src/client.test.ts @@ -1,33 +1,50 @@ import { beforeEach, describe, expect, it } from 'bun:test'; -import { Elysia } from 'elysia'; +import { Hono } from 'hono'; +import { testClient } from 'hono/testing'; +import type { StatusCode } from 'hono/utils/http-status'; import { MOCK_MIRROR_HEADER } from './const'; import { createMockMirror } from './client'; -import { app } from '.'; +import server, { app } from '.'; -const serverUrl = `http://localhost:${app.server?.port}`; +const serverUrl = `http://localhost:3210`; -const mockMirror = await createMockMirror({ mockMirrorUrl: serverUrl }); +Bun.serve({ + ...server, + port: 3210, +}); + +void testClient(app)['mock-mirror'].add.$post({ + json: { + scope: 'scope-one', + routes: [ + { + pathPattern: '/api/users/ffff', + method: 'GET', + response: 'any user', + status: 200, + }, + ], + }, +}); + +const mockMirror = createMockMirror({ + mockMirrorUrl: serverUrl, +}); const testServiceUrl = 'http://this-service-does-not-exist.local'; -const testBackend = new Elysia() - .derive(({ headers: originalHeaders }) => { - const headers = originalHeaders as Record; +const testBackend = new Hono().get('/api/users/:id', async (ctx) => { + const headers = ctx.req.raw.headers; + const hasMockMirrorScope = ctx.req.header(MOCK_MIRROR_HEADER); - const api = headers[MOCK_MIRROR_HEADER] - ? async (url: string) => fetch(`${serverUrl}${url}`, { headers }) // This is the mock - : async (url: string) => fetch(`${testServiceUrl}${url}`, { headers }); // This would be the actual service, but we're mocking it + const api = hasMockMirrorScope + ? async (url: string) => fetch(`${serverUrl}${url}`, { headers }) // This is the mock + : async (url: string) => fetch(`${testServiceUrl}${url}`, { headers }); // This would be the actual service, but we're mocking it - return { - api, - }; - }) - .get('/api/users/:id', async ({ params, api }) => { - return api(`/api/users/${params.id}`); - }) - .listen(9898); + const result = await api(`/api/users/${ctx.req.param('id')}`); -const testBackendUrl = `http://${testBackend.server?.hostname}:${testBackend.server?.port}`; + return ctx.body(await result.text(), result.status as StatusCode); +}); describe('client', () => { beforeEach(async () => { @@ -36,11 +53,14 @@ describe('client', () => { it('should fail if mocks are not provided', async () => { await mockMirror(async ({ scope }) => { - const response = await fetch(`${testBackendUrl}/api/users/777`, { - headers: { - [MOCK_MIRROR_HEADER]: scope, + const response = await testClient(testBackend).api.users[':id'].$get( + { param: { id: '777' } }, + { + headers: { + [MOCK_MIRROR_HEADER]: scope, + }, }, - }); + ); expect(response.status).toBe(404); }); @@ -53,11 +73,14 @@ describe('client', () => { response: 'This is a mock response for the user', }); - const response = await fetch(`${testBackendUrl}/api/users/777`, { - headers: { - [MOCK_MIRROR_HEADER]: scope, + const response = await testClient(testBackend).api.users[':id'].$get( + { param: { id: '777' } }, + { + headers: { + [MOCK_MIRROR_HEADER]: scope, + }, }, - }); + ); expect(await response.text()).toBe('This is a mock response for the user'); }); @@ -66,11 +89,14 @@ describe('client', () => { it('should be able to mock the service behind the backend on the go', async () => { await mockMirror(async ({ addRoute, scope }) => { { - const response = await fetch(`${testBackendUrl}/api/users/777`, { - headers: { - [MOCK_MIRROR_HEADER]: scope, + const response = await testClient(testBackend).api.users[':id'].$get( + { param: { id: '777' } }, + { + headers: { + [MOCK_MIRROR_HEADER]: scope, + }, }, - }); + ); expect(response.status).toBe(404); } @@ -81,11 +107,14 @@ describe('client', () => { response: 'This is a mock response for the user', }); - const response = await fetch(`${testBackendUrl}/api/users/777`, { - headers: { - [MOCK_MIRROR_HEADER]: scope, + const response = await testClient(testBackend).api.users[':id'].$get( + { param: { id: '777' } }, + { + headers: { + [MOCK_MIRROR_HEADER]: scope, + }, }, - }); + ); expect(await response.text()).toBe('This is a mock response for the user'); } From 585ae1d008d9b6a42523351e162d14fd5b4aec0b Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Sun, 10 Mar 2024 21:18:27 +0100 Subject: [PATCH 08/20] refactor tests --- src/client.test.ts | 20 +-- src/index.test.ts | 299 +++++++++++++++++++++++---------------------- 2 files changed, 153 insertions(+), 166 deletions(-) diff --git a/src/client.test.ts b/src/client.test.ts index c7d28d5..5f89555 100644 --- a/src/client.test.ts +++ b/src/client.test.ts @@ -4,27 +4,13 @@ import { testClient } from 'hono/testing'; import type { StatusCode } from 'hono/utils/http-status'; import { MOCK_MIRROR_HEADER } from './const'; import { createMockMirror } from './client'; -import server, { app } from '.'; +import server from '.'; -const serverUrl = `http://localhost:3210`; +const serverUrl = `http://localhost:3211`; Bun.serve({ ...server, - port: 3210, -}); - -void testClient(app)['mock-mirror'].add.$post({ - json: { - scope: 'scope-one', - routes: [ - { - pathPattern: '/api/users/ffff', - method: 'GET', - response: 'any user', - status: 200, - }, - ], - }, + port: 3211, }); const mockMirror = createMockMirror({ diff --git a/src/index.test.ts b/src/index.test.ts index bc2e415..bf29ee1 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,110 +1,107 @@ import { beforeEach, describe, expect, it } from 'bun:test'; -import { edenTreaty } from '@elysiajs/eden'; +import { testClient } from 'hono/testing'; import { MOCK_MIRROR_HEADER } from './const'; -import { app } from '.'; +import server, { app } from '.'; -const serverUrl = `http://localhost:${app.server?.port}`; -const api = edenTreaty(serverUrl); +const serverUrl = 'http://localhost:3212'; +Bun.serve({ + ...server, + port: 3212, +}); describe('server', () => { beforeEach(async () => { - await api['mock-mirror'].reset.post(); + await testClient(app)['mock-mirror'].reset.$post(); }); it('should return error if missing input for adding routes', async () => { - // @ts-expect-error input is omitted deliberately - const { data, error, status, response } = await api['mock-mirror'].add.post(); + const response = await testClient(app)['mock-mirror'].add.$post({ + // @ts-expect-error input is omitted deliberately + json: {}, + }); - expect(status).toBe(400); - expect(data).toInclude('Expected object'); + expect(await response.text()).toInclude('ZodError'); }); it('should add routes to scope if provided', async () => { - const { data } = await api['mock-mirror'].add.post({ scope: 'scope-one', routes: [] }); + const response = await testClient(app)['mock-mirror'].add.$post({ json: { scope: 'scope-one', routes: [] } }); - expect(data).toMatchSnapshot(); + expect(await response.json()).toMatchSnapshot(); }); it('should be able to respond with mock', async () => { { - const { data } = await api['mock-mirror'].add.post({ - scope: 'scope-one', - routes: [ - { - pathPattern: '/api/users/*', - method: 'GET', - response: 'any user', - status: 200, - }, - ], + const response = await testClient(app)['mock-mirror'].add.$post({ + json: { + scope: 'scope-one', + routes: [ + { + pathPattern: '/api/users/*', + method: 'GET', + response: 'any user', + status: 200, + }, + ], + }, }); - expect(data).toMatchSnapshot('add mock route'); + expect(await response.json()).toMatchSnapshot('add mock route'); } { - const response = await app - .handle( - new Request(`${serverUrl}/api/users/777`, { - method: 'GET', - headers: { - [MOCK_MIRROR_HEADER]: 'scope-one', - }, - }), - ) - .then(async (result) => result.text()); + const response = await fetch(`${serverUrl}/api/users/777`, { + headers: { + [MOCK_MIRROR_HEADER]: 'scope-one', + }, + }); - expect(response).toMatchSnapshot('mock response'); + expect(await response.text()).toMatchSnapshot('mock response'); } }); it('should be able to clear scope', async () => { { - const { data } = await api['mock-mirror'].add.post({ - scope: 'scope-one', - routes: [ - { - pathPattern: '/api/users/*', - method: 'GET', - response: 'any user', - status: 200, - }, - ], + const response = await testClient(app)['mock-mirror'].add.$post({ + json: { + scope: 'scope-one', + routes: [ + { + pathPattern: '/api/users/*', + method: 'GET', + response: 'any user', + status: 200, + }, + ], + }, }); - expect(data).toMatchSnapshot('add mock route'); + expect(await response.json()).toMatchSnapshot('add mock route'); } { - const response = await app - .handle( - new Request(`${serverUrl}/api/users/777`, { - method: 'GET', - headers: { - [MOCK_MIRROR_HEADER]: 'scope-one', - }, - }), - ) - .then(async (result) => result.text()); + const response = await fetch(`${serverUrl}/api/users/777`, { + method: 'GET', + headers: { + [MOCK_MIRROR_HEADER]: 'scope-one', + }, + }); - expect(response).toMatchSnapshot('mock response'); + expect(await response.text()).toMatchSnapshot('mock response'); } { - const { data } = await api['mock-mirror']['clear-scope'].post({ scope: 'scope-one' }); + const response = await testClient(app)['mock-mirror']['clear-scope'].$post({ json: { scope: 'scope-one' } }); - expect(data).toMatchSnapshot('clear scope'); + expect(await response.json()).toMatchSnapshot('clear scope'); } { - const response = await app.handle( - new Request(`${serverUrl}/api/users/777`, { - method: 'GET', - headers: { - [MOCK_MIRROR_HEADER]: 'scope-one', - }, - }), - ); + const response = await fetch(`${serverUrl}/api/users/777`, { + method: 'GET', + headers: { + [MOCK_MIRROR_HEADER]: 'scope-one', + }, + }); expect(response.status).toBe(404); } @@ -112,33 +109,33 @@ describe('server', () => { it('should respond with defined status code and headers', async () => { { - const { data } = await api['mock-mirror'].add.post({ - scope: 'scope-one', - routes: [ - { - pathPattern: '/api/users/*', - method: 'POST', - response: 'user will be created', - status: 201, - headers: { - 'x-custom-header': 'custom-header-value', + const response = await testClient(app)['mock-mirror'].add.$post({ + json: { + scope: 'scope-one', + routes: [ + { + pathPattern: '/api/users/*', + method: 'POST', + response: 'user will be created', + status: 201, + headers: { + 'x-custom-header': 'custom-header-value', + }, }, - }, - ], + ], + }, }); - expect(data).toMatchSnapshot('add mock route'); + expect(await response.json()).toMatchSnapshot('add mock route'); } { - const response = await app.handle( - new Request(`${serverUrl}/api/users/777`, { - method: 'POST', - headers: { - [MOCK_MIRROR_HEADER]: 'scope-one', - }, - }), - ); + const response = await fetch(`${serverUrl}/api/users/777`, { + method: 'POST', + headers: { + [MOCK_MIRROR_HEADER]: 'scope-one', + }, + }); expect(response.status).toBe(201); expect(response.headers.get('x-custom-header')).toBe('custom-header-value'); @@ -148,98 +145,102 @@ describe('server', () => { it('should respond with defined content type', async () => { { - const { data } = await api['mock-mirror'].add.post({ - scope: 'scope-one', - routes: [ - { - pathPattern: '/api/users/*', - method: 'POST', - response: 'user will be created', - status: 201, - contentType: 'application/json', - headers: { - 'x-custom-header': 'custom-header-value', + const response = await testClient(app)['mock-mirror'].add.$post({ + json: { + scope: 'scope-one', + routes: [ + { + pathPattern: '/api/users/*', + method: 'POST', + response: 'user will be created', + status: 201, + contentType: 'application/json', + headers: { + 'x-custom-header': 'custom-header-value', + }, }, - }, - ], + ], + }, }); - expect(data).toMatchSnapshot('add mock route'); + expect(await response.json()).toMatchSnapshot('add mock route'); } { - const response = await app.handle( - new Request(`${serverUrl}/api/users/777`, { - method: 'POST', - headers: { - [MOCK_MIRROR_HEADER]: 'scope-one', - }, - }), - ); - - expect(response.headers.toJSON()).toMatchSnapshot('headers'); + const response = await fetch(`${serverUrl}/api/users/777`, { + method: 'POST', + headers: { + [MOCK_MIRROR_HEADER]: 'scope-one', + }, + }); + + expect(response.headers.get('content-type')).toMatch('application/json'); + expect(response.headers.get('x-custom-header')).toMatch('custom-header-value'); } }); it('should respond with defined delay', async () => { { - const { data } = await api['mock-mirror'].add.post({ - scope: 'scope-one', - routes: [ - { - pathPattern: '/api/users/*', - method: 'POST', - response: 'user will be created', - status: 201, - contentType: 'application/json', - delay: 2000, - }, - ], + const response = await testClient(app)['mock-mirror'].add.$post({ + json: { + scope: 'scope-one', + routes: [ + { + pathPattern: '/api/users/*', + method: 'POST', + response: { + message: 'user will be created', + }, + status: 201, + contentType: 'application/json', + delay: 2000, + }, + ], + }, }); - expect(data).toMatchSnapshot('add mock route'); + expect(await response.json()).toMatchSnapshot('add mock route'); } const start = Date.now(); - const response = await app.handle( - new Request(`${serverUrl}/api/users/777`, { - method: 'POST', - headers: { - [MOCK_MIRROR_HEADER]: 'scope-one', - }, - }), - ); + const response = await fetch(`${serverUrl}/api/users/777`, { + method: 'POST', + headers: { + [MOCK_MIRROR_HEADER]: 'scope-one', + }, + }); + const end = Date.now(); expect(end - start).toBeGreaterThanOrEqual(1000); expect(response.status).toBe(201); - expect(await response.text()).toMatchSnapshot('mock response'); + expect(await response.json()).toMatchSnapshot('mock response'); }); it('should respond with default scope if no scope is provided', async () => { { - const { data } = await api['mock-mirror'].add.post({ - routes: [ - { - pathPattern: '/api/users/*', - method: 'POST', - response: 'user will be created', - status: 201, - contentType: 'application/json', - }, - ], + const response = await testClient(app)['mock-mirror'].add.$post({ + json: { + routes: [ + { + pathPattern: '/api/users/*', + method: 'POST', + response: 'user will be created', + status: 201, + contentType: 'application/json', + }, + ], + }, }); - expect(data).toMatchSnapshot('add mock route'); + expect(await response.json()).toMatchSnapshot('add mock route'); } - const response = await app.handle( - new Request(`${serverUrl}/api/users/777`, { - method: 'POST', - }), - ); + const response = await fetch(`${serverUrl}/api/users/777`, { + method: 'POST', + }); expect(response.status).toBe(201); - expect(await response.text()).toMatchSnapshot('mock response'); + expect(await response.json()).toMatchSnapshot('mock response'); }); }); From ebb87d2a751a3a4f706a4a71c5147e4549ead03c Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Sun, 10 Mar 2024 21:18:50 +0100 Subject: [PATCH 09/20] update test snapshots --- src/__snapshots__/index.test.ts.snap | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/__snapshots__/index.test.ts.snap b/src/__snapshots__/index.test.ts.snap index fb06d69..e5c0dc7 100644 --- a/src/__snapshots__/index.test.ts.snap +++ b/src/__snapshots__/index.test.ts.snap @@ -57,7 +57,11 @@ exports[`server should respond with defined delay: add mock route 1`] = ` } `; -exports[`server should respond with defined delay: mock response 1`] = `"user will be created"`; +exports[`server should respond with defined delay: mock response 1`] = ` +{ + "message": "user will be created", +} +`; exports[`server should respond with default scope if no scope is provided: add mock route 1`] = ` { From 0c72b5bf0b2b187336f196eed5741641acd9ce9c Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Sun, 10 Mar 2024 21:20:12 +0100 Subject: [PATCH 10/20] take care of proper responses --- src/index.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1064d7b..d20ed0e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -72,7 +72,7 @@ const mockMirrorRoutes = new Hono() ) .get('/stats', (ctx) => ctx.json(stats())); -const app = new Hono() +export const app = new Hono() .get('/', (ctx) => ctx.text("Hello 👋, I'm Mock Mirror")) .route('/mock-mirror', mockMirrorRoutes) .all('*', async (ctx) => { @@ -97,13 +97,11 @@ const app = new Hono() }); } - ctx.status((found.route.status ?? 200) as StatusCode); - - for (const [key, value] of Object.entries(headers)) { - ctx.header(key, value); + if (found.route.contentType === 'application/json' || headers['content-type'] === 'application/json') { + return ctx.json(found.route.response, (found.route.status ?? 200) as StatusCode, headers); } - ctx.body('found.route.response'); + return ctx.body(found.route.response as string, (found.route.status ?? 200) as StatusCode, headers); } ctx.status(404); From 4371d43d151b25ff6d7aff056777ef8bfd94f4a5 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Mon, 11 Mar 2024 23:22:23 +0100 Subject: [PATCH 11/20] use isomoriphic fetch --- bun.lockb | Bin 193485 -> 196075 bytes package.json | 2 ++ 2 files changed, 2 insertions(+) diff --git a/bun.lockb b/bun.lockb index 551128b002dc0eff3ffa293e66bdb070a13a4155..04a57fe485f8a7d038646bda6901cee86c84de75 100755 GIT binary patch delta 38808 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

=@Wq@U;w}e#`gn!#GO0WR^WYL0MG*Q{%nM~ zaQHfzHso0G}e<7qUCR zzZ~L;a~JX*1Wo|wfQsPeB3%hE72sbR%tzV5_OT!6YvIx08N4BK%pN#Rw2R5 zKpv1ypin+)9RTnc!UFhI`!SDT0-&&6bP`Si}?5oI0AeNTmZfSI5De1ehnN3J_gu3Tw=J?_yRsa zZGa2y45TjtxSDWP$p>=Lxcadm8Om^A7%&t_0-^xg%jKjk&P$9MA)Z0lEXxKrGPHKmuf6pdZj57zhji z1_6VCAwUkmH82$z3DD>ifNLbzN6ucZk8~~_;GUR?`k!u=OvufWIhzgvRngQ{fC|8S&{atG;~x;5wWonoz)65DTUd$@_9Y8A4zQ4e zz~{hc04?7S>;XOk*mOI9?Z7{P4}q<~7JwDL3v2>b1IvNefMtLH*fPlgyv!TKMx>Jow5XI3cL&~0bT`I(d)npU?uPdunJfM zYy{q68?Qy+O<+CnHt-feLuvr)3|SL$1F#wR0C*pu*9>n1$P@H_C-4cd3-}oL6xa>0 zRsIF+1NH*{1P-8ny*XLo9wW@AWRtPE4g+W=-JlAPM}Z^2F@OfJ>%IWK1bzk10AB%R zz;)mpa2EI$_y#x+d<|Rxt^$P@@$o(I9dHS_3|s+z0Dc560@r|FfV;pQ;0ACTxC#6O z+%m$HKLhuHd%y$WA@Cc(E_ehy27U*)cU9!(=7@j|Z~&}8WuOv(NZlwN6>0!D4(JOG zJ&pt@UBGc8bOmYxJmGl(^#CsC+zRRd9srkihU){~M)(B#zY&xMKrRY2o1IgJp0Hc! zbvu9+o&b&m+;6B8jLU&O9^k<$6BrAO12O=fwb>FZj{}lrrh^*=H0S2VlUHMag>lp2 ze&ddC6GL);i3M5!slZ5p%RhPUAt68tkPHk5c;+1j3SiAD-2B)godH&62RZ@VRk^#;xk!L8b} z_cjbL6Em9@(>k^U3w@qPX8iN=kfYJe&#>8bbcS8QR(;B||DP&Gn3>rn;jGh9&!pvvLHsV&slJJj$zXSR=}CJLE*f_oz$M9$)jB*S0QO$(qGp0djf0gIPLg&&-$*II?$>p|unk>Z8kUw+f zb=Jjnl0*MyxWi2`!Y@GX0%+h4U^_r# z{sBpY*`-Xg4Y&i{7qXE3{~_Q4Yy{o|)&ksE)<9MSRs*YmH-OiHDu|l_IUP8U@CwM+ zfLDQ+fn~rZ;am}%!D{0hJ{i-B2yZa^^ttZ*LWY+x=h2Y3l!riH))V38rol>j#6 zafaLjsTp!9WE}J*kRL+|$mPH`gjYh+Xd1}+;BcX31C)1ww}ExQo4{MZdSDa4xOV}j zBack_-5dE2fUUq5fUUL}V0`&>jAtDAKi9L7^Z$J#f(fewE&%)MKEPeGgfvEI0Kl4 zn&-7eh&Ky04dgD!em@J8H z4j3N0zC-*{;EExyLgs>}$JY?P15^fX13v*bfa}1Iz%Ae=FdJ#f&*qm-cM;%uO%ez= zs`$W3B}Kfis?CCGV-`&_-d5nC1O@~KgtV65XjDT?$>}Z2ZrAu_ii0vVFd#S}2%D6+ zuc|%qa7%{@YDnE4D9IV=%(o<{N(f2_1Upz9?yGu;mnx`r@Jz-#NUU5C-&9aN>hfUb zh&;7>1$@w{?3*A}8QLl!EC3$f6)qK359K$}yrMcS*Pf zIE-|j{I(QpPLa>oEr&K4>T^|9UJQk+@S$PFrIpm+y5CpCtU&_jlYU;~8YR|1L7}ps zEYY(P3X+D2W0ll8VSin#`NqQIi{AeE%M+j8)C~?|lNyfJtq+UY9b4D>^xfXd+l!1P`GLop=1jm2UNLV9Q9B=3eC4Cns2|*3k+j{=KB@R_haa8wPK9< zrbY8j8Hh33Ej2SGYh-%n=$Q+b^t(8#WTz`fm+q+fHb(Pp8t4oT$=dMEd|{*c!VOj= z;vCdAVde|aQeXUWV$Og|Z|f%LqtJXq$$=xsuGi^3GypMS0l}E=7R)_wTehxU07Sbtyu zni>mxb^VuxqQ`v#%ci-cE<#Kw8XB#6yt)|5yyhDRt5w*wy6m-{cqkELNuTKEOAQ0- zL~R>(MoZB1>RvsnE;b>rr}-Mhmb0@ehevw6iWIG--!+`YMMt!*`6kDf)g4B7m3$yA z3kqn3I{~DqjmaJ7ETXHRl@p!C)GE-EoyF2B>L@kCSv-LrX1;#&4d0>`&1a>K(5(oe z2j&YnM-_~*&;KLcju>Np`?!c6Rbij`O3v3dT{`<@LF(CODR#SvBBTg2U)I@T%~kEr zqZjHvOCew1iFa3Btlc8)Msg%#I7EYSt58!Ms|t&Iy9(6_a-gdSgY?{EjE~8?T`#)2 zxc!P8#wd8rRm?<+F!N2G1=rTT;`QWy$g>oy-Sp!|(cT(u9}Mg9^5JJOwQGr+PVl8c zExFwJ-Mw%-xAvXwND(I6xS_=5zK6)EjvVGoJ`;xD z{NuO9$q)51^%W+luGmu@_Ly(`te6zs?`WkB`;mgv9}9!|{?BGDqaT+Ia=nKbtUw&E zL+goZ&KL!g>Ipw*wC(hIVld_WdSWqn&*}B$9GpM$m8~D2JNT_$D>f#a`s#~pH`UDw zlh@Q27nlh{s)h?p#cM$Iox<_;OXp@*{!TW#zVM#&62o2K)b#q|Jr|g~+*>x(oXSmC z_FVfu7ZNVO*blEF26L&XtylEW$uGxnnqV-n;wlZqW8_tR8;V*r)E=H|8_MmZfBfON zWake**PYYXw)Yx}7m-3;+ejR#0cXsYdv3h{%bLJbw+|qtocWenzDsMO$gIYqT?5FH z#v;2vWJWa+R}&7IFZqjq@L}uq`_Hkbjgh~ru~>l=>cPh11WP#GSX`%mwXvx03J1+M zIVL0?iBG-xVzh3Y-Y4esHY)}$3b@higKCJ;$9uS?QWKHkiZWc9h*glD=IbMKKTg>A zo?0Cbed{X*dT&@0@e^t>>~!}uUr<@gI-p)$y&2erA_7A7y=Hn7(asG`V7}V2O;BvV zI@7-0WO#xxBKy-_-PJ@CyP?rA56(b(ny-j_Y4!UFzrS#ttCG=ZKQs|hwNU%LCL*w} z>L#*ksqvoX+de%Gl-RzRwPhocV~}!SyZVXqwcr*;i*s$rMt1hu6Ve$;ms1*H7_ z#3o9)Rt48JUxJ$XYwNO}WBPEl;K2ohV6a$SSFL45sGFboDFL3DZ({YH5VIx6HD(Xe z1_rcd#l!tX??FgmzQZ-)?4{;4ykaNoDfDy4O+T^49iC)16^GnWe?e36gnCI+;qC!_ zc~jBD10|R*bN%38;maLIZ}LS69F%B1q%1;;(7#TRjUx{S^nl$zSyx=R<)`Nj3JAv` zU*GZ7%gx0#R<_zrc-Dbj+fekb10yhs$JF6CZzRWg%eU=&ewa1l1>JmoobPNYzNn)X zsVRY?UtP6_qBl;6ni(igL-l+U_VK(mu4(-d8(taFOCA|P;uWmI?un{;g@~yGp7x}> zc+*p@;dl{ER|nZ9v=Vzf;ruByHqU=!+rKh*{K@m2XU4G&#r*7vip;l&#LwJO45c{Wu`+lUvvP_g-5!JB0xz8O~2 zfiu|{&E`v6Z&da6s50F*7%|527UTPfm)guR2KOU%Vf4CoOTE=mVb9q@0s|sA7|fTI z-kbVv)b4n7Hi`-kKsETH*)CRiqb*n2rT=N6!`^Dy;?@$RFivXd{S!!`dPj=u-e|F) zNKxMhhMTW?o&2?z^N|$)Nyxz#LpyeglHL0-bLn(f$F~|H#<+7h5hXHwP)2E}ScNjw zFPaGNj*#e$FTsbIufrWNzES(AD%B(OGI5~5z3PAT4=clfu*fAcSUulSWOJ!J*GUvN zP&?ay>?Ah|^Np~}@6_D5@BFJyD3i^IRx#fNJ2|TP{{#fDai{>_8DVsYl z`pJju8cj8f)N7I3VAitZ)nA<3LpUpKaG!^-LyF8U8xkj$q6|;-b+W$}%((lV?@A9> z#oj(b-^y`n{@xeohPu6kHyrjfUpRZ<$5xy2w_L#8Pcw8Mue-fOQDc;4zF~IqmJT!H z+O6iw&R)i(NsAZ98e`Cz?~=XS%4PP+SAR}4d}5cy%R#q3rL@n)2j8pDd|41LJe#0? z^Zm5@>tuiR!0yrcS&ARyMQ^4s-&ot=9ou=2uea2Dmg4JpF|!F;%zS6<#YWS=`n>vK z4Jo)sqW-@w-PpTDML%?Sw|>Ig4;?L!$8MsRpBf%&z6-eTjap&-C#63?sUdO|H{U|M zcFctQNlymRA`Cz-@N&NqrGD7rp1U9G?S}o>-W1iX>nD<$qAwODh&|B5^w*uDHyzux zpSD?9{EqG^?ho;0Py#ka#EePnRyOfs+mG}ZSc7j=5=6CTSmv%Ih{4TJrlr4_&w zf6<~jij3+nhBwCo7l)J>Bt@U}7q2u&)d%~F%Zx8e6erri1SLrXwqR=}iQy5DgOfy5 zd&s;b@gjIn^F_Q_ug<*oX8k>vjGABrO7IRv%5_QNA}8SXBvCCKa!=Ayf3%N$A1z+% zKJxn?AI_c9L(i!1tCy1G9s1C?i+z8L_v@g?=*wPNlGqZgx_NQW7$9%Ds#jcjd04P# zkx?&NrS1SR-5*2U7b)D~*_vw0cIGeK741GmGOp&@U38g$Ou3y%^e_CF~`aQqIWA8hN!oZiC^k$ zIF+>Fv-w-N5paeDg;=%?5I-^TZe(hU;*T1|H+22<^*6pM#cY)+@x|8w(XJ&@TtSLj zD63&%*QWPxdUJCP!aXOPd^A90BSo0`n&ByHd&MNbcO*?uq2E)OFCt!luV7Bnt3JG! z;khLOUoH(4pE9rcDr5J?SA8xlevO+5<{1~z5d-y~E{jfHpLpiGI=waw6y_V5?^LZlChd#gdOl0BZ=m>?17lH=cnK*v)ST_; zhCP95D^K$c(tT5^oo>Ejs5Dz2xkhh!n(vanxw-vn*KG;rp$Zer9AV~?FZpZ_-^{wvDW7RCl7!m|}h?}-%77>^YaCKms&)RXt4 z95FCXP5^h$Ax0UiUUdxf+PUDikpi6~>v!Yv-D{9oiQ-gKnrn@r|CJLkM6M}4RAUBv z{`F)t8^#!H|HpOee<|~SuU)2my5{{CJ5AsJyw1KeL@dT?8TJ>e7`DT|UOxV+%>Tk; zrFOI80B8=bO%WJeX8Lmd#EGh!gA!edUJYz5w+pif>4`q;jM6;Kcbspja&BC~tbg!Z zJ$`G04uzXXTBFFPGi20K4&%S@8WqAO-G*|t7$f_?tf!_UkouPNRG0e+7YNgF&$enE zPxD>sPPNJg-dS*ww_V0KH%l<>HJbucz+Bc9Y+o*zB+>tBUn?#rjt3~?AdSqr?a#?< zTJ+~(uwHMEu;=bX`n{9+Hh0gi(#i#Ac&oaYVn2HXq+8(`TZsw+8 z0tkJ54@czilY9@fk?(b-NlRplRo^7Hhem`Wm2<+o@dw7sfGS4=Y_l@ z=%H@R7MZ-6+m(&K3dPO$=h^bCQQ_9XiJP5Hc107QYj{>Zl`Zacgw+??h(?`oti%0m z4(0k>v8NLbN9Oy~({6uwyHBgUrpU=}PEhTUTv4qvQkZXGZ}9sFpTd8{-qD*^zo9L6 z8nudvoz+h2g%L zcEpuDail9uxQ!H?M?=0km$jm^!h88(`L$MA-qLQcpkluMHws17zIc4^!(W$dL4r{J zniOd9$QNTzF>IwlqwD8C7d%XMBXZsM0R(zR{d5Mr^&JvFFiT> zo?k1cWqQqePs=p!p?^_+a4X9WfWz6jLERFXJf# zeCb`ZcHojntJZzDN>z|tzbUd7iQaKYQMX9C-mpbd&r_+13!bG2ED{%E(X^i<1sgxF zYKy6hH}4#+D*P1+^Tg6~njF8i0zJn6)@5w5(QIhi1=B?U>JR(t`hTCU|4CSp z6NXmb{j6To_P?&z^z!TsajfTm_42_C@r3PBd#1QaZv*f^KyCOE{^nJ`x8Hk$+iSBu zuyFKXSzVBVU)@cezj9%p$6mXhr7&vs{GaVIYvrQEvd!N4ztJmZS?d3}4NTwv{Dsi7 zuX&6wg3O)m|M(plmg+fjGDnR)`^kVMLpiDVO(J?83m=z`|HH<>K3AUrNf+Ciiom zrD!xyPRW}i2PS;eZjb(j64$LjOUrp;a6Ec>xSt%;^%`7ixX0Fj-vWlqEf8PuFGdP= zak)MOW!~LknM~w+T<_U_#wMRACxiGSq$%kGF=ynPw``sXRC$s zJBE_4yDRUP4xghc#xLKN9t%ZOB2s2A6bJgKof|DeN@ujfU+2IUjh|HhTbZ(~V6n8X z+6rF-;n9x}RlKycA2R*<7hIao$sK5MUnE-fgX#FSFRdTGpTe(wCy*oTuMNqDsa`Nu zJwJQuvUwp`y-la^T~~el?()?At&8P`Sh9WJzVrq`*fq_R#}A4Ezy(^S|NO z=aSLr96v*o)Yty^l8dy`suiFm@Fd5mvE`O6!O2p)(ZGd^7+&l8)|H=a6D z^V3==o_A%Kj7JgUf{MTYCjRsETlI$OG5Yp2ajrNv5UyT83Qp?wqfd;SI%iBHra%Jh zV2$PpPyX_@e5qWZH)REVnDJA}N~AERnfcq?a~1|UWX)giREK3^CCUnW?vG6RdE&Wc z=qF%Vi<=W1jvgtW#+o})R2!`3)R~QmNaJTlgdR}}sy*3Z#P|JOyf#=3svVU#DLXZ{ zWqNMbxU8J)wDc7JQK@+;Y2xr;wVCsu+3gPotEa1U_%q$o|3C8k>;4Dijh>vDH8NF} zIWa9cZ{lc%MNppVmW zRL>w;3DjqWLh;vse$vyYjz}Mw;h&O~IUzMCH$5v;tlq48HktTmI+ywXgA|)bs=iKq ts0h&%{()lJLbc)c!)fZ6VqsgUdWxtGYNclXOBDDJ?>!w5JkDMWv+D_xU=Pa(C0`^Lso#fBgRFe0W{Q`*^+A<9M&X%Y` zkCwVMzR8C_OfEhArHnfp?D}GUr(X9w+`DDks}nLxKU%+bom-lX8TE0;@2!BY6U#E{ z40-B{prg1Sr2>J%{23D*CBmolR1#D|#v@B24&V! zu|Ob`C=e)v_7g={L*~t#nln2w5cncsw+q%TcoSMRy@FKcok-QV z)zfF@OrJq8P=ZJmb0o@b$th$N^nJ*x%7;{o9zs?|u0zU_B(I#_D2l%esTNP3;gnkt zs7C@dV7^mLAaEbCYQRdQtVk!9GIr)e1%8QC{70TXjYQ&~K&s%psabiGDKjs7+T85i zKwu&Ml3@$7rcTYtn;n>$n>~AWPIm4MBrX*w2o%nknLVBKftloqIDJ06`fO&_?8z-I z&t5Rq)5jxK&TwRz0s?&r$f7i)3Ys@LXS^)P$)hS{AG%E*s1@yYK|T#q`XVfl{2X2d zj?2v&pPd&7Tw2}b*CFMR?~#h1mOW!0#RcX=N*{tOh$GO8fGVCeb;h`H3j%>HHQkCH zLMma-r0FwqX~{jFJ}o@LG(U56>xUprDpp@l){f*u<>7*|VrHH+xQA&cp?Q z;=De8?@JxGh0lZF+OW{Hj+-69oWlYlc58M$BBhy%i@1oxUs$)aR*X;`ZGga zqdPlqYR>f8fxvAIUCXZbWYtElUg+t&(Pi<}?77qB;wQcMRi3;XNiP&m$fhkhd4XBv zli^9!?=--l9;KVQ1!t2$9+;BXVnNolse!EANi(x@^RhGG<$?Z4HC%qrnwl5z2dCd9 z|67j~_%4Z1pe*>Th1-DKoN2jP69WOKV`=27csD+0`sD1~oY{Ff(BV@{l>*YKp@ak<%96Iw8yef^aVZbRmG zbXWXKkxZ|`?A+WLxh=BiPh+c4xqZC&@sqQrUQYS{>6e16NxxE&Jn-KRaw|AXL9(<& znrqQ&EMb}yK28I)u-EV6dgei-rbKL4H+@BS%ep znSfkch}6)f$7al(r=NnY-As|VRTH^)T{~FpTaBs>qvR*IWPUYBv2@iNr&_}oj~1`dE{H!Iu+uZF(~Um3X_ zy(+ThXm=WaL<3~ukLVhd*N$<^-{aLc4_$-0U?KqxiqT$zugD-*zD5D6-~buq;w|Xa zkav0VMlU`Wsdb|n4Ny5hkzV;n!bc;!Bh`Rpq#DFln43K*dwv``=?fsTa&xm51j<3k zf_XSd1-*fkWfr_#^`+O4kCE!zN0CZ@H&T`?MXG{@NTshe$?c-<=t|!bsr03g)sVl^ z;Bq?Ntl%gi3y(v{;=7Pq@vcEeBL~p8if=K+Ex;gaqj!W?`ld*&%M-H4&7safIdtWp zFk}4e+??szXNI^H9+>K;`y9P8@h>53Y5s2~pbCm;w&wRtq&(m+ss1MZ`*gQKr;tj| z#xN(BW5W@24Mu;1IEXHfOr0^oIq40iJT-V;_PCi@<1Y{N&2v4~6-h%UQN0Um<~bf35xzUV1^k8$Ua1X4bf=*@5chQv=E&)i6$$*)1lF z3s~qH1OC?N?WKk9VT{Je7qeUs?aB4#|7If8QiIfJ9(je^SKX0S&`%*%K})18oku=Z zP#T>RQQ_B%S<8{r$X^lpM4{W2jfw(+#^?p`8cQ>-bUl-aRDEd$UZ5CK+iM+UJ>*C- zR6_cj#qYFOmj38T--3}#T;BJ{8^p`C?P;AVc=#H(oOMWf_OIu&f)Urc8TupT;+a$D zP<0@1?^1UVt|X)Qj@P;A{B86acnz|AuV8;$u1m!l{V!5mb>zQpa6K~yDG$s=vRxGJ zy3uJ+!5?R8|F(qVs&iQO&(>XTavKmKV_h=dN5PV3ZgvZtdW+i?@1tw>l&1k|Sif7{ z2KaX=ACs>XJgbe4+5z_f!~FM8h<`n2(R#<_o0hwh%+Bmcuw73q;K<213q;-Kx^_WA zDibdd@LjzdUR~gO_9yaHKtG1mARIq6XHHHO84_1g!DCg*4+%cDszUj)_NF?$g1zmZ z>vRgvx7*h36il}l@VVOFUbj>Da4-<)Oy-~+Y>*O+wcEya3SMq6;B%k7ozL3#&wP%u z+t%w8zO7gw(1x_d>|NKS+S_45#RGw4CwJQFRJ(2cPN6w4z3kuWw-3HtuLodG;E2p&Gu6e|Is;r&gJQ6xn)^kdDBpszf9CwG;qNzGZ z3w@8)K}CjJGT#2+!}I?`djYMRQ%_M`N;rxEkcftR;!>=h73>}Hk?_@!YIg~{Xi}K&yB~JHKTl+@YddL2>!fx~-yp7IH77y4RAQ!of;zA!w^pqflg=r9whiNt4&Y zZxL$m@I{UAS7pC3>YcmM+`dx9Co9`$6C&Yw7C*N;)%po&scx}occ7`CiaQSeKyl6x zs9e?Moyx;Q(PW&{4dI*7y8KbXel*Us7)}?3OEX#I#Y>#-%R`f=7&;83^?ztIF8mIf z+qE*UDa%S1nHQQ$sDu3*oxCC1-jNgue+}VQo`!{un9=ThP?6bas+{T=9~;rS*-zu6 zUkNGCCC&&=t`P_%pp}u6tw}ZP{FF%e9>`qCpt4$3YT9QZhp_4;L6&ulc@9m62Gr5v z;w(JD0XHJE7 zqjj>Mj%#mKt8MRS7YX;N?IsS|MU7Le!rFGb_L1-bNO#qc6?N;_J0K^rVRdo}+LeTd z(FVF&Y>IWdj-B5j67F8t9Y^l)zX?rNFu!g~jY845cB4wgx|(ZX7MdFDy3lwUz04&2 z49zu7JsDfi@AI_bsZl5zUL_pQ8#LEyB|PLM#H^gu;%vEnodG?LkebA7!8JS3R5oLc zn$DnUKofU+YE*+jUOC#1fLveTNUgzswTdem8P!tZ$+&25-kypcu}!@?TX z$lj3_u?{w}&!$DfwHgNkttcSibXPw#bqNK~)ayJA&(uq?b~m=qc8P?_HNhJDx0Lpw z?u2^S9a%PRAf(0@Q!iV)o7iW&M#5E_{!!MT)L>IPzgr}{7S3}8!{Kc-xyhYTWt+L4 zXHl(}5*m!wPK8@HHnVqhk614>v(I*qgnx&ZXINrrVaMifd2Vmzp()N;al)HD&FzG5 z(Nve4vQY~+UomG;k495IcZlDPrX{C@oz^Ec3Plbn<}6K3;?JLVqtWC#=3V=g@CGzx zbEZ=GB${g)gE!o`rK{n{E-B%$XsXT`aMoQd?X$fj;dedK*;2yQC|7=>T9(Ovt?V6r zB4OJjaTne3ax1%CdL$fXPOE_-MoMZh!QKIPC0u_uHkI&cH?+NSx!wQ0vyTT|?BmytTcfUnF!VM00y_T>J3Lgh)$5P43F9 z1l^n>nVR8DG&#WSi(Ap$o>2wwqje$9?e|8EUs-@VQd6w)N%oEbk?>APQjuM&$PZ|) zH?{gT$1`f5hHPpS$|$F-AhjMs8{m|sHZ)2J1co`9R<}hdcK)D9_)Ew^P6iF2b`(qD z-u#(?Ca2CT^fbDJMptg@#<>O!MUx3`Z?8df$3Km*-fe5=4~c~9!3-uzam}UB zEohycnf@*z*Iw-vb=bwUb_MLBAt~WWX!0oYFFnQD(B3{fG!i}vDd#!syA{{L&L0*D zUq%sf2}>`QzJ;a+;F9>1aM_Ojm>|x&uA`klJQ98ravVw6R`FpH1uA>cW>AFZqp1ca z03+*(h@GDi374cTjdUuoI2Cyg6HvR%;}&19hN=ZuZ$rBjK%(6Cs@`7_QabZ&@%g#TwDw z-Z3f?z6(Ly_*J%^?_%K)KA&Y;N??s}8l({;BS zHyuqWDVl+GFPiIT_2k=V>d7c)>nP8PqBs^-YUqO2$I*6mNsU6G4mpBC4x_1k&giu2 z_qOxLMy%Pr?H!0vAN%asNO&4qUH{|i@B?TX0Pd1~xR1RfD`GWBx6dNxrrYhtMZ!;~ zyMAb-RdVr%y_Fi>HO1!(h0doj*QeP3ULu7#|7$yPtbfa|VcYq@Uex zLL?l=^5oiF)GZ~Pfu^FF0}RcZ(1xQq)rL->wX>g&Z69vP#GdM;QYSx;CWGCxS#5@( zyzOeE(CA=|NoLquFU~FRD4MgcgkxC+DrAN|cd-3r0;r@g)qMTHrrP3UKK1WCv)7IuG*4dHv*{P9mD&yHr%v1<3L6bSS zfi3wcTBcKRr%|rau6u4k!>dZgs`)+|_DQ3J-lN^+*-05%kLENzRB8;<*eUB)LT-(k z-mm8A|6i8ffL)^OZ-f5@cGO^rhF zMpJ4^=pM8_I?ac_CZv(h%xAT%Kf!lZ5mR_6nksef6GC604YZ$b-#(n2?N9AAjv4xy zkhMtmjYoaPg=_}COMMBPfA5G)f?V2W&{+QlfQo=cC z9o%y1>h)+t7BuWt!?~Bahg9&#b1dw&Y2&d6Ou8kgi|=GO?S6R_rP~O8tWdn zLTXsG8Lr3NH9Z4Oed%g9p}B@?kQ_l%nQn`k%=FtwTqq08JqfHM(Dd{*wooR-_Nt#6-L4}X1kR;M|x|_ zYWBmE14RbH}K&YlOa*W_P@@eYo%Z^M}WBw1Jf8p3%QXQ|YcRnk{gBh<}%*M*X2^ z0&GR=M8cYO8q0Pt-?bu2n}XFn-)^@gV%?B$=P!wbUn9<)MjDOQLf;82$X17icDrjL z)`Eq0{xuvWh|`eg97zq|q3LA6S%6MTT;w{zjhl|9slvEzlwv);$lh^nB>WqshBn8f zHmSkIcDtn!Yt3Rie`zE%?Fx0{;-&3F4-;ZkFKr+Gg^=3CF5Dy~lu$s44u3TvcXFk% zE$l=a>*TCk=&Y!eF`p0%7@;=_G4%)yC<+9oIHB!?`Z%HMuhg=j*!Kwaazgd4@sai7rO8h2_rUvq?ZqV;!+BuNxTU?`Z4G-U1&u~)?-ay>8D51p2F>}-!TRxfd&lxf=;a&a@~4-#57oL+U8c}@ zLSqyP?2X^(2TeZ0#3?D(_T~23Rea?H zsWr^GDF|oVZpSel)jMddiL0PPb-40vfj}Bs6=$jryUlL*uSj?aq}sylYM&DN0ByL_ z#>5q_C)@+>Of+?~(;wj-XqtoW_83^{IycH`aeFlR^AdY=oz__;1J2JFe7dqMGTPBx z86;fY)6bV7xY|JRb-efsNy~ovj%o!;RnJQ$X@Lend>qgvDZL?3d}E;N|3G%p*ZlIc zQ$ZV*%Ju(62JLBg)+tc47?@7eN2v%dN%2eu=aQ5@*3n!kv$BBXIFCPHiXZRAPw?W; zm&M^GtJq@p*ALfmygAKFCK(2EJ^g&CV&{3hq;&QW=lb8sVx(lDck(TAvjvbf9AdFY zoG&eV^doL(7kSyP^iutAWC_x;JUQ2eq=p=`-?=1Z4zu2oOmXKzCOYnCk~$I0J?E0N z0Mp0enIO*PNW0_4YH3oL8qRgTRIXJ()vX57ZwI<0#or-m-?_1l-F$5nyHX&qRv%nz zK?SfGXk@wKxU{OquZbPvcRcW51{HgUn*f8ctzFq;w2S~_4MZ5#Y-)z z+}%C>d?~62ACf)2_zOvu-rGyp$CK%v?Ca$_PiiaUl&J*vZ}-#*tC54zirHlzuV%+Q z9_69DGeL(FqcJ;5>5-%PP=%LC`HK{vJb3~s&wS(Q-y(HMs>0JA|D&h>id1<)GO6O?p1j199ZRY-E=eUQ+{;?|ygfY%dMAgO{ok zG77yXl7E5Te8`CYo*W{QOH!o{M=F1YCo?_%QcoX+RIOt@JNwA*rY`m>(?oToqU$rq5iE+kdXZZH0P zDe5JUKVOQ9d)bSSQ~|FbW%+)OmkiovwpVjjiZ?v=d@1TJKGghoym(2CppTKF4)Y;D z9zm+3kBjuA_;1ifo#aF1czf|Mqlq8>wGC{vd90ORFcd2@RI7OH3tYs{>?34@_*d>$+Fr&Ev*A|T}b|O&xd2=q%2R4 zL+UzTDtbhkW|aJ)d*$_s=~a_kjQ0 z^Zj$r_aFCu+V=ms=lkcLPp7wk?)m9zD= zGrQ=u413QjW$YtpE$oE78TR~F%h)USrUzTvhtV3pR>toBdO8okE_*%0K7@7}t+k!D zFT-BCw~W1hUpjxBaSAQ<^)mLb{prCJd)@vF`xsj2KzgvPJ?KD&y=q?>dkb27JNQP1 z-D`gtJNu3FU`Km1+8MMeZ>9%3*;#L9*c%R%v3H}T*%jW(uruE%W6yajJ=oRWg%tKex4ecOWPdoPQ414lhW$Yzyr}Gyv2hgGqma&uGN#}23ir&FKv?FN! z?1V$u_crz&N)HaO52H1H2m9Vl4-T@Iy^DQlr_qMkY42g*A?$lEoxgKAg_im*_Pw7T z%&^zJk9}yN57L7p>_H!3-+S1HcBviw5c}T8z7NxbqwURTXV9vAlpegy&iV-ZKEOV- zEW5(T*!Lm!eViT~Z|_2j`Uv|zNe^b*vp&H-w1a4q?AXKD_c8VzP7mhT2hgHF!M;z^ z`J0}iPq7c}2--9|;WO+zjD4S_2WQxa(Heh>eV?ZXXW7d>$3C>vXnA(p5$yX6`;Mds z=h&yvQa{JOFVcha>~&vYA6n>WdT@a~=qUCb!9KKwcJLVXeSv+)(s^WKGuj!nDqp4t z3+${fvF|AMp%vK`j$_|3>^q(wyvp8%7WF0eeU;8%49)rq`_K-eU2Dglz`oTun#TtT{?d+HRwC+JBfX0x7)$*vF{Z2eV-n@ z)834B2Cd2u>A^L2)(_bC9rmHEvn!m&zVET`bb9a}dly>N57_r(dT_lx>qqQEJBW6_ z9s3jZoyNYO(t{7!2hgH_#J)4>!3XW4GuVfA1nprv;b-jo3HyFd4{o#%qcuK*eP`2y zo9$(1u@CJu+GBRwFWC1p_WhC`++v?XOFfHyzorMb+UtJBKD5wp>HJ;Rpx?0X7wkiO z(hi=(zF)ELTzc?ndo$V@v?{-+2cNaGe#gGwun%q5iVBSM%yVT{%n7CkpI@;HE$a6& zCN7A&+sq20Z$m#Q{UsA?1v6Gm4wf-XEcBPn0dx}`gh(m|@v12*2C+xP5fOV$LUD-s z7Q~9;5c|ww5siyMbPqusFv~&^heVtf@uo>D0kO0=#QG8t2hAxFsUe7Amq5H@)?ET| zOhhOO;$1T+3Sw0Wh%F-CH^DGOuS+1Z!w?^u%_7c-s8SN*W0O@9VnY5$ z9uY@Gd~FiSLCh}$v7#Krx8|^j#$_S8mxnlImX(J%B;vG)?@bzAWR{kLSYH9+v^gar zwLHYIiV#1Ubrm6wi3nAK_}L7q1hJ|D#1;tamw>S<2Zx(p6=AX~!~7O7kBT`Xrb-o< z-vee`6_^c`V0Oap;wjg{W_KiHNET5myf)&djO@u}#E5 z5sgf2eTd1i5KHPqG%*K6MAw5zY5>v96g7a@BjSjN7A7GMVt##y6>$(P&0!IZ8$fh# z2$5ixHH0`M;~(m06qjUbZDDG{j+A%-=ENHOagLmU$kY68*L3~BMaHG_z2 z0ny#eY5}oL#6b}~O>8{GqCCYS`#t2IP+62xe;S;QF;RgxhtGg-+H8`?nZ7LjEtq(EdQLCi^k z7;kooh)RZtONGcbvr-|pi8v@?l8J2#F*yZdNn3~j<&5J;eHs5Od5a5vd&@hD9Le znRO9}VEva5KGLgt`OTq929Y_iR}h4xeLUSZV=a*10tfkLL_yE zxWN>4hu9K`a>n z@qjrXBDz0B(m;p@P0>JzJtB^Xc-SNif|x%5V#Oecjpne3#seX`4~E!mmJNnDB;vG) z$4uG~h^2!d)(?T$Vor%j9SkvSD8yE?ZYabt5usra+s&Y15UYkjY!UIK2@Z$oH54Ly zIK6>bs0o<7Q}~UvxqYys*Hp9*kp}^*f17iw}`{0!gz?xEQmSdAwDy^L`02) zh?@X$#LSuiu}#E55l2mIHpJxd5KFQlzBC6!L{ET7nh5cgDVoSV(BpL)+b8Q3kBab3 z5D!EaZe5!boEbEQlY)E1-@hs+7*xPizC5@;)Q-p2%9ufmxN(FlgVI?>~kb1IN#+4(IS;qe_Cc5*$a9tJfi@m3QjRU z6K3<*!AQZm-`z{xHlh@pDuv6})ioMhy0()*KVj`gW2I zajuFUZy^qJ~Nhba|zjpWaxK}(* zPX_ZKy>q?laeC}(vajB{Fb8ixc4<4SD=Tu6s`fTC!lWl(Bo4E2OTC5_zX@J>zRUwJ?@B`t{_ks!T_dm;0rHv zEa9twE{#|IIggo7beKTkn8(#epX71=LuL)o^-Y^D|A8}BslYfm^}qk{Swq4#i_!n; zLp_P6N*aO23UGbxag7O=C#-(;pHyo?xC8~NYxK05vNQz-Gu1UGJ+2wyXu|T^DUWMT zSdW0pL*MCS^Sg7CBU^% zB-a^_V*w7t6PA%bBbB8<56|DPlDYJJ9sisM==I#DTKb#EB@vEOB-c5lvLpjNIjx3@ zP;?5IO37-Jp3@Vj^(2{q8fC$$Vm)csQjbikRUt1Sf4&e%AfdPt9>=opJdcbBT;g#Z z2=^vk8)THnbtHT{P@}>gr$viOoCcKixK41YLyanBjdfet8AVUas#T@EglU9-0BV39 zBqZ8d@$^i%Dl6-8T?s3%Dl6x4g&I&?JAW{Jz7O_~*YiJ@0zDU$2~@f2lEErp2B}Py z(J5da`92siC%+Fi56eOgz$PZ`hhU?SCP^tX?T28s0xcg}Ei?o*^R=$6Kl%fkvPns1K?GEz31Q45$UPvRwwof-Eo|OwiM8C246XP#P2idLnHY(9`RmlQETW zTcFjg31|wMfwF)86!;G4@zR^YE#OwL9N6GCuoA2W zcYr(9|925s1J;4N!98Fam<}cbJ#@Q_3YLT0z(UX$Tn?s!DPS^~1oR;IWne7Ol3WKY z#i$zqJsp@1W`LPs7RVKwM_@LX19HF=FdmEnmw^T#7SsWCL2aNldm<=91u@_=I6cu= z4`}Uf4s5t(;1a@%kPE@r;2ZEQ_!8{XXnzxEdDL>K>tG+~Pp$!AAQ%Mn1n$#7j|^)!dI0^j{}96Iv0!uDd@K$qmVx%PpWH< z6ZjaSEt%SZ6wnOBfM}qt>@d)AM9f>h$#gCfe#0;j-t z;Ct{R_zHYZ{1H8Tuj9+3WZDGuACGrH?+BhCoQ_Nb5uj7#V`SU~_JB9QucRpi*MZq! z2Dpl}SA*-p&0rSLd2BAYn>+?A0Xo_Y*CJ4ph{F&y3G2wBa4Eui5Ld^Q6%?u?%381p zWP*`kI8a)l68S0v9b1NhSWq9-1*?d^9!v$31M7wDy#jl}H3F;Q z{{@s-rvse^T7p&}0qi2ZHuScj9k|^~dw!bM@QL6D;`$)(K>A#pAdjLShPV@;1Wx|I z2iFG6GU?w3<0^cDt}|>~NS$k%nF_xKUn*!yP$}Qz;{bROybBJ2cYxMx4YGq^H_$Yg z52k}@K#POCUIAPJ7L#7frWOV*5Lzgv0j=?uf{|bZ$OMBmXxkA`o05Sx0G*{;fL5R- z&`~G>v<7WJ3P=TmKpN--+Jgw_2s(JOGqNv82YotJlW5wU`l+Irvr&GVP)8r!a zfR@kMK=XVa&`#tpd!G@01-t-C;A-tuzY*R5eg$X2&)^K$5B7n*K%RXSs4JEKWuP*i z2L-$M*a_6~XTc8eIFRce1)ISp@CbMi{2Qpk`@wxc19b(s4J-%CfIOqV9uIB>Hv{$i z)!-&@J-7~Bss1k{ATJbwYrs-)EzrDHhO2;h>5?~qCg4VJ3$TGI(ps<*tOBdSzrY<} zJ-7$l3GM=SgLPmnxL!3^B(MfZRs#2e2f)MNAt0+2-U!4Cvi>o!6>I@dfNfwqkXN1o zPlG4HQ{XuuZ_09cSYdf`xh%g7>;`g^5)=b3ffvCZpa!V#UIVX#@4*}30Qe4k4&DN9 zf_K3?;B9aa90G^Id*CDRA$T8r06qoHZzX4x^ z6X08L68r$v1*gG};3x14I0uRc-D8=B{2e`rWZ@}rBGsyL5T!vFXu*-iZGmJd^pc<~ z&|yJKdv#C^Q~?!%mTWEERlTs5?;2kC70RehSci;hgqH(({jB<5mdNe0IvJ=!Wq29X zB7<~6aXKpL$TS5^2H9W|m6ZD#-|1}2C7^QQ)4wI&To(!;v1qk z(iJ$npEfK%gEITpD6w=ulX&f%@`%n6sw@@gjG?oJ4l1&)El`{qBq+V4@+j;#xOou& z%M~j0LKdm@ivQ0{e{yQHAMbaaj8Rv}tIFr|e+?^dp8_wD-%44lmiP5|Wmdxe!1vMs z!m7Y8Q0e{3#0e@#`7{nPJWjfJb-Bt_x&oi{3)A3J1?sC&K(18dlu=RzYH*&P-Y-Lg zO#@7E3TJs?-vdf_e!g+)fA!b-WH!2%R!L1IO-n7e=cmabP7V1hcfPL5lqTaZs6 zmmybxjf7Vr)o3+P70Tj!!98FdSOe|?Yt{dE6Sxm3@_wL%L^zoqLW+MFJP7^`YF{_W$+Tv!9bDX9`vNozY3>~r4i6(^a{EP z*@xT<#uC=3&H}HYzYbK6@@q5Ok9-Tf;c=>~z(22DhvFCNw@|yF`u$Cy%zi_aPK{Kf zRhSA=W5m7fN&i5jgNYiVhWaO;576HSG`8TsPCgHlP{*zhNuWXYu_q5Br@^bBPYE9f zLGUFw29AKw!Drwo_yQD?R=ng_NFCRF{g7p$73be5^Iq6$YHF9Xs`4aFQc0^{u%5Z9 zq}7@yO%6yzOsJIAI!U>i3Wed{m#rB)`*6?-j!BGf6`$fBpi2@{Z9x2EUC(@&KunwX z$R`r^-Xj%obKRW4&t>dOO z`?phYoA_32R6LbM_Egg;i*yG`SDJM1b{Mwe@+=&xeJq`p&(bhHJ|Nr<;G@iP> z){K>J64NTaje4?f88fwl6&>ThtDxm=S?1%)osWiPtm#~d&L|>!6yGD4f)Sj{-kn+DnFA`%LrZZLPfKP6g+xhHdVnt{(B~NRcwFy zvcAh@y_Yhrve{qDsuE&_>08;HBXP3x208Wc%DYa4>W)ow5<8R9f5+V0{d?U1)h&nH z{8`M@%BF8snmWI-nOK#k`ERd#W^m5)*10!tC8eB6Ew@xQk5{!OT79aSm}*vEt5&p` zQjMm59&PTbM(zG<<~n}AbMq_ZlO~gssjIGBSKVEcif1Mc-CN?{yNS`LA?Ew)<^*|T z{1?5=jorKW#v^sUcG~B({GOVoZZx$#RMQMaCj0NA8@0RnvFeK(^(Q4I$#woq<8GL6 z+vca&m4EuHy@EQd1HMy#k?J#|M0-K9f}-_ z99#XDa(yu{zgWLvtG5!95}!yzT0PmoG_Of5D@(7eWks7YHLV&k{tK%HoqoLShTZR} z_bEj_JJP`1R+C=WyRldv3b(c>w!YlH=f5L`yWlW8O<^kvI;(>GS8RpT7j6Gxeaa@1 zrMd;xh%>EX=t%!bl{-e>68}}x$I6mI-IJISXccEB#NhPyaXh4oq@k}VhyTX3!%N?v zy}AD@QBKv#8XT?($^I+Ys)k0?>R;3ERQo+)}mM3Y1G&HHO|zoO%DGZI=8QG@Jv7Z{vXJJ zXVt?o4b9-%EI0le?5gis7XEO_rUyyEcBL5-X=vW6jcJUts5;304NV70cd<*fG8&qD zr4K1<4oPM=G^OiW(VQ{T8nGkJYG^v6#`rH0tUI^QrrgSXb~gX5xwkbm zH!G$8+QE^*JKpc{)0&y2^d{D-MrKSb-S%Exy3K#v;I__dUQZ7^b(&=I0tG(V$dr$z z|NZwB&fk7ICDg9BY|_wY$MXt`@!w#Wy0XP>vCmhe7p`Dm(-`tnumL|9YgLcA&<5;| zkp9ahzBtj|ueD&D*fXT9Fi#55r7@2yO714{Sb8@TGW&M&;REi>Bj4V$IY zkykgcnl>m&xCVoMEmDWNtmk5P_gqz7?rFnzPdg~xlNT1DXHc-}{F(Z2MQ}#MFlS?1tv4 zIC|56C*jqd-m2MW|DK*+UfS_ck~tYCf25d5L(XnVW>!P%c57p*Dci_VHGaG!VvPSv z!UtnB?>@Wa_XZT>&Gx9a=GI13~_viuDrI; zoN2_GPnV5t%!=f{lkdAl4|jfckadmIL}wKEuLt~P$e8NyesujuP7H&Q^KS?92zf8G zl`+DUqEAQjX=4V)r5(+!{n^r>=65u$nvltVt>KT;S6*4U1pPp7y+&T^^)@S;QptaJje3UZ5m?>8v}wvf@pq}5k*{7{r3@t?t&&LlE zQ-zpz8~ctv+AnSPpD`7?n%d16!m(Y=RAkJ>Q+g?^!fxh+p5(Z>n^~)ztGbyT%`ohZ z?&cenaDgeN1p)^%#p*R@HS^z&`24#A5-OC<-RHE%8T^xbo1Oiv%0snKJlET`ruFji z`Md61%U-nwMmvW8a>TFNXJx&d*gs5u26cRLa_XmEimlPRKiI$j+~>qNQ=v`Z+uo*h z3tBm{kLlb3U-^$A&RDgt+!agvn)1O3eO!NEybG`GYi_2D82@F9KhMAF)Ms&bRj(Y3 zbaeNmv!%@kEwHeAKewfkG1gOWeH+EOpea48Yj8g^CLXWV?9bEpwCA(_<{|VL|NV)d zf4g|~2RHK+#ve0i-2n3mDU$s+Fm4!sVDP-tpId*X@ZZ_EZ^^{v-&T0?YR_ss9vW!k zTawp*n`3VH#xl|GUb^AW6#n}h>pc+up!)lpYW-QNm)G3Z(yC#-KgevNAj>1QlWNO1 zzY-ZUYq0Y(Mqy&-G4Y3ATC|cfQruI%d;X3#eOg&)Gx z9Zes1Q!=l$vihrrm;@Yik-Zw@_6;@z6Re5J{>%NUMBmxGs%(s zp<~g;;H?ih#yOkN=R?d1O1I7oF(ndNW&Jk_X5Uz;{qk2I8%IhGpz-_?&~&Joo=8VA z?C(bU8)wI%=42wx^Iu)qq2Y#v@?CCS=TzY=zN3el2Cd0BWvIz)&H6TfD1|Y4j}A3A zwq{}deyDj*@%4tAotnmUR|ht2`18NK8dxf} z4>z}xLM5zCwW`#?V*jO;(Q6j2zG=fyEwkQ2e3-;(3-y-PDpt7MGyU&dML-5*wDK2d6xS2ro{&M*^_ znRNckD5IC`S^Z4r1D|{6_%+|glduf~hsC;Frs+@0WbYN4h4Wi9`)X0s8poWXD2y+y z$jcpG;lg_c^vQbUHQjlr5}MOJ)7(Mc82|O0GdnMOaA!e#9ZQu0lX5f7ewFUO;PcJn z^A3GhW57l)1r7CI0a`gNsmq+__Py`Lu)%D}G__O7`x^CEr2bIF+1u9M-oqaiwB3I% z>B-X7rc8e0Y`;HC{W{Z3C9ma5O8$!Pb$hgRUY}RfhH5g*Gi~w2|2QcBD9ImSxtE&G?a1lBd-Y(<&JTt> z{=&Up8}QIy+u-J`VxDft`Hb=SfwEt`Vy~jVN#RvsM)5zD$N2AjeRBTsL5F($*2(iJ zHu|&otI_5mO%2Zn){mpjC+NxkOJ9#?Jre3T`@Vx-PW*rXUp@@~mp|`c`*RBW^XcNo|L1`EA4~e5YVzYl%n`qTy+$bNLaSu*1xJXp z{`&9ny}RtH?Q?E=va{zS{OisCiJQexI>b#^Mg8F0=FKlH5Wek|yJAbNNv2iTKL^qIUFT0`ch!yYUtiqo zg^pGG=l0KVn&+%1ezUxS|JY{yy8p5>CSTmp3vMHhlU7VNcXVS(xX8>oZ@T!+_Lf9b zy*n$4w;8Le={je#cKXfTtg9ApEw8NYE=M?fr1Kq_x4Bu5W|(iBC@-_mx0qr&sPivU zXl)hho=M~S#jbs!(+8FQ9K#p!jb)~o(mI{{o8m>%nHzg@#&VDHeg7E1e{r;RV5*79 zWD~!&*LkD-B8e_$h3gs}JyhHxZPXWO(;u#5jQP#yD-ymz=^Nv}a5-iF>T0juGqLoa zjyN*i6q3R^HQl`4hr)lKZjSZgOu~(4OgcQbms2F)m}R!5!+$Z$oJc3#kF!jPzML!k z*Z$V~ZCq@@6X_?NlAYU2yfd_~HO7j{HIMhT>RNo={Wdw0{TB!St8H@0bN_zt964IY zr#M^xe;xb%1$$bqiS%Rm`EMz{>-#&pREsRS!>KJnXAYBVa{JMq&AINc96v63Vr)Xk zX{VeNZFS^uZ;sd&NKuik|Gr-zd+E(tH~U}WF@*hB7Ek{6@oxvWncalEN%0A~BYrQ} zll4jQ?Kh)IP6#lX!cL`Q&KBoh;{G_r^Fn*~)W|0asS-|4iGD zcj^eF6tu^GB{YA7X2!glrLV0xE$Vx>>UriP)mAUhoKbD@d8YXQYSZhm^{vOROYdKP z==E7uoE(m?`{tRkr1<-GQ8vFh&0aaks?s*GRlt9R_06NF*n3`cNZ`N5`{xSTm!E61cePVEXV5l*l=-GVd1Ee?qLt@}P-2_F zr}NDnN?WOz+tk?iTaIZoCiW<$(fw)&cb)qOVeaJ%O!2|U9~YPpvT(QmO*~&tOch^{ zZ`LAX{`TvsZw8a|r(6>?1V=Kk9~y@K^ zDXLLN%DqP$-~Y%tZXNv;{tL|W9$#K+M07Ro!~K{USDG>MgVuqHzCGOyF}g0eh&Yq= zVk^D3vHaIJDU_M2UoMja`PZ0%nT*71c=)nB{V?LyC8y7RJ5*mxyx)=4X`F?T7& zI#Ot||28$aX=c4{YL|WkqFoPPV@{C5VszIXL7)1s2|qDm0yh{*8TWZVJg$i=Y~E~1_Bf?*zdVcFU|Nl4Y>m0W z>>oo}{!88;sP*BVgO7Iq(F(dH@hr~`X6H!09o&9{>p4G1Ohe~E)54>VELfKD>8ziy zzBQ{Qi=vmpa&sn{n=iH6@I40)AyhXXTx!)w_TD64_3+gZF zzi{5nCf{`VGsZK;`#Gd#h_Af8pE}IB(N>Lt7q{p|#uC3B7EXM()1$*$q#h|BEPsph z$AyL2c~f(y&*p2Fp@j=Kmfvuue9(8X_idQvF0kVenB!xt zZVmjb7w^Y2H=Dti;lN!tnrklO`y)R`zgx`?@q82i4Kn7H4(^xylansHuVwSESJ1C) zTpItr%lS!q_*m;-7yRzN_22(!aWlI_@{bGLePvn8kvBEm(y=X@V zdp{QLzRkqzXDsjMtP8Cg-m%6{k+{;eZP=S98syI?KZ07kb>)}VW)o#aF0k_G`y)Jm zJ#$Szyws|9xc<&?z3t@ner6gt%5uI3zIBu-F~Q2Mv56V4)$8iiq}PJ;%bwfm#V37l gmQSz}%zG28rdum#TW^=zx;odIa=jVyo3-ly0SOY?00000 diff --git a/package.json b/package.json index 1ea5724..a4e48f4 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,14 @@ "dependencies": { "@hono/zod-validator": "0.2.0", "hono": "4.0.10", + "isomorphic-fetch": "3.0.0", "minimatch": "9.0.3", "pino": "8.19.0", "pino-pretty": "10.3.1", "zod": "3.22.4" }, "devDependencies": { + "@types/isomorphic-fetch": "0.0.39", "bun-plugin-dts": "^0.2.1", "bun-types": "latest", "prettier": "^3.2.5", From 67f7342713ade92ce36c197d2867cdd13849e632 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Mon, 11 Mar 2024 23:22:44 +0100 Subject: [PATCH 12/20] use isomoriphic fetch --- src/client.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 4073535..285fbb0 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,5 +1,6 @@ import { randomUUID } from 'node:crypto'; import type { ClientRequestOptions } from 'hono'; +import fetch from 'isomorphic-fetch'; import { hc } from 'hono/client'; import type { MockedRoute, MockedRoutes } from './types'; import type { App } from '.'; @@ -13,7 +14,10 @@ export const createMockMirror = ({ defaultRoutes?: MockedRoutes; options?: ClientRequestOptions; }) => { - const client = hc(mockMirrorUrl ?? Bun.env.MOCK_MIRROR_URL ?? 'http://localhost:3210', options); + const client = hc(mockMirrorUrl ?? Bun.env.MOCK_MIRROR_URL ?? 'http://localhost:3210', { + ...options, + fetch: options?.fetch ?? fetch, + }); if (defaultRoutes) { void client['mock-mirror'].add.$post({ json: { routes: defaultRoutes } }); From a2b27a4cf8d0606ace61eeb139195a3fdb091d87 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Mon, 11 Mar 2024 23:22:59 +0100 Subject: [PATCH 13/20] expose types --- src/client.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client.ts b/src/client.ts index 285fbb0..a069f83 100644 --- a/src/client.ts +++ b/src/client.ts @@ -5,6 +5,8 @@ import { hc } from 'hono/client'; import type { MockedRoute, MockedRoutes } from './types'; import type { App } from '.'; +export type { MockedRoute, MockedRoutes } from './types'; + export const createMockMirror = ({ mockMirrorUrl, defaultRoutes, From 20d63b686b36975754be4f2d1a9d75eaca198b93 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Mon, 11 Mar 2024 23:23:13 +0100 Subject: [PATCH 14/20] reset scope after test completes --- src/client.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index a069f83..616e490 100644 --- a/src/client.ts +++ b/src/client.ts @@ -49,10 +49,14 @@ export const createMockMirror = ({ ) => { const scope = options?.scope ?? randomUUID(); - return integrationTest( + const testResults = integrationTest( getTools({ scope, }), ); + + void client['mock-mirror']['clear-scope'].$post({ json: { scope } }); + + return testResults; }; }; From 8f7ac9535da28d807808f050656923ebeb89e8f7 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Mon, 11 Mar 2024 23:23:28 +0100 Subject: [PATCH 15/20] fix log message --- src/registry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registry.ts b/src/registry.ts index c5f1eaf..a780f54 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -32,7 +32,7 @@ export const addMockedRoute = ({ scope, route }: { scope: string; route: MockedR registry.set(scope, [newRoute, ...routes]); - logger.info(`Added new route to scope ${scope}: ${route.method} ${route.pathPattern}`); + logger.info(`Added new route to scope ${scope}: ${route.method ?? 'ALL'} ${route.pathPattern}`); }; export const addMockedRoutes = ({ scope, routes }: { scope: string; routes: MockedRoutes }) => { From d03e666bdc3d792a52703cb06da5828a80d1bd46 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Mon, 11 Mar 2024 23:24:01 +0100 Subject: [PATCH 16/20] update ts config --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 8d912d8..d2bf65e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -100,5 +100,5 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "exclude": ["src/**/*.test.ts", "dist", "node_modules"] + "exclude": ["dist", "node_modules"] } From 5015af9c8b4f0380700f0c8660d6ebda8ca24844 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Tue, 12 Mar 2024 00:25:56 +0100 Subject: [PATCH 17/20] fix scope clearing --- src/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index 616e490..8037245 100644 --- a/src/client.ts +++ b/src/client.ts @@ -41,7 +41,7 @@ export const createMockMirror = ({ scope, }); - return ( + return async ( integrationTest: (tools: ReturnType) => unknown, options?: { scope?: string; @@ -49,7 +49,7 @@ export const createMockMirror = ({ ) => { const scope = options?.scope ?? randomUUID(); - const testResults = integrationTest( + const testResults = await integrationTest( getTools({ scope, }), From 4517da387d54b1d19e2c39866585745a76d8cc1e Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Tue, 12 Mar 2024 00:26:24 +0100 Subject: [PATCH 18/20] limit tests to src folder --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a4e48f4..a643be8 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ ], "types": "dist/src/client.d.ts", "scripts": { - "test": "bun test", + "test": "bun test src/*.test.ts", "lint": "xo", "build": "bunx tsc", "dev": "bun run --hot src/index.ts", From 41dd4730a0823dc1002520ce38fabe144fa92c1e Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Tue, 12 Mar 2024 00:29:58 +0100 Subject: [PATCH 19/20] clean up --- build.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/build.ts b/build.ts index 056234c..25e9099 100644 --- a/build.ts +++ b/build.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-extraneous-dependencies import dts from 'bun-plugin-dts'; await Bun.build({ From eb92993da08df1d75a84e1dea2b4da0c89e73ea8 Mon Sep 17 00:00:00 2001 From: Chris Kalmar Date: Tue, 12 Mar 2024 00:31:11 +0100 Subject: [PATCH 20/20] add gh workflow --- .github/workflows/main.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..2d67169 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,26 @@ +on: [pull_request] + +jobs: + build: + runs-on: ubuntu-latest + name: Mock Mirror + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Lint + run: bun run lint + + - name: Test + run: bun run test + + - name: Build + run: bun run build