From de6cdd3d7e40611c8ed7b5e3f8581d97f679f016 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Sun, 2 Jun 2024 20:49:33 +0800 Subject: [PATCH] Update tests Use example project to setup integration tests among the modules. --- .../http-resource-server/package.json | 5 + .../http-resource-server/src/setup.ts | 34 ++++ .../test/default.test.ts | 73 +++---- .../test/fixtures/data-source.ts | 14 ++ .../http-resource-server/test/index.test.ts | 8 - packages/extenders/http/package.json | 30 +++ .../http/test/error-handling.test.ts | 191 ------------------ packages/recipes/resource/package.json | 17 ++ pnpm-lock.yaml | 10 + 9 files changed, 136 insertions(+), 246 deletions(-) create mode 100644 packages/examples/http-resource-server/src/setup.ts rename packages/{extenders/http => examples/http-resource-server}/test/default.test.ts (61%) create mode 100644 packages/examples/http-resource-server/test/fixtures/data-source.ts delete mode 100644 packages/examples/http-resource-server/test/index.test.ts delete mode 100644 packages/extenders/http/test/error-handling.test.ts diff --git a/packages/examples/http-resource-server/package.json b/packages/examples/http-resource-server/package.json index 6cbdc44..87c0dd2 100644 --- a/packages/examples/http-resource-server/package.json +++ b/packages/examples/http-resource-server/package.json @@ -41,5 +41,10 @@ "author": "TheoryOfNekomata ", "publishConfig": { "access": "restricted" + }, + "dependencies": { + "@modal-sh/yasumi": "workspace:*", + "@modal-sh/yasumi-recipe-resource": "workspace:*", + "@modal-sh/yasumi-extender-http": "workspace:*" } } diff --git a/packages/examples/http-resource-server/src/setup.ts b/packages/examples/http-resource-server/src/setup.ts new file mode 100644 index 0000000..29d91a4 --- /dev/null +++ b/packages/examples/http-resource-server/src/setup.ts @@ -0,0 +1,34 @@ +import { + app, + composeRecipes, +} from '@modal-sh/yasumi'; +import { + DataSource +} from '@modal-sh/yasumi/backend'; +import {server} from '@modal-sh/yasumi-extender-http/backend'; +import {addResourceRecipe} from '@modal-sh/yasumi-recipe-resource'; + +export const setupApp = (dataSource: DataSource) => { + const { + app: theApp, + operations, + backend: theBackend, + } = composeRecipes([ + addResourceRecipe({ endpointName: 'users', dataSource, }), + addResourceRecipe({ endpointName: 'posts', dataSource, }) + ])({ + app: app({ + name: 'default' as const, + }), + }); + + const theServer = typeof theBackend !== 'undefined' ? server({ + backend: theBackend, + }) : undefined; + + return { + app: theApp, + operations, + server: theServer, + }; +}; diff --git a/packages/extenders/http/test/default.test.ts b/packages/examples/http-resource-server/test/default.test.ts similarity index 61% rename from packages/extenders/http/test/default.test.ts rename to packages/examples/http-resource-server/test/default.test.ts index 0af51c9..2e5bb2b 100644 --- a/packages/extenders/http/test/default.test.ts +++ b/packages/examples/http-resource-server/test/default.test.ts @@ -4,41 +4,34 @@ import { afterAll, it, expect, - vi, Mock, + Mock, } from 'vitest'; import { - app, - Endpoint, - Operation, - composeRecipes, + statusCodes } from '@modal-sh/yasumi'; -import {DataSource, DataSourceQuery, EmplaceDetails, Server} from '@modal-sh/yasumi/backend'; +import { + DataSource, + Server, +} from '@modal-sh/yasumi/backend'; import {Client} from '@modal-sh/yasumi/client'; -import {server} from '@modal-sh/yasumi-extender-http/backend'; import {client} from '@modal-sh/yasumi-extender-http/client'; -import {addResourceRecipe, ResourceItemFetchedResponse} from '@modal-sh/yasumi-recipe-resource'; +import {ResourceItemFetchedResponse} from '@modal-sh/yasumi-recipe-resource'; +import {createDummyDataSource} from './fixtures/data-source'; + +import {setupApp} from '../src/setup'; + +const connectionParams = { + port: 3001, +}; describe('default', () => { let theClient: Client; let theServer: Server; - let theRawEndpoint: Endpoint; - let theOperation: Operation; let dataSource: Record; beforeAll(() => { - dataSource = { - create: vi.fn(async (data) => data), - getById: vi.fn(async () => ({})), - delete: vi.fn(), - emplace: vi.fn(async () => [{}, { isCreated: false }]), - getMultiple: vi.fn(async () => []), - getSingle: vi.fn(async () => ({})), - getTotalCount: vi.fn(async () => 1), - newId: vi.fn(async () => 1), - patch: vi.fn(async (id, data) => ({ ...data, id })), - initialize: vi.fn(async () => {}), - }; + dataSource = createDummyDataSource(); }); afterAll(() => { @@ -48,27 +41,9 @@ describe('default', () => { beforeAll(async () => { const { app: theApp, - operations, - backend: theBackend, - } = composeRecipes([ - addResourceRecipe({ endpointName: 'users', dataSource, }), - addResourceRecipe({ endpointName: 'posts', dataSource, }) - ])({ - app: app({ - name: 'default' as const, - }), - }); - - theRawEndpoint = theApp.endpoints.get('users'); - theOperation = operations.fetch; - - theServer = server({ - backend: theBackend, - }); - - const connectionParams = { - port: 3001, - }; + server: createdServer, + } = setupApp(dataSource); + theServer = createdServer; await theServer.serve(connectionParams); @@ -85,6 +60,8 @@ describe('default', () => { }); it('works', async () => { + const theEndpoint = theClient.app.endpoints.get('users'); + const theOperation = theClient.app.operations.get('fetch'); // TODO create wrapper for fetch's Response here // // should we create a helper object to process client-side received response from server's sent response? @@ -92,7 +69,7 @@ describe('default', () => { // the motivation is to remove the manual deserialization from the client (provide serialization on the response // object so as the client is not limited to .text(), .json(), .arrayBuffer() etc) const responseRaw = await theClient - .at(theRawEndpoint) + .at(theEndpoint) .makeRequest( theOperation .search({ @@ -102,11 +79,13 @@ describe('default', () => { const response = ResourceItemFetchedResponse.fromFetchResponse(responseRaw); - expect(response).toHaveProperty('statusCode', 200); + expect(response).toHaveProperty('statusCode', statusCodes.HTTP_STATUS_OK); expect(response).toHaveProperty('statusMessage', 'Resource Collection Fetched'); }); it('works for items', async () => { + const theEndpoint = theClient.app.endpoints.get('users'); + const theOperation = theClient.app.operations.get('fetch'); // TODO create wrapper for fetch's Response here // // should we create a helper object to process client-side received response from server's sent response? @@ -114,7 +93,7 @@ describe('default', () => { // the motivation is to remove the manual deserialization from the client (provide serialization on the response // object so as the client is not limited to .text(), .json(), .arrayBuffer() etc) const responseRaw = await theClient - .at(theRawEndpoint, { resourceId: 3 }) + .at(theEndpoint, { resourceId: 3 }) // TODO how to inject extra data (e.g. headers, body) in the operation (e.g. auth)? .makeRequest( theOperation @@ -125,7 +104,7 @@ describe('default', () => { const response = ResourceItemFetchedResponse.fromFetchResponse(responseRaw); - expect(response).toHaveProperty('statusCode', 200); + expect(response).toHaveProperty('statusCode', statusCodes.HTTP_STATUS_OK); expect(response).toHaveProperty('statusMessage', 'Resource Item Fetched'); }); }); diff --git a/packages/examples/http-resource-server/test/fixtures/data-source.ts b/packages/examples/http-resource-server/test/fixtures/data-source.ts new file mode 100644 index 0000000..d5dfde8 --- /dev/null +++ b/packages/examples/http-resource-server/test/fixtures/data-source.ts @@ -0,0 +1,14 @@ +import {vi} from 'vitest'; + +export const createDummyDataSource = () => ({ + create: vi.fn(async (data) => data), + getById: vi.fn(async () => ({})), + delete: vi.fn(), + emplace: vi.fn(async () => [{}, { isCreated: false }]), + getMultiple: vi.fn(async () => []), + getSingle: vi.fn(async () => ({})), + getTotalCount: vi.fn(async () => 1), + newId: vi.fn(async () => 1), + patch: vi.fn(async (id, data) => ({ ...data, id })), + initialize: vi.fn(async () => {}), +}); diff --git a/packages/examples/http-resource-server/test/index.test.ts b/packages/examples/http-resource-server/test/index.test.ts deleted file mode 100644 index 441ca94..0000000 --- a/packages/examples/http-resource-server/test/index.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import add from '../src'; - -describe('blah', () => { - it('works', () => { - expect(add(1, 1)).toEqual(2); - }); -}); diff --git a/packages/extenders/http/package.json b/packages/extenders/http/package.json index 8f40169..cf05179 100644 --- a/packages/extenders/http/package.json +++ b/packages/extenders/http/package.json @@ -45,5 +45,35 @@ }, "dependencies": { "@modal-sh/yasumi": "workspace:*" + }, + "exports": { + "./backend": { + "development": { + "require": "./dist/cjs/development/backend.js", + "import": "./dist/esm/development/backend.js" + }, + "require": "./dist/cjs/production/backend.js", + "import": "./dist/esm/production/backend.js", + "types": "./dist/types/backend/index.d.ts" + }, + "./client": { + "development": { + "require": "./dist/cjs/development/client.js", + "import": "./dist/esm/development/client.js" + }, + "require": "./dist/cjs/production/client.js", + "import": "./dist/esm/production/client.js", + "types": "./dist/types/client/index.d.ts" + } + }, + "typesVersions": { + "*": { + "backend": [ + "./dist/types/backend/index.d.ts" + ], + "client": [ + "./dist/types/client/index.d.ts" + ] + } } } diff --git a/packages/extenders/http/test/error-handling.test.ts b/packages/extenders/http/test/error-handling.test.ts deleted file mode 100644 index 3212819..0000000 --- a/packages/extenders/http/test/error-handling.test.ts +++ /dev/null @@ -1,191 +0,0 @@ -import {describe, it, expect, beforeAll, afterAll} from 'vitest'; -import { - App, - app, - Endpoint, - endpoint, - Operation, - operation, - statusCodes, - validation as v, -} from '../../../core/src/common'; -import {Backend, backend, DataSource, Server} from '../../../core/src/backend'; -import {Client} from '../../../core/src/client'; -import {server} from '../../../core/src/extenders/http/backend'; -import {client} from '../../../core/src/extenders/http/client'; - -const op = operation({ - name: 'create' as const, - method: 'POST' as const, -}); - -const e = endpoint({ - name: 'e' as const, - schema: v.object({}), -}) - .can('create'); - -const a = app({ - name: 'foo' as const, -}) - .operation(op) - .endpoint(e); - -describe('app', () => { - let theApp: App; - let theBackend: Backend; - let theClient: Client; - let theDataSource: DataSource; - let theEndpoint: Endpoint; - let theServer: Server; - let theOperation: Operation; - - beforeAll(async () => { - theOperation = operation({ - name: 'fetch' as const, - }); - - theEndpoint = endpoint({ - name: 'users' as const, - schema: v.object({ - username: v.string() - }), - }) - .param('resourceId') - .can('fetch'); - - theApp = app({ - name: 'foo' as const - }) - .operation(theOperation) - .endpoint(theEndpoint); - - theBackend = backend({ - app: theApp - }); - - // add recipes function that will wrap app and backend to add operations and implement them, and will return a set - // of operations. - // - // recipes should have a backend and client counterpart. - theBackend.implementOperation('fetch', async (ctx) => { - // noop - }); - - theServer = server({ - backend: theBackend - }); - - const connectionParams = { - port: 3000, - }; - - await theServer.serve(connectionParams); - - theClient = client({ - app: theApp - }); - - await theClient.connect(connectionParams); - }); - - afterAll(async () => { - await theClient.disconnect(); - await theServer.close(); - }); - - it('works', async () => { - const response = await theClient - .at(theEndpoint, { resourceId: 3 }) - .makeRequest(theOperation); - - expect(response).toHaveProperty('status', statusCodes.HTTP_STATUS_UNPROCESSABLE_ENTITY); - }); -}); - -// const theEndpoint = endpoint({ -// schema: v.object({ -// username: v.string(), -// }), -// }) -// .can('patch') -// .can('query'); -// -// const canPatch = operation({ -// name: 'patch' as const, -// args: [ -// 'merge', -// 'delta', -// ] as const, -// // TODO define resource-specific stuff, like defining URL params, etc. -// }); -// -// const canFetch = operation({ -// name: 'fetch' as const, -// args: [ -// 'item', -// 'default', -// ] as const, -// }); -// -// const canQuery = operation({ -// name: 'query' as const, -// }); -// -// const canCreate = operation({ -// name: 'create' as const, -// }); -// -// const canEmplace = operation({ -// name: 'emplace' as const, -// }); -// -// const canDelete = operation({ -// name: 'delete' as const, -// }); -// -// export const theApp = app({ -// name: 'foo' as const, -// }) -// .operation(canQuery) -// .operation(canPatch) -// .operation(canFetch) -// .operation(canCreate) -// .operation(canEmplace) -// .operation(canDelete) -// .endpoint(theEndpoint); -// // -// // const bootstrap = async (theApp: App) => { -// // if (typeof window === 'undefined') { -// // const { backend } = await import('./backend'); -// // const theBackend = backend({ -// // app: theApp -// // }); -// // } -// // }; -// -// const b = backend({ -// app: theApp, -// }) -// .implementOperation({ -// operation: 'fetch' as const, -// implementation: ({ -// endpoint, -// arg -// }) => { -// switch (arg) { -// case 'default': { -// -// } -// } -// }, -// }); -// -// const s = server({ -// backend: b, -// }) -// .serve({ -// host: '0.0.0.0', -// port: 3000, -// basePath: '/api' -// }); diff --git a/packages/recipes/resource/package.json b/packages/recipes/resource/package.json index db446f6..eb180fc 100644 --- a/packages/recipes/resource/package.json +++ b/packages/recipes/resource/package.json @@ -45,5 +45,22 @@ }, "dependencies": { "@modal-sh/yasumi": "workspace:*" + }, + "types": "./dist/types/index.d.ts", + "main": "./dist/cjs/production/index.js", + "module": "./dist/esm/production/index.js", + "exports": { + ".": { + "development": { + "require": "./dist/cjs/development/index.js", + "import": "./dist/esm/development/index.js" + }, + "require": "./dist/cjs/production/index.js", + "import": "./dist/esm/production/index.js", + "types": "./dist/types/index.d.ts" + } + }, + "typesVersions": { + "*": {} } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 649ab06..36a2fe1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -76,6 +76,16 @@ importers: version: 1.2.0(@types/node@20.11.0) packages/examples/http-resource-server: + dependencies: + '@modal-sh/yasumi': + specifier: workspace:* + version: link:../../core + '@modal-sh/yasumi-extender-http': + specifier: workspace:* + version: link:../../extenders/http + '@modal-sh/yasumi-recipe-resource': + specifier: workspace:* + version: link:../../recipes/resource devDependencies: '@types/node': specifier: ^20.11.0