diff --git a/packages/adapter-commons/package.json b/packages/adapter-commons/package.json index b03f3285b2..cddc70295d 100644 --- a/packages/adapter-commons/package.json +++ b/packages/adapter-commons/package.json @@ -33,7 +33,8 @@ "scripts": { "prepublish": "npm run compile", "compile": "shx rm -rf lib/ && tsc", - "test": "mocha --config ../../.mocharc.json --recursive test/**.test.ts test/**/*.test.ts" + "mocha": "mocha --config ../../.mocharc.json --recursive test/**.test.ts test/**/*.test.ts", + "test": "npm run compile && npm run mocha" }, "directories": { "lib": "lib" diff --git a/packages/express/src/declarations.ts b/packages/express/src/declarations.ts index 9031f04e99..aaf30b2dbd 100644 --- a/packages/express/src/declarations.ts +++ b/packages/express/src/declarations.ts @@ -23,8 +23,8 @@ export interface ExpressOverrides { listen(port: number, hostname: string, callback?: () => void): Promise; listen(port: number|string|any, callback?: () => void): Promise; listen(callback?: () => void): Promise; - close (): Promise; use: ExpressUseHandler; + server: http.Server; } export type Application = diff --git a/packages/express/src/index.ts b/packages/express/src/index.ts index 957dc0567d..0ff44d17f4 100644 --- a/packages/express/src/index.ts +++ b/packages/express/src/index.ts @@ -2,7 +2,6 @@ import express, { Express } from 'express'; import { Application as FeathersApplication, defaultServiceMethods } from '@feathersjs/feathers'; import { routing } from '@feathersjs/transport-commons'; import { createDebug } from '@feathersjs/commons'; -import http from 'http'; import { Application } from './declarations'; @@ -26,8 +25,7 @@ export default function feathersExpress (feathersApp?: Feather const app = expressApp as any as Application; const { use: expressUse, listen: expressListen } = expressApp as any; - const feathersUse = feathersApp.use; - let server:http.Server | undefined; + const { use: feathersUse, teardown: feathersTeardown } = feathersApp; Object.assign(app, { use (location: string & keyof S, ...rest: any[]) { @@ -71,25 +69,19 @@ export default function feathersExpress (feathersApp?: Feather }, async listen (...args: any[]) { - server = expressListen.call(this, ...args); + const server = expressListen.call(this, ...args); + this.server = server; await this.setup(server); debug('Feathers application listening'); return server; }, - async close () { - if ( server ) { - server.close(); - - await new Promise((resolve) => { - server.on('close', () => { resolve(true) }); - }) - } - - debug('Feathers application closing'); - await this.teardown(); + async teardown (server?: any) { + return feathersTeardown.call(this, server).then(() => + new Promise((resolve, reject) => this.server.close(e => e ? reject(e) : resolve(this))) + ); } } as Application); diff --git a/packages/express/test/authentication.test.ts b/packages/express/test/authentication.test.ts index 020d54a2bb..e75a9ca2a6 100644 --- a/packages/express/test/authentication.test.ts +++ b/packages/express/test/authentication.test.ts @@ -16,7 +16,6 @@ describe('@feathersjs/express/authentication', () => { const password = 'superexpress'; let app: express.Application; - let server: any; let user: any; let authResult: AuthenticationResult; @@ -26,7 +25,8 @@ describe('@feathersjs/express/authentication', () => { .configure(express.rest()); app = createApplication(expressApp as any) as unknown as express.Application; - server = await app.listen(9876); + + await app.listen(9876); app.use('/dummy', { get (id, params) { @@ -34,7 +34,7 @@ describe('@feathersjs/express/authentication', () => { } }); - //@ts-ignore + // @ts-ignore app.use('/protected', express.authenticate('jwt'), (req, res) => { res.json(req.feathers.user); }); @@ -60,7 +60,7 @@ describe('@feathersjs/express/authentication', () => { authResult = res.data; }); - after(done => server.close(done)); + after(() => app.teardown()); describe('service authentication', () => { it('successful local authentication', () => { diff --git a/packages/express/test/index.test.ts b/packages/express/test/index.test.ts index dca63971cd..e5594efcf5 100644 --- a/packages/express/test/index.test.ts +++ b/packages/express/test/index.test.ts @@ -174,30 +174,7 @@ describe('@feathersjs/express', () => { await new Promise(resolve => server.close(() => resolve(server))); }); - it('.close calls .teardown', async () => { - const app = feathersExpress(feathers()); - let called = false; - - app.use('/myservice', { - async get (id: Id) { - return { id }; - }, - - async teardown (appParam, path) { - assert.strictEqual(appParam, app); - assert.strictEqual(path, 'myservice'); - called = true; - } - - }); - - await app.listen(8787); - await app.close(); - - assert.ok(called); - }); - - it('.close closes http server', async () => { + it('.teardown closes http server', async () => { const app = feathersExpress(feathers()); let called = false; @@ -206,7 +183,7 @@ describe('@feathersjs/express', () => { called = true; }) - await app.close(); + await app.teardown(); assert.ok(called); }); diff --git a/packages/express/test/rest.test.ts b/packages/express/test/rest.test.ts index 69f5f2ced8..77dde0ea28 100644 --- a/packages/express/test/rest.test.ts +++ b/packages/express/test/rest.test.ts @@ -78,7 +78,6 @@ describe('@feathersjs/express/rest provider', () => { }); describe('CRUD', () => { - let server: Server; let app: express.Application; before(async () => { @@ -97,10 +96,10 @@ describe('@feathersjs/express/rest provider', () => { .use('/', new Service()) .use('todo', new Service()); - server = await app.listen(4777, () => app.use('tasks', new Service())); + await app.listen(4777, () => app.use('tasks', new Service())); }); - after(done => server.close(done)); + after(() => app.teardown()); restTests('Services', 'todo', 4777); restTests('Root Service', '/', 4777); @@ -197,7 +196,7 @@ describe('@feathersjs/express/rest provider', () => { app.service('hook-status').hooks({ after (hook: HookContext) { - hook.http!.statusCode = 206; + hook.http.statusCode = 206; } }); diff --git a/packages/feathers/src/application.ts b/packages/feathers/src/application.ts index e255cddad0..249d02a73e 100644 --- a/packages/feathers/src/application.ts +++ b/packages/feathers/src/application.ts @@ -140,11 +140,8 @@ export class Feathers extends EventEmitter implements Feathe } setup () { - let promise = Promise.resolve(); - - // Setup each service (pass the app so that they can look up other services etc.) - for (const path of Object.keys(this.services)) { - promise = promise.then(() => { + return Object.keys(this.services).reduce((current, path) => current + .then(() => { const service: any = this.service(path as any); if (typeof service.setup === 'function') { @@ -152,34 +149,27 @@ export class Feathers extends EventEmitter implements Feathe return service.setup(this, path); } + }), Promise.resolve()) + .then(() => { + this._isSetup = true; + return this; }); - } - - return promise.then(() => { - this._isSetup = true; - return this; - }); } teardown () { - let promise = Promise.resolve(); - - // Teardown each service (pass the app so that they can look up other services etc.) - for (const path of Object.keys(this.services)) { - promise = promise.then(() => { + return Object.keys(this.services).reduce((current, path) => current + .then(() => { const service: any = this.service(path as any); if (typeof service.teardown === 'function') { - debug(`Teardown service for \`${path}\``); + debug(`Tearing down service for \`${path}\``); return service.teardown(this, path); } + }), Promise.resolve()) + .then(() => { + this._isSetup = false; + return this; }); - } - - return promise.then(() => { - this._isSetup = false; - return this; - }); } } diff --git a/packages/feathers/src/declarations.ts b/packages/feathers/src/declarations.ts index 4bcb43755b..f1978ac474 100644 --- a/packages/feathers/src/declarations.ts +++ b/packages/feathers/src/declarations.ts @@ -211,16 +211,26 @@ export interface FeathersApplication { path: L ): FeathersService; + /** + * Set up the application and call all services `.setup` method if available. + * + * @param server A server instance (optional) + */ setup (server?: any): Promise; + /** + * Tear down the application and call all services `.teardown` method if available. + * + * @param server A server instance (optional) + */ + teardown (server?: any): Promise; + /** * Register application level hooks. * * @param map The application hook settings. */ hooks (map: HookOptions): this; - - teardown (cb?: () => Promise): Promise; } // This needs to be an interface instead of a type @@ -365,3 +375,8 @@ export type HookMap = { export type HookOptions = HookMap | HookFunction[] | RegularHookMap; + +export type AppHookOptions = HookOptions & { + setup: any[], + teardown: any[] +} diff --git a/packages/feathers/test/application.test.ts b/packages/feathers/test/application.test.ts index aeb52ee15a..d2d2b7ab82 100644 --- a/packages/feathers/test/application.test.ts +++ b/packages/feathers/test/application.test.ts @@ -293,16 +293,23 @@ describe('Feathers application', () => { }); }); - describe('.setup', () => { - it('app.setup calls .setup on all services', async () => { + describe('.setup and .teardown', () => { + it('app.setup and app.teardown calls .setup and .teardown on all services', async () => { const app = feathers(); let setupCount = 0; + let teardownCount = 0; app.use('/dummy', { async setup (appRef: any, path: any) { setupCount++; assert.strictEqual(appRef, app); assert.strictEqual(path, 'dummy'); + }, + + async teardown (appRef: any, path: any) { + teardownCount++; + assert.strictEqual(appRef, app); + assert.strictEqual(path, 'dummy'); } }); @@ -317,6 +324,12 @@ describe('Feathers application', () => { setupCount++; assert.strictEqual(appRef, app); assert.strictEqual(path, 'dummy2'); + }, + + async teardown (appRef: any, path: any) { + teardownCount++; + assert.strictEqual(appRef, app); + assert.strictEqual(path, 'dummy2'); } }); @@ -324,6 +337,11 @@ describe('Feathers application', () => { assert.ok((app as any)._isSetup); assert.strictEqual(setupCount, 2); + + await app.teardown(); + + assert.ok(!(app as any)._isSetup); + assert.strictEqual(teardownCount, 2); }); it('registering a service after app.setup will be set up', done => { diff --git a/packages/koa/src/declarations.ts b/packages/koa/src/declarations.ts index 721fe2e8eb..23f909749b 100644 --- a/packages/koa/src/declarations.ts +++ b/packages/koa/src/declarations.ts @@ -4,8 +4,8 @@ import { Application as FeathersApplication, HookContext, Params, RouteLookup } import '@feathersjs/authentication'; export type ApplicationAddons = { + server: Server; listen (port?: number, ...args: any[]): Promise; - close (): Promise; } export type Application = diff --git a/packages/koa/src/index.ts b/packages/koa/src/index.ts index c5ca38a1c7..f4b5dee895 100644 --- a/packages/koa/src/index.ts +++ b/packages/koa/src/index.ts @@ -3,7 +3,6 @@ import koaQs from 'koa-qs'; import { Application as FeathersApplication } from '@feathersjs/feathers'; import { routing } from '@feathersjs/transport-commons'; import { createDebug } from '@feathersjs/commons'; -import http from 'http'; import { Application } from './declarations'; @@ -28,39 +27,31 @@ export function koa (feathersApp?: FeathersApplication, const app = feathersApp as any as Application; const { listen: koaListen, use: koaUse } = koaApp; - const feathersUse = feathersApp.use as any; - let server:http.Server | undefined; + const { use: feathersUse, teardown: feathersTeardown } = feathersApp; Object.assign(app, { use (location: string|Koa.Middleware, ...args: any[]) { if (typeof location === 'string') { - return feathersUse.call(this, location, ...args); + return (feathersUse as any).call(this, location, ...args); } return koaUse.call(this, location); }, async listen (port?: number, ...args: any[]) { - server = koaListen.call(this, port, ...args); + const server = koaListen.call(this, port, ...args); + this.server = server; await this.setup(server); debug('Feathers application listening'); return server; }, - async close () { - if ( server ) { - server.close(); - - await new Promise((resolve) => { - server.on('close', () => { resolve(true) }); - }) - } - - debug('Feathers server closed'); - - await this.teardown(); + async teardown (server?: any) { + return feathersTeardown.call(this, server).then(() => + new Promise((resolve, reject) => this.server.close(e => e ? reject(e) : resolve(this))) + ); } } as Application); diff --git a/packages/koa/test/authentication.test.ts b/packages/koa/test/authentication.test.ts index a0ef78a8d4..c4f86241c9 100644 --- a/packages/koa/test/authentication.test.ts +++ b/packages/koa/test/authentication.test.ts @@ -1,6 +1,5 @@ import { strict as assert } from 'assert'; import _axios from 'axios'; -import { Server } from 'http'; import { AuthenticationResult } from '@feathersjs/authentication'; import app from './app.fixture'; @@ -13,12 +12,11 @@ describe('@feathersjs/koa/authentication', () => { const email = 'koatest@authentication.com'; const password = 'superkoa'; - let server: Server; let authResult: AuthenticationResult; let user: any; before(async () => { - server = await app.listen(9776); + await app.listen(9776); user = await app.service('users').create({ email, password }); authResult = (await axios.post('/authentication', { strategy: 'local', @@ -27,7 +25,7 @@ describe('@feathersjs/koa/authentication', () => { })).data; }); - after(done => server.close(done)); + after(() => app.teardown()); describe('service authentication', () => { it('successful local authentication', () => { diff --git a/packages/koa/test/index.test.ts b/packages/koa/test/index.test.ts index 8c9db95a2c..a51d2997cf 100644 --- a/packages/koa/test/index.test.ts +++ b/packages/koa/test/index.test.ts @@ -1,14 +1,12 @@ import { strict as assert } from 'assert'; import Koa from 'koa'; import axios from 'axios'; -import { Server } from 'http'; import { feathers, Id } from '@feathersjs/feathers'; import { Service, restTests } from '@feathersjs/tests'; import { koa, rest, Application, bodyParser, errorHandler } from '../src'; describe('@feathersjs/koa', () => { let app: Application; - let server: Server; before(async () => { app = koa(feathers()); @@ -33,10 +31,10 @@ describe('@feathersjs/koa', () => { ] }); - server = await app.listen(8465); + await app.listen(8465); }); - after(() => server.close()); + after(() => app.teardown()); it('throws an error when initialized with invalid application', () => { try { @@ -99,7 +97,7 @@ describe('@feathersjs/koa', () => { 'X-Service-Method': 'customMethod' } }); - + assert.deepStrictEqual(data, { data: { message: 'Custom hello' }, method: 'customMethod', @@ -140,30 +138,7 @@ describe('@feathersjs/koa', () => { }); }); - it('.close calls .teardown', async () => { - const app = koa(feathers()); - let called = false; - - app.use('/myservice', { - async get (id: Id) { - return { id }; - }, - - async teardown (appParam, path) { - assert.strictEqual(appParam, app); - assert.strictEqual(path, 'myservice'); - called = true; - } - - }); - - await app.listen(8787); - await app.close(); - - assert.ok(called); - }); - - it('.close closes http server', async () => { + it('.teardown closes http server', async () => { const app = koa(feathers()); let called = false; @@ -172,7 +147,7 @@ describe('@feathersjs/koa', () => { called = true; }) - await app.close(); + await app.teardown(); assert.ok(called); });