Browse Source

Update tests

Use example project to setup integration tests among the modules.
refactor/new-arch
TheoryOfNekomata 5 months ago
parent
commit
de6cdd3d7e
9 changed files with 136 additions and 246 deletions
  1. +5
    -0
      packages/examples/http-resource-server/package.json
  2. +34
    -0
      packages/examples/http-resource-server/src/setup.ts
  3. +26
    -47
      packages/examples/http-resource-server/test/default.test.ts
  4. +14
    -0
      packages/examples/http-resource-server/test/fixtures/data-source.ts
  5. +0
    -8
      packages/examples/http-resource-server/test/index.test.ts
  6. +30
    -0
      packages/extenders/http/package.json
  7. +0
    -191
      packages/extenders/http/test/error-handling.test.ts
  8. +17
    -0
      packages/recipes/resource/package.json
  9. +10
    -0
      pnpm-lock.yaml

+ 5
- 0
packages/examples/http-resource-server/package.json View File

@@ -41,5 +41,10 @@
"author": "TheoryOfNekomata <allan.crisostomo@outlook.com>", "author": "TheoryOfNekomata <allan.crisostomo@outlook.com>",
"publishConfig": { "publishConfig": {
"access": "restricted" "access": "restricted"
},
"dependencies": {
"@modal-sh/yasumi": "workspace:*",
"@modal-sh/yasumi-recipe-resource": "workspace:*",
"@modal-sh/yasumi-extender-http": "workspace:*"
} }
} }

+ 34
- 0
packages/examples/http-resource-server/src/setup.ts View File

@@ -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,
};
};

packages/extenders/http/test/default.test.ts → packages/examples/http-resource-server/test/default.test.ts View File

@@ -4,41 +4,34 @@ import {
afterAll, afterAll,
it, it,
expect, expect,
vi, Mock,
Mock,
} from 'vitest'; } from 'vitest';


import { import {
app,
Endpoint,
Operation,
composeRecipes,
statusCodes
} from '@modal-sh/yasumi'; } 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 {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 {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', () => { describe('default', () => {
let theClient: Client; let theClient: Client;
let theServer: Server; let theServer: Server;
let theRawEndpoint: Endpoint;
let theOperation: Operation;
let dataSource: Record<keyof DataSource, Mock>; let dataSource: Record<keyof DataSource, Mock>;


beforeAll(() => { 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(() => { afterAll(() => {
@@ -48,27 +41,9 @@ describe('default', () => {
beforeAll(async () => { beforeAll(async () => {
const { const {
app: theApp, 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); await theServer.serve(connectionParams);


@@ -85,6 +60,8 @@ describe('default', () => {
}); });


it('works', async () => { 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 // 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? // 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 // 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) // object so as the client is not limited to .text(), .json(), .arrayBuffer() etc)
const responseRaw = await theClient const responseRaw = await theClient
.at(theRawEndpoint)
.at(theEndpoint)
.makeRequest( .makeRequest(
theOperation theOperation
.search({ .search({
@@ -102,11 +79,13 @@ describe('default', () => {


const response = ResourceItemFetchedResponse.fromFetchResponse(responseRaw); 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'); expect(response).toHaveProperty('statusMessage', 'Resource Collection Fetched');
}); });


it('works for items', async () => { 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 // 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? // 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 // 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) // object so as the client is not limited to .text(), .json(), .arrayBuffer() etc)
const responseRaw = await theClient 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)? // TODO how to inject extra data (e.g. headers, body) in the operation (e.g. auth)?
.makeRequest( .makeRequest(
theOperation theOperation
@@ -125,7 +104,7 @@ describe('default', () => {


const response = ResourceItemFetchedResponse.fromFetchResponse(responseRaw); 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'); expect(response).toHaveProperty('statusMessage', 'Resource Item Fetched');
}); });
}); });

+ 14
- 0
packages/examples/http-resource-server/test/fixtures/data-source.ts View File

@@ -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 () => {}),
});

+ 0
- 8
packages/examples/http-resource-server/test/index.test.ts View File

@@ -1,8 +0,0 @@
import { describe, it, expect } from 'vitest';
import add from '../src';

describe('blah', () => {
it('works', () => {
expect(add(1, 1)).toEqual(2);
});
});

+ 30
- 0
packages/extenders/http/package.json View File

@@ -45,5 +45,35 @@
}, },
"dependencies": { "dependencies": {
"@modal-sh/yasumi": "workspace:*" "@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"
]
}
} }
} }

+ 0
- 191
packages/extenders/http/test/error-handling.test.ts View File

@@ -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'
// });

+ 17
- 0
packages/recipes/resource/package.json View File

@@ -45,5 +45,22 @@
}, },
"dependencies": { "dependencies": {
"@modal-sh/yasumi": "workspace:*" "@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": {
"*": {}
} }
} }

+ 10
- 0
pnpm-lock.yaml View File

@@ -76,6 +76,16 @@ importers:
version: 1.2.0(@types/node@20.11.0) version: 1.2.0(@types/node@20.11.0)


packages/examples/http-resource-server: 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: devDependencies:
'@types/node': '@types/node':
specifier: ^20.11.0 specifier: ^20.11.0


Loading…
Cancel
Save