From 8f05a07a67cd02935080784b65c93ff56aeab781 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Thu, 4 Apr 2024 09:45:09 +0800 Subject: [PATCH] Refactor http server Define http server exports in their own directory. --- package.json | 2 +- pridepack.json | 7 +- src/backend/common.ts | 38 ++++- src/backend/core.ts | 2 +- .../{http/server.ts => servers/http/core.ts} | 131 ++++++------------ .../decorators/backend/content-negotiation.ts | 6 +- .../http/decorators/backend/index.ts | 4 +- .../http/decorators/backend/resource.ts | 6 +- .../http/decorators/method/index.ts | 2 +- .../http/decorators/url/base-path.ts | 4 +- .../{ => servers}/http/decorators/url/host.ts | 4 +- .../http/decorators/url/index.ts | 6 +- .../http/decorators/url/scheme.ts | 4 +- .../{ => servers}/http/handlers/default.ts | 6 +- .../{ => servers}/http/handlers/resource.ts | 3 +- src/backend/servers/http/index.ts | 1 + src/backend/servers/http/response.ts | 36 +++++ src/backend/{ => servers}/http/utils.ts | 8 ++ src/common/language.ts | 2 + src/index.ts | 2 - test/e2e/features.test.ts | 100 +++++++++++++ test/e2e/http.test.ts | 32 ++--- test/fixtures.ts | 16 +++ 23 files changed, 279 insertions(+), 143 deletions(-) rename src/backend/{http/server.ts => servers/http/core.ts} (86%) rename src/backend/{ => servers}/http/decorators/backend/content-negotiation.ts (88%) rename src/backend/{ => servers}/http/decorators/backend/index.ts (79%) rename src/backend/{ => servers}/http/decorators/backend/resource.ts (80%) rename src/backend/{ => servers}/http/decorators/method/index.ts (72%) rename src/backend/{ => servers}/http/decorators/url/base-path.ts (67%) rename src/backend/{ => servers}/http/decorators/url/host.ts (65%) rename src/backend/{ => servers}/http/decorators/url/index.ts (86%) rename src/backend/{ => servers}/http/decorators/url/scheme.ts (66%) rename src/backend/{ => servers}/http/handlers/default.ts (90%) rename src/backend/{ => servers}/http/handlers/resource.ts (98%) create mode 100644 src/backend/servers/http/index.ts create mode 100644 src/backend/servers/http/response.ts rename src/backend/{ => servers}/http/utils.ts (82%) create mode 100644 test/e2e/features.test.ts create mode 100644 test/fixtures.ts diff --git a/package.json b/package.json index 887d9a3..6ff2344 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "yasumi", + "name": "@modal-sh/yasumi", "version": "0.0.0", "files": [ "dist", diff --git a/pridepack.json b/pridepack.json index 0bc7a8f..52d656d 100644 --- a/pridepack.json +++ b/pridepack.json @@ -1,3 +1,8 @@ { - "target": "es2018" + "target": "es2018", + "entrypoints": { + ".": "src/index.ts", + "./backend": "src/backend/index.ts", + "./client": "src/client/index.ts" + } } diff --git a/src/backend/common.ts b/src/backend/common.ts index 0ebed9b..344c2a1 100644 --- a/src/backend/common.ts +++ b/src/backend/common.ts @@ -1,7 +1,6 @@ -import {ApplicationState, ContentNegotiation, Resource} from '../common'; -import {DataSource} from './data-source'; import {BaseSchema} from 'valibot'; -import {Middleware} from './http/server'; +import {ApplicationState, ContentNegotiation, Language, LanguageStatusMessageMap, Resource} from '../common'; +import {DataSource} from './data-source'; export interface BackendState { app: ApplicationState; @@ -15,6 +14,28 @@ export interface BackendState { export interface RequestContext {} +export interface Middleware {} + +export class MiddlewareError extends Error {} + +export interface MiddlewareResponseErrorParams extends Omit { + cause?: unknown; +} + +export abstract class MiddlewareResponseError extends MiddlewareError implements Response { + readonly statusMessage: Response['statusMessage']; + readonly statusCode: Response['statusCode']; + readonly headers: Response['headers']; + + constructor(statusMessage: keyof Language['statusMessages'], params: MiddlewareResponseErrorParams) { + super(statusMessage, { cause: params.cause }); + this.statusCode = params.statusCode; + this.headers = params.headers; + this.statusMessage = statusMessage; + } +} + + export type RequestDecorator = (req: RequestContext) => RequestContext | Promise; export type ParamRequestDecorator = []> = (...args: Params) => RequestDecorator; @@ -27,3 +48,14 @@ export interface AllowedMiddlewareSpecification, resourceId?: string) => BaseSchema; allowed: (resource: Resource) => boolean; } + +export interface Response { + // type of response + statusCode: number; + + // description of response + statusMessage?: keyof LanguageStatusMessageMap; + + // metadata of the response + headers?: Record; +} diff --git a/src/backend/core.ts b/src/backend/core.ts index e7c5cea..c48246f 100644 --- a/src/backend/core.ts +++ b/src/backend/core.ts @@ -1,6 +1,6 @@ import {ApplicationState, FALLBACK_CHARSET, FALLBACK_LANGUAGE, FALLBACK_MEDIA_TYPE, Resource} from '../common'; import http from 'http'; -import {createServer, CreateServerParams} from './http/server'; +import {createServer, CreateServerParams} from './servers/http'; import https from 'https'; import {BackendState} from './common'; import {DataSource} from './data-source'; diff --git a/src/backend/http/server.ts b/src/backend/servers/http/core.ts similarity index 86% rename from src/backend/http/server.ts rename to src/backend/servers/http/core.ts index 76700e4..15e8150 100644 --- a/src/backend/http/server.ts +++ b/src/backend/servers/http/core.ts @@ -1,9 +1,15 @@ import http from 'http'; -import {AllowedMiddlewareSpecification, BackendState, RequestContext} from '../common'; -import {Language, Resource, LanguageStatusMessageMap} from '../../common'; import https from 'https'; import {constants} from 'http2'; import * as v from 'valibot'; +import { + AllowedMiddlewareSpecification, + BackendState, + Middleware, + RequestContext, + Response +} from '../../common'; +import {Resource} from '../../../common'; import { handleGetRoot, handleOptions, } from './handlers/default'; @@ -15,79 +21,11 @@ import { handleGetItem, handlePatchItem, } from './handlers/resource'; -import {getBody} from './utils'; +import {getBody, isTextMediaType} from './utils'; import {decorateRequestWithBackend} from './decorators/backend'; import {decorateRequestWithMethod} from './decorators/method'; import {decorateRequestWithUrl} from './decorators/url'; - -declare module '../common' { - interface RequestContext extends http.IncomingMessage { - body?: unknown; - } -} - -export interface Response { - statusCode: number; - - statusMessage?: keyof LanguageStatusMessageMap; - - headers?: Record; -} - -interface ResponseParams { - statusCode: Response['statusCode']; - statusMessage?: Response['statusMessage']; - headers?: Response['headers']; -} - -export class MiddlewareError extends Error {} - -interface PlainResponseParams extends ResponseParams { - body?: T; -} - -interface HttpMiddlewareErrorParams extends Omit, 'statusMessage'> { - cause?: unknown -} - -export class PlainResponse implements Response { - readonly statusCode: Response['statusCode']; - - readonly statusMessage?: keyof LanguageStatusMessageMap; - - readonly headers: Response['headers']; - - readonly body?: T; - - constructor(args: PlainResponseParams) { - this.statusCode = args.statusCode; - this.statusMessage = args.statusMessage; - this.headers = args.headers; - this.body = args.body; - } -} - -export class HttpMiddlewareError extends MiddlewareError { - readonly response: PlainResponse; - - constructor(statusMessage: keyof Language['statusMessages'], params: HttpMiddlewareErrorParams) { - super(statusMessage, { cause: params.cause }); - this.response = new PlainResponse({ - ...params, - statusMessage, - }); - } -} - -export interface CreateServerParams { - basePath?: string; - host?: string; - cert?: string; - key?: string; - requestTimeout?: number; - // CQRS - streamResponses?: boolean; -} +import {HttpMiddlewareError, PlainResponse} from './response'; type RequiredResource = Required>['resource']; @@ -99,8 +37,14 @@ interface ResourceRequestContext extends Omit { resource: ResourceWithDataSource; } -export interface Middleware { - (req: Req): undefined | Response | Promise; +declare module '../../common' { + interface RequestContext extends http.IncomingMessage { + body?: unknown; + } + + interface Middleware { + (req: Req): undefined | Response | Promise; + } } const constructPostSchema = (resource: Resource) => { @@ -143,7 +87,6 @@ const constructPatchSchema = (resource: Resource) => : schema ); }; - // TODO add a way to define custom middlewares const defaultCollectionMiddlewares: AllowedMiddlewareSpecification[] = [ { @@ -184,6 +127,16 @@ const defaultItemMiddlewares: AllowedMiddlewareSpecification[] = [ }, ]; +export interface CreateServerParams { + basePath?: string; + host?: string; + cert?: string; + key?: string; + requestTimeout?: number; + // CQRS + streamResponses?: boolean; +} + export const createServer = (backendState: BackendState, serverParams = {} as CreateServerParams) => { const isHttps = 'key' in serverParams && 'cert' in serverParams; @@ -203,14 +156,6 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr decorateRequestWithBackend(backendState), ]; - const isTextMediaType = (mediaType: string) => ( - mediaType.startsWith('text/') - || [ - 'application/json', - 'application/xml' - ].includes(mediaType) - ); - const handleMiddlewares = async (currentHandlerState: Awaited>, currentMiddleware: AllowedMiddlewareSpecification, req: ResourceRequestContext) => { const { method: middlewareMethod, middleware, constructBodySchema} = currentMiddleware; const effectiveMethod = req.method === 'HEAD' ? 'GET' : req.method; @@ -350,8 +295,9 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr return await effectiveRequestDecorators.reduce( async (resultRequestPromise, decorator) => { const resultRequest = await resultRequestPromise; - - return await decorator(resultRequest); + const decoratedRequest = await decorator(resultRequest); + // TODO log decorators + return decoratedRequest; }, Promise.resolve(reqRaw as RequestContext) ); @@ -373,11 +319,11 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr middlewareState = await processRequestFn(resourceReq) as any; // TODO fix this } catch (processRequestErrRaw) { const finalErr = processRequestErrRaw as HttpMiddlewareError; - const headers = finalErr.response.headers ?? {}; + const headers = finalErr.headers ?? {}; let encoded: Buffer | undefined; let serialized; try { - serialized = typeof finalErr.response.body !== 'undefined' ? resourceReq.backend.cn.mediaType.serialize(finalErr.response.body) : undefined; + serialized = typeof finalErr.body !== 'undefined' ? resourceReq.backend.cn.mediaType.serialize(finalErr.body) : undefined; } catch (cause) { res.statusMessage = resourceReq.backend.cn.language.statusMessages['unableToSerializeResponse']?.replace( /\$RESOURCE/g, @@ -401,9 +347,9 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr `charset=${resourceReq.backend.cn.charset.name}`, ].join('; '); - const statusMessageKey = finalErr.response.statusMessage ? resourceReq.backend.cn.language.statusMessages[finalErr.response.statusMessage] : undefined; + const statusMessageKey = finalErr.statusMessage ? resourceReq.backend.cn.language.statusMessages[finalErr.statusMessage] : undefined; res.statusMessage = statusMessageKey?.replace(/\$RESOURCE/g, resourceReq.resource!.state.itemName) ?? ''; - res.writeHead(finalErr.response.statusCode, headers); + res.writeHead(finalErr.statusCode, headers); if (typeof encoded !== 'undefined') { res.end(encoded); return; @@ -503,7 +449,12 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr return; } - throw new Error('Not implemented'); + res.statusMessage = reqRaw.backend.cn.language.statusMessages.notImplemented.replace(/\$RESOURCE/g, + reqRaw.resource!.state.itemName) ?? ''; + res.writeHead(constants.HTTP_STATUS_NOT_IMPLEMENTED, { + 'Content-Language': reqRaw.backend.cn.language.name, + }); + res.end(); }; server.on('request', handleRequest); diff --git a/src/backend/http/decorators/backend/content-negotiation.ts b/src/backend/servers/http/decorators/backend/content-negotiation.ts similarity index 88% rename from src/backend/http/decorators/backend/content-negotiation.ts rename to src/backend/servers/http/decorators/backend/content-negotiation.ts index 55081e8..6896647 100644 --- a/src/backend/http/decorators/backend/content-negotiation.ts +++ b/src/backend/servers/http/decorators/backend/content-negotiation.ts @@ -1,8 +1,8 @@ -import {ContentNegotiation} from '../../../../common'; -import {RequestDecorator} from '../../../common'; +import {ContentNegotiation} from '../../../../../common'; +import {RequestDecorator} from '../../../../common'; import Negotiator from 'negotiator'; -declare module '../../../common' { +declare module '../../../../common' { interface RequestContext { cn: ContentNegotiation; } diff --git a/src/backend/http/decorators/backend/index.ts b/src/backend/servers/http/decorators/backend/index.ts similarity index 79% rename from src/backend/http/decorators/backend/index.ts rename to src/backend/servers/http/decorators/backend/index.ts index c1c7132..4942219 100644 --- a/src/backend/http/decorators/backend/index.ts +++ b/src/backend/servers/http/decorators/backend/index.ts @@ -1,8 +1,8 @@ -import {BackendState, ParamRequestDecorator} from '../../../common'; +import {BackendState, ParamRequestDecorator} from '../../../../common'; import {decorateRequestWithContentNegotiation} from './content-negotiation'; import {decorateRequestWithResource} from './resource'; -declare module '../../../common' { +declare module '../../../../common' { interface RequestContext { backend: BackendState; } diff --git a/src/backend/http/decorators/backend/resource.ts b/src/backend/servers/http/decorators/backend/resource.ts similarity index 80% rename from src/backend/http/decorators/backend/resource.ts rename to src/backend/servers/http/decorators/backend/resource.ts index 05b4f3b..b3a300e 100644 --- a/src/backend/http/decorators/backend/resource.ts +++ b/src/backend/servers/http/decorators/backend/resource.ts @@ -1,7 +1,7 @@ -import {RequestDecorator} from '../../../common'; -import {Resource} from '../../../../common'; +import {Resource} from '../../../../../common'; +import {RequestDecorator} from '../../../../common'; -declare module '../../../common' { +declare module '../../../../common' { interface RequestContext { resource?: Resource; resourceId?: string; diff --git a/src/backend/http/decorators/method/index.ts b/src/backend/servers/http/decorators/method/index.ts similarity index 72% rename from src/backend/http/decorators/method/index.ts rename to src/backend/servers/http/decorators/method/index.ts index 520bcc4..a19084d 100644 --- a/src/backend/http/decorators/method/index.ts +++ b/src/backend/servers/http/decorators/method/index.ts @@ -1,4 +1,4 @@ -import {RequestDecorator} from '../../../common'; +import {RequestDecorator} from '../../../../common'; export const decorateRequestWithMethod: RequestDecorator = (req) => { req.method = req.method?.trim().toUpperCase() ?? ''; diff --git a/src/backend/http/decorators/url/base-path.ts b/src/backend/servers/http/decorators/url/base-path.ts similarity index 67% rename from src/backend/http/decorators/url/base-path.ts rename to src/backend/servers/http/decorators/url/base-path.ts index 4182cf0..3243a8a 100644 --- a/src/backend/http/decorators/url/base-path.ts +++ b/src/backend/servers/http/decorators/url/base-path.ts @@ -1,6 +1,6 @@ -import {ParamRequestDecorator} from '../../../common'; +import {ParamRequestDecorator} from '../../../../common'; -declare module '../../../common' { +declare module '../../../../common' { interface RequestContext { basePath: string; } diff --git a/src/backend/http/decorators/url/host.ts b/src/backend/servers/http/decorators/url/host.ts similarity index 65% rename from src/backend/http/decorators/url/host.ts rename to src/backend/servers/http/decorators/url/host.ts index d6fac67..1755a9b 100644 --- a/src/backend/http/decorators/url/host.ts +++ b/src/backend/servers/http/decorators/url/host.ts @@ -1,6 +1,6 @@ -import {ParamRequestDecorator} from '../../../common'; +import {ParamRequestDecorator} from '../../../../common'; -declare module '../../../common' { +declare module '../../../../common' { interface RequestContext { host: string; } diff --git a/src/backend/http/decorators/url/index.ts b/src/backend/servers/http/decorators/url/index.ts similarity index 86% rename from src/backend/http/decorators/url/index.ts rename to src/backend/servers/http/decorators/url/index.ts index dc977f0..123fc05 100644 --- a/src/backend/http/decorators/url/index.ts +++ b/src/backend/servers/http/decorators/url/index.ts @@ -1,10 +1,10 @@ -import {ParamRequestDecorator} from '../../../common'; -import {CreateServerParams} from '../../server'; +import {ParamRequestDecorator} from '../../../../common'; +import {CreateServerParams} from '../../index'; import {decorateRequestWithScheme} from './scheme'; import {decorateRequestWithHost} from './host'; import {decorateRequestWithBasePath} from './base-path'; -declare module '../../../common' { +declare module '../../../../common' { interface RequestContext { rawUrl?: string; query: URLSearchParams; diff --git a/src/backend/http/decorators/url/scheme.ts b/src/backend/servers/http/decorators/url/scheme.ts similarity index 66% rename from src/backend/http/decorators/url/scheme.ts rename to src/backend/servers/http/decorators/url/scheme.ts index 784db7c..ea91944 100644 --- a/src/backend/http/decorators/url/scheme.ts +++ b/src/backend/servers/http/decorators/url/scheme.ts @@ -1,6 +1,6 @@ -import {ParamRequestDecorator} from '../../../common'; +import {ParamRequestDecorator} from '../../../../common'; -declare module '../../../common' { +declare module '../../../../common' { interface RequestContext { scheme: string; } diff --git a/src/backend/http/handlers/default.ts b/src/backend/servers/http/handlers/default.ts similarity index 90% rename from src/backend/http/handlers/default.ts rename to src/backend/servers/http/handlers/default.ts index aadcce5..c2d7dec 100644 --- a/src/backend/http/handlers/default.ts +++ b/src/backend/servers/http/handlers/default.ts @@ -1,7 +1,7 @@ -import {HttpMiddlewareError, Middleware, PlainResponse} from '../server'; -import {LinkMap} from '../utils'; import {constants} from 'http2'; -import {AllowedMiddlewareSpecification} from '../../common'; +import {AllowedMiddlewareSpecification, Middleware} from '../../../common'; +import {LinkMap} from '../utils'; +import {PlainResponse, HttpMiddlewareError} from '../response'; export const handleGetRoot: Middleware = (req) => { const { backend, basePath } = req; diff --git a/src/backend/http/handlers/resource.ts b/src/backend/servers/http/handlers/resource.ts similarity index 98% rename from src/backend/http/handlers/resource.ts rename to src/backend/servers/http/handlers/resource.ts index b0d2509..e8a3b01 100644 --- a/src/backend/http/handlers/resource.ts +++ b/src/backend/servers/http/handlers/resource.ts @@ -1,6 +1,7 @@ import { constants } from 'http2'; import * as v from 'valibot'; -import {HttpMiddlewareError, PlainResponse, Middleware} from '../server'; +import {Middleware} from '../../../common'; +import {HttpMiddlewareError, PlainResponse} from '../response'; export const handleGetCollection: Middleware = async (req) => { const { query, resource, backend } = req; diff --git a/src/backend/servers/http/index.ts b/src/backend/servers/http/index.ts new file mode 100644 index 0000000..4b0e041 --- /dev/null +++ b/src/backend/servers/http/index.ts @@ -0,0 +1 @@ +export * from './core'; diff --git a/src/backend/servers/http/response.ts b/src/backend/servers/http/response.ts new file mode 100644 index 0000000..4ec108a --- /dev/null +++ b/src/backend/servers/http/response.ts @@ -0,0 +1,36 @@ +import {Language, LanguageStatusMessageMap} from '../../../common'; +import {MiddlewareResponseError, Response} from '../../common'; + +interface PlainResponseParams extends Response { + body?: T; +} + +interface HttpMiddlewareErrorParams extends Omit, 'statusMessage'> { + cause?: unknown +} + +export class HttpMiddlewareError extends MiddlewareResponseError implements PlainResponseParams { + body?: T; + + constructor(statusMessage: keyof Language['statusMessages'], params: HttpMiddlewareErrorParams) { + super(statusMessage, params); + this.body = params.body; + } +} + +export class PlainResponse implements Response { + readonly statusCode: Response['statusCode']; + + readonly statusMessage?: keyof LanguageStatusMessageMap; + + readonly headers: Response['headers']; + + readonly body?: T; + + constructor(args: PlainResponseParams) { + this.statusCode = args.statusCode; + this.statusMessage = args.statusMessage; + this.headers = args.headers; + this.body = args.body; + } +} diff --git a/src/backend/http/utils.ts b/src/backend/servers/http/utils.ts similarity index 82% rename from src/backend/http/utils.ts rename to src/backend/servers/http/utils.ts index bf57b6d..68a5d04 100644 --- a/src/backend/http/utils.ts +++ b/src/backend/servers/http/utils.ts @@ -1,5 +1,13 @@ import {IncomingMessage} from 'http'; +export const isTextMediaType = (mediaType: string) => ( + mediaType.startsWith('text/') + || [ + 'application/json', + 'application/xml' + ].includes(mediaType) +); + export const getBody = ( req: IncomingMessage, ) => new Promise((resolve, reject) => { diff --git a/src/common/language.ts b/src/common/language.ts index 4292a09..5da416a 100644 --- a/src/common/language.ts +++ b/src/common/language.ts @@ -33,6 +33,7 @@ export const LANGUAGE_DEFAULT_STATUS_MESSAGE_KEYS = [ 'resourcePatched', 'resourceCreated', 'resourceReplaced', + 'notImplemented', ] as const; export type LanguageDefaultStatusMessageKey = typeof LANGUAGE_DEFAULT_STATUS_MESSAGE_KEYS[number]; @@ -90,6 +91,7 @@ export const FALLBACK_LANGUAGE = { unableToEmplaceResource: 'Unable To Emplace $RESOURCE', resourceIdNotGiven: '$RESOURCE ID Not Given', unableToCreateResource: 'Unable To Create $RESOURCE', + notImplemented: 'Not Implemented' }, bodies: { languageNotAcceptable: [], diff --git a/src/index.ts b/src/index.ts index 7219eba..d0b9323 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1 @@ export * from './common'; - -export * as dataSources from './backend/data-sources'; diff --git a/test/e2e/features.test.ts b/test/e2e/features.test.ts new file mode 100644 index 0000000..d92ff22 --- /dev/null +++ b/test/e2e/features.test.ts @@ -0,0 +1,100 @@ +import {describe, afterAll, afterEach, beforeAll, beforeEach, it} from 'vitest'; +import {mkdtemp, rm} from 'fs/promises'; +import {join} from 'path'; +import {tmpdir} from 'os'; +import {application, resource, Resource, validation as v} from '../../src'; +import {dataSources} from '../../src/backend'; +import {Server} from 'http'; +import {autoIncrement} from '../fixtures'; + +const PORT = 3001; +const HOST = '127.0.0.1'; +const BASE_PATH = '/api'; +const ACCEPT = 'application/json'; +const ACCEPT_LANGUAGE = 'en'; +const CONTENT_TYPE_CHARSET = 'utf-8'; +const CONTENT_TYPE = ACCEPT; + +describe('decorators', () => { + let baseDir: string; + beforeAll(async () => { + try { + baseDir = await mkdtemp(join(tmpdir(), 'yasumi-')); + } catch { + // noop + } + }); + afterAll(async () => { + try { + await rm(baseDir, { + recursive: true, + }); + } catch { + // noop + } + }); + + let Piano: Resource; + beforeEach(() => { + Piano = resource(v.object( + { + brand: v.string() + }, + v.never() + )) + .name('Piano' as const) + .route('pianos' as const) + .id('id' as const, { + generationStrategy: autoIncrement, + serialize: (id) => id?.toString() ?? '0', + deserialize: (id) => Number.isFinite(Number(id)) ? Number(id) : 0, + schema: v.number(), + }); + }); + + let server: Server; + beforeEach(() => { + const app = application({ + name: 'piano-service', + }) + .resource(Piano); + + const backend = app + .createBackend({ + dataSource: new dataSources.jsonlFile.DataSource(baseDir), + }) + .throwsErrorOnDeletingNotFound(); + + server = backend.createHttpServer({ + basePath: BASE_PATH + }); + + return new Promise((resolve, reject) => { + server.on('error', (err) => { + reject(err); + }); + + server.on('listening', () => { + resolve(); + }); + + server.listen({ + port: PORT + }); + }); + }); + + afterEach(() => new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + reject(err); + } + + resolve(); + }); + })); + + it('decorates requests', () => { + + }); +}); diff --git a/test/e2e/http.test.ts b/test/e2e/http.test.ts index 127b082..0623211 100644 --- a/test/e2e/http.test.ts +++ b/test/e2e/http.test.ts @@ -20,32 +20,17 @@ import { } from 'path'; import {request, Server} from 'http'; import {constants} from 'http2'; -import {DataSource} from '../../src/backend/data-source'; -import { dataSources } from '../../src/backend'; +import {BackendBuilder, dataSources} from '../../src/backend'; import { application, resource, validation as v, Resource } from '../../src'; +import { autoIncrement } from '../fixtures'; const PORT = 3000; const HOST = '127.0.0.1'; +const BASE_PATH = '/api'; const ACCEPT = 'application/json'; const ACCEPT_LANGUAGE = 'en'; const CONTENT_TYPE_CHARSET = 'utf-8'; const CONTENT_TYPE = ACCEPT; -const BASE_PATH = '/api'; - -const autoIncrement = async (dataSource: DataSource) => { - const data = await dataSource.getMultiple() as Record[]; - - const highestId = data.reduce( - (highestId, d) => (Number(d.id) > highestId ? Number(d.id) : highestId), - -Infinity - ); - - if (Number.isFinite(highestId)) { - return (highestId + 1); - } - - return 1; -}; describe('yasumi HTTP', () => { let baseDir: string; @@ -84,6 +69,7 @@ describe('yasumi HTTP', () => { }); }); + let backend: BackendBuilder; let server: Server; beforeEach(() => { const app = application({ @@ -91,11 +77,9 @@ describe('yasumi HTTP', () => { }) .resource(Piano); - const backend = app - .createBackend({ - dataSource: new dataSources.jsonlFile.DataSource(baseDir), - }) - .throwsErrorOnDeletingNotFound(); + backend = app.createBackend({ + dataSource: new dataSources.jsonlFile.DataSource(baseDir), + }); server = backend.createHttpServer({ basePath: BASE_PATH @@ -1062,10 +1046,12 @@ describe('yasumi HTTP', () => { beforeEach(() => { Piano.canDelete(); + backend.throwsErrorOnDeletingNotFound(); }); afterEach(() => { Piano.canDelete(false); + backend.throwsErrorOnDeletingNotFound(false); }); it('throws on item not found', () => { diff --git a/test/fixtures.ts b/test/fixtures.ts new file mode 100644 index 0000000..93c1f24 --- /dev/null +++ b/test/fixtures.ts @@ -0,0 +1,16 @@ +import {DataSource} from '../src/backend/data-source'; + +export const autoIncrement = async (dataSource: DataSource) => { + const data = await dataSource.getMultiple() as Record[]; + + const highestId = data.reduce( + (highestId, d) => (Number(d.id) > highestId ? Number(d.id) : highestId), + -Infinity + ); + + if (Number.isFinite(highestId)) { + return (highestId + 1); + } + + return 1; +};