From 4e514517047d599d873180d7109c337b284d99c9 Mon Sep 17 00:00:00 2001 From: Zak Henry Date: Thu, 16 Jun 2016 17:03:52 +0100 Subject: [PATCH] feat(middleware): Add tests for all middleware classes and decorators --- src/common/models/collection.spec.ts | 10 +- src/common/models/model.spec.ts | 2 +- src/common/services/consoleLogger.service.ts | 3 + src/common/services/logger.service.spec.ts | 15 +++ src/common/services/logger.service.ts | 3 + src/server/controllers/abstract.controller.ts | 20 +-- .../middleware/debugLog.middleware.spec.ts | 96 ++++++++++++++ src/server/middleware/debugLog.middleware.ts | 8 +- src/server/middleware/index.ts | 6 +- src/server/middleware/middleware.decorator.ts | 5 +- src/server/middleware/middleware.spec.ts | 122 ++++++++++++++++++ src/server/servers/abstract.server.spec.ts | 25 ++++ src/server/servers/abstract.server.ts | 1 + src/server/services/remoteCli.service.spec.ts | 20 +++ src/server/services/remoteCli.service.ts | 8 +- typings.json | 1 - 16 files changed, 313 insertions(+), 32 deletions(-) create mode 100644 src/common/services/logger.service.spec.ts create mode 100644 src/server/middleware/debugLog.middleware.spec.ts create mode 100644 src/server/middleware/middleware.spec.ts create mode 100644 src/server/servers/abstract.server.spec.ts create mode 100644 src/server/services/remoteCli.service.spec.ts diff --git a/src/common/models/collection.spec.ts b/src/common/models/collection.spec.ts index 34cef68..3ee287e 100644 --- a/src/common/models/collection.spec.ts +++ b/src/common/models/collection.spec.ts @@ -1,11 +1,5 @@ -import { - it, xit, - inject, - describe, xdescribe, fdescribe, - beforeEachProviders, - expect -} from '@angular/core/testing'; -import { UUID, Model, primary } from './model'; +import { it, describe, beforeEach, expect } from '@angular/core/testing'; +import { Model, primary } from './model'; import { Collection } from './collection'; class BasicModel extends Model { diff --git a/src/common/models/model.spec.ts b/src/common/models/model.spec.ts index 39e0cbc..2f95263 100644 --- a/src/common/models/model.spec.ts +++ b/src/common/models/model.spec.ts @@ -1,4 +1,4 @@ -import { it, describe, expect } from '@angular/core/testing'; +import { it, describe, expect, beforeEach } from '@angular/core/testing'; import { UUID, Model, primary } from './model'; import { castDate } from '../types/date.decorator'; import * as moment from 'moment'; diff --git a/src/common/services/consoleLogger.service.ts b/src/common/services/consoleLogger.service.ts index c342238..c4a78cc 100644 --- a/src/common/services/consoleLogger.service.ts +++ b/src/common/services/consoleLogger.service.ts @@ -1,7 +1,10 @@ import { Logger, LogLevel } from './logger.service'; import { yellow, red, bgRed, magenta, gray, blue, bgYellow } from 'chalk'; import { inspect } from 'util'; +import { Injectable } from '@angular/core'; import * as moment from 'moment'; + +@Injectable() export class ConsoleLogger extends Logger { constructor() { diff --git a/src/common/services/logger.service.spec.ts b/src/common/services/logger.service.spec.ts new file mode 100644 index 0000000..44c30db --- /dev/null +++ b/src/common/services/logger.service.spec.ts @@ -0,0 +1,15 @@ +import { Logger, LogLevel } from './logger.service'; +import { Injectable } from '@angular/core'; + +@Injectable() +export class LoggerMock extends Logger { + + constructor() { + super(LoggerMock); + } + + public persistLog(logLevel: LogLevel, messages: any[]): Promise|this { + return this; + } + +} diff --git a/src/common/services/logger.service.ts b/src/common/services/logger.service.ts index 4d4d2ee..060aa45 100644 --- a/src/common/services/logger.service.ts +++ b/src/common/services/logger.service.ts @@ -1,3 +1,5 @@ +import { Injectable } from '@angular/core'; + const tableModule = require('table'); const table: Table = tableModule.default; @@ -69,6 +71,7 @@ export interface Table { (data: any[][], config?: TableConfig): string; } +@Injectable() export abstract class Logger { protected sourceName: string; diff --git a/src/server/controllers/abstract.controller.ts b/src/server/controllers/abstract.controller.ts index 28f55e5..26bded3 100644 --- a/src/server/controllers/abstract.controller.ts +++ b/src/server/controllers/abstract.controller.ts @@ -1,5 +1,5 @@ import { Server } from '../servers/abstract.server'; -import { Injectable, ReflectiveInjector } from '@angular/core'; +import { Injectable, Injector } from '@angular/core'; import { Logger } from '../../common/services/logger.service'; import { InjectableMiddlewareFactory, MiddlewareFactory } from '../middleware/index'; import { PromiseFactory } from '../../common/util/serialPromise'; @@ -39,7 +39,7 @@ export abstract class AbstractController { protected routeBase: string; protected logger: Logger; - private injector: ReflectiveInjector; + private injector: Injector; constructor(protected server: Server, logger: Logger) { this.logger = logger.source('controller'); @@ -52,7 +52,7 @@ export abstract class AbstractController { * @param injector * @returns {AbstractController} */ - public registerInjector(injector: ReflectiveInjector) { + public registerInjector(injector: Injector) { this.injector = injector; return this; } @@ -99,7 +99,6 @@ export abstract class AbstractController { } return this; - } /** @@ -116,18 +115,19 @@ export abstract class AbstractController { if (this.registeredMiddleware){ const methodMiddlewareFactories = this.registeredMiddleware.methods.get(methodSignature); - //wrap method registered factories with the class defined ones - methodMiddlewareFactories.before.unshift(...this.registeredMiddleware.all.before); - methodMiddlewareFactories.after.push(...this.registeredMiddleware.all.after); + + //wrap method registered factories with the class defined ones [beforeAll, before, after, afterAll] + const beforeMiddleware = this.registeredMiddleware.all.before.concat(methodMiddlewareFactories.before); + const afterMiddleware = methodMiddlewareFactories.after.concat(this.registeredMiddleware.all.after); if (methodMiddlewareFactories) { - callStack.unshift(...methodMiddlewareFactories.before.map((middleware: MiddlewareFactory) => middleware(this.injector))); - callStack.push(...methodMiddlewareFactories.after.map((middleware: MiddlewareFactory) => middleware(this.injector))); + callStack.unshift(...beforeMiddleware.map((middleware: MiddlewareFactory) => middleware(this.injector))); + callStack.push(...afterMiddleware.map((middleware: MiddlewareFactory) => middleware(this.injector))); } } - this.server.register({ + methodName: methodSignature, method: methodDefinition.method, path: `/api/${this.routeBase}${methodDefinition.route}`, callStack: callStack, diff --git a/src/server/middleware/debugLog.middleware.spec.ts b/src/server/middleware/debugLog.middleware.spec.ts new file mode 100644 index 0000000..1aed4bf --- /dev/null +++ b/src/server/middleware/debugLog.middleware.spec.ts @@ -0,0 +1,96 @@ +import { + it, + inject, + beforeEachProviders, + expect, + describe, + fdescribe +} from '@angular/core/testing'; +import { IsolatedMiddlewareFactory } from './index'; +import { Request } from '../controllers/request'; +import { Response } from '../controllers/response'; +import { Action } from '../controllers/action.decorator'; +import { AfterAll, BeforeAll, Before, After } from './middleware.decorator'; +import { AbstractController } from '../controllers/abstract.controller'; +import { Injectable, Injector, Provider, provide } from '@angular/core'; +import { Logger } from '../../common/services/logger.service'; +import { Server, RouteConfig } from '../servers/abstract.server'; +import { LoggerMock } from '../../common/services/logger.service.spec'; +import { ServerMock } from '../servers/abstract.server.spec'; +import { RemoteCli } from '../services/remoteCli.service'; +import { RemoteCliMock } from '../services/remoteCli.service.spec'; +import { PromiseFactory } from '../../common/util/serialPromise'; +import { debugLog, DebugLogMiddleware } from './debugLog.middleware'; + +@Injectable() +class MiddlewareController extends AbstractController { + + constructor(server: Server, logger: Logger) { + super(server, logger); + } + + @Action('GET', '/test') + @Before(debugLog('test log input')) + public testMethod(request: Request, response: Response): Response { + return response; + } + +} + +let source: string, logs: any[] = []; + +let mockLogger = { + source: (input: string) => { + source = input; + return mockLogger; + }, + debug: (input: string) => { + logs.push(input); + } +}; + +const providers = [ + MiddlewareController, + provide(Server, {useClass: ServerMock}), + provide(Logger, {useClass: LoggerMock}), + provide(RemoteCli, {useClass: RemoteCliMock}), + provide(DebugLogMiddleware, { + deps: [], + useFactory: () => { + return new DebugLogMiddleware(mockLogger) + }, + }) +]; + +describe('debugLog middleware', () => { + + let controller: MiddlewareController; + + beforeEachProviders(() => providers); + + it('Calls debug.log on the passed value to the middleware decorator', + inject([MiddlewareController, Injector, Server], + (c: MiddlewareController, i: Injector, s: Server) => { + + controller = c.registerInjector(i) + .registerRoutes(); + + const callStackHandler: any = s.getRoutes() + .find((route: RouteConfig) => route.methodName == 'testMethod').callStackHandler; + + let request = new Request(); + let response = new Response(); + + return callStackHandler(request, response) + .then(() => { + + expect(source) + .toEqual('debugLog'); + expect(logs) + .toEqual(['test log input']); + + }); + + })); + +}); diff --git a/src/server/middleware/debugLog.middleware.ts b/src/server/middleware/debugLog.middleware.ts index 73f6524..5f807c3 100644 --- a/src/server/middleware/debugLog.middleware.ts +++ b/src/server/middleware/debugLog.middleware.ts @@ -9,8 +9,7 @@ export class DebugLogMiddleware implements InjectableMiddleware { protected logger: Logger; constructor(loggerBase: Logger) { - console.log('initialized log middleware', loggerBase); - this.logger = loggerBase.source('Log middleware'); + this.logger = loggerBase.source('debugLog'); } public middlewareFactory(messages: string[]): Middleware { @@ -18,7 +17,8 @@ export class DebugLogMiddleware implements InjectableMiddleware { return function debugLog(request: Request, response: Response): Response { this.logger.debug(...messages); return response; - } + }.bind(this); + } } @@ -26,7 +26,7 @@ export function debugLog(...messages: string[]): InjectableMiddlewareFactory { return (injector: ReflectiveInjector): Middleware => { return injector.get(DebugLogMiddleware) - .middlewareFactory(messages) + .middlewareFactory(messages); } } diff --git a/src/server/middleware/index.ts b/src/server/middleware/index.ts index c7b46c8..3d10c5b 100644 --- a/src/server/middleware/index.ts +++ b/src/server/middleware/index.ts @@ -1,4 +1,4 @@ -import { ReflectiveInjector } from '@angular/core'; +import { Injector } from '@angular/core'; import { Response } from '../controllers/response'; import { Request } from '../controllers/request'; @@ -11,11 +11,11 @@ export interface InjectableMiddleware { } export interface InjectableMiddlewareFactory { - (injector: ReflectiveInjector): Middleware; + (injector: Injector): Middleware; } export interface IsolatedMiddlewareFactory { - (injector?: ReflectiveInjector): Middleware; + (injector?: Injector): Middleware; } export type MiddlewareFactory = InjectableMiddlewareFactory | IsolatedMiddlewareFactory; diff --git a/src/server/middleware/middleware.decorator.ts b/src/server/middleware/middleware.decorator.ts index dfa7d09..6bf1f23 100644 --- a/src/server/middleware/middleware.decorator.ts +++ b/src/server/middleware/middleware.decorator.ts @@ -1,6 +1,5 @@ import { AbstractController, MiddlewareRegistry } from '../controllers/abstract.controller'; import { MiddlewareFactory } from './index'; -import { initializeRelationMap } from '../../common/relations/index'; /** * Decorator for assigning before middleware method in a controller @@ -51,7 +50,7 @@ export function initializeMiddlewareRegister(target: AbstractController): void { export function BeforeAll(...middlewareFactories: MiddlewareFactory[]): ClassDecorator { return function (target: Function): void { initializeMiddlewareRegister(target.prototype); - target.prototype.registeredMiddleware.all.before.push(...middlewareFactories); + target.prototype.registeredMiddleware.all.before = middlewareFactories; } } @@ -64,6 +63,6 @@ export function BeforeAll(...middlewareFactories: MiddlewareFactory[]): ClassDec export function AfterAll(...middlewareFactories: MiddlewareFactory[]): ClassDecorator { return function (target: Function): void { initializeMiddlewareRegister(target.prototype); - target.prototype.registeredMiddleware.all.after.push(...middlewareFactories); + target.prototype.registeredMiddleware.all.after = middlewareFactories; } } diff --git a/src/server/middleware/middleware.spec.ts b/src/server/middleware/middleware.spec.ts new file mode 100644 index 0000000..fe1f743 --- /dev/null +++ b/src/server/middleware/middleware.spec.ts @@ -0,0 +1,122 @@ +import { it, inject, beforeEachProviders, expect, describe } from '@angular/core/testing'; +import { IsolatedMiddlewareFactory } from './index'; +import { Request } from '../controllers/request'; +import { Response } from '../controllers/response'; +import { Action } from '../controllers/action.decorator'; +import { AfterAll, BeforeAll, Before, After } from './middleware.decorator'; +import { AbstractController } from '../controllers/abstract.controller'; +import { Injectable, ReflectiveInjector } from '@angular/core'; +import { Logger } from '../../common/services/logger.service'; +import { Server, RouteConfig } from '../servers/abstract.server'; +import { LoggerMock } from '../../common/services/logger.service.spec'; +import { ServerMock } from '../servers/abstract.server.spec'; +import { RemoteCli } from '../services/remoteCli.service'; +import { RemoteCliMock } from '../services/remoteCli.service.spec'; +import { PromiseFactory } from '../../common/util/serialPromise'; + +let middlewareCalls: string[] = []; + +function middlewareFixture(input: string): IsolatedMiddlewareFactory { + return () => function mockMiddleware(request: Request, response: Response): Response { + middlewareCalls.push(input); + return response; + } +} + +@BeforeAll(middlewareFixture('one'), middlewareFixture('two')) +@AfterAll(middlewareFixture('five')) +@Injectable() +class MiddlewareController extends AbstractController { + + constructor(server: Server, logger: Logger) { + super(server, logger); + } + + @Action('GET', '/test') + @Before(middlewareFixture('three')) + @After(middlewareFixture('four')) + public testMethod(request: Request, response: Response): Response { + return response; + } + +} + +const providers = [ + MiddlewareController, + {provide: Server, useClass: ServerMock}, + {provide: Logger, useClass: LoggerMock}, + {provide: RemoteCli, useClass: RemoteCliMock}, + ReflectiveInjector +]; + +describe('Middleware Decorators', () => { + + let controller: MiddlewareController; + + beforeEachProviders(() => providers); + + it('defines registeredMiddleware on the controller', + inject([MiddlewareController, ReflectiveInjector], + (c:MiddlewareController, i:ReflectiveInjector) => { + + controller = c.registerRoutes().registerInjector(i); + + expect(controller.registeredMiddleware) + .not + .toBeNull(); + expect(controller.registeredMiddleware.all.before.length) + .toEqual(2); + expect(controller.registeredMiddleware.all.after.length) + .toEqual(1); + })); + + + it('adds middleware to the call stack', + inject([MiddlewareController, ReflectiveInjector, Server], + (c:MiddlewareController, i:ReflectiveInjector, s:Server) => { + + controller = c.registerRoutes().registerInjector(i); + + const callStack:any = s.getRoutes().reduce((middlewareStackMap:Object, route:RouteConfig) => { + middlewareStackMap[route.methodName] = route.callStack.map((handler: PromiseFactory) => handler.name); + return middlewareStackMap; + }, {}); + + expect(callStack.testMethod).toEqual([ + 'mockMiddleware', + 'mockMiddleware', + 'mockMiddleware', + 'testMethod', + 'mockMiddleware', + 'mockMiddleware' + ]); + + })); + + + it('calls the stack in the correct order defined by middleware', + inject([MiddlewareController, ReflectiveInjector, Server], + (c:MiddlewareController, i:ReflectiveInjector, s:Server) => { + + controller = c.registerRoutes().registerInjector(i); + + const callStackHandler:any = s.getRoutes().find((route:RouteConfig) => route.methodName == 'testMethod').callStackHandler; + + let request = new Request(); + let response = new Response(); + + return callStackHandler(request, response).then(() => { + + expect(middlewareCalls).toEqual([ + 'one', + 'two', + 'three', + 'four', + 'five', + ]); + + }); + + })); + +}); diff --git a/src/server/servers/abstract.server.spec.ts b/src/server/servers/abstract.server.spec.ts new file mode 100644 index 0000000..4223cc3 --- /dev/null +++ b/src/server/servers/abstract.server.spec.ts @@ -0,0 +1,25 @@ +import { Server, RouteConfig } from './abstract.server'; +import { RemoteCli } from '../services/remoteCli.service'; +import { Logger } from '../../common/services/logger.service'; +import { Injectable } from '@angular/core'; + +@Injectable() +export class ServerMock extends Server { + + constructor(logger: Logger, remoteCli: RemoteCli) { + super(logger, remoteCli); + } + + protected registerRouteWithEngine(config: RouteConfig): this { + return this; + } + + protected initialize(): this { + return this; + } + + public start(): Promise { + return Promise.resolve(this); + } + +} diff --git a/src/server/servers/abstract.server.ts b/src/server/servers/abstract.server.ts index 21ac3cc..e1fe46e 100644 --- a/src/server/servers/abstract.server.ts +++ b/src/server/servers/abstract.server.ts @@ -10,6 +10,7 @@ export type HttpMethod = 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE'; export interface RouteConfig { path: string; + methodName: string; method: HttpMethod; callStack: PromiseFactory[]; callStackHandler: (request: Request, response: Response) => Promise; diff --git a/src/server/services/remoteCli.service.spec.ts b/src/server/services/remoteCli.service.spec.ts new file mode 100644 index 0000000..0956f3a --- /dev/null +++ b/src/server/services/remoteCli.service.spec.ts @@ -0,0 +1,20 @@ +import { Injectable, Injector } from '@angular/core'; +import { RemoteCli, ConnectedSocketCallback } from './remoteCli.service'; +import { Logger } from '../../common/services/logger.service'; + +@Injectable() +export class RemoteCliMock extends RemoteCli { + + constructor(loggerBase: Logger, injector: Injector) { + super(loggerBase, injector) + } + + protected registerCommands(): this { + return this; + } + + public start(port: number, callback?: ConnectedSocketCallback): this { + return this; + } + +} diff --git a/src/server/services/remoteCli.service.ts b/src/server/services/remoteCli.service.ts index 0b01761..6f3cad5 100644 --- a/src/server/services/remoteCli.service.ts +++ b/src/server/services/remoteCli.service.ts @@ -49,7 +49,7 @@ export class RemoteCli { /** * Registers the pre-defined commands */ - private registerCommands(): void { + protected registerCommands(): this { let remoteCli = this; @@ -85,6 +85,8 @@ export class RemoteCli { this.log('\n' + table); callback(); }); + + return this; } /** @@ -92,7 +94,7 @@ export class RemoteCli { * @param port * @param callback */ - public start(port: number, callback?: ConnectedSocketCallback): void { + public start(port: number, callback?: ConnectedSocketCallback): this { if (!callback) { callback = (socket: Socket) => { @@ -102,6 +104,8 @@ export class RemoteCli { this.vantage.listen(port, callback); this.logger.info(`Vantage server started on ${port}`); + + return this; } } diff --git a/typings.json b/typings.json index 651d80a..486a481 100644 --- a/typings.json +++ b/typings.json @@ -2,7 +2,6 @@ "globalDependencies": { "dotenv": "registry:dt/dotenv#2.0.0+20160327131627", "hapi": "registry:dt/hapi#13.0.0+20160423150146", - "jasmine": "registry:dt/jasmine#2.2.0+20160505161446", "lodash": "registry:dt/lodash#3.10.0+20160330154726", "moment-node": "registry:dt/moment-node#2.11.1+20160511043338", "node": "registry:dt/node#6.0.0+20160514165920",