-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added unit tests for ctx and decorators and middleware
- Loading branch information
Showing
7 changed files
with
342 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import sinon from 'sinon'; | ||
import { expect } from 'chai'; | ||
import { describe, it } from 'mocha'; | ||
import { WebContext } from '../context'; | ||
import { IncomingMessage } from 'http'; | ||
import { ServerResponse } from 'http'; | ||
import { RQ, RS } from '../../types'; | ||
describe('CONTEXT', () => { | ||
let webContext: WebContext; | ||
let mockRequest: sinon.SinonStubbedInstance<IncomingMessage & RQ>; | ||
let mockResponse: sinon.SinonStubbedInstance<ServerResponse & RS>; | ||
beforeEach(() => { | ||
mockRequest = sinon.createStubInstance<IncomingMessage & RQ>(IncomingMessage); | ||
mockResponse = sinon.createStubInstance<ServerResponse & RS>(ServerResponse); | ||
webContext = new WebContext(mockRequest, mockResponse); | ||
}); | ||
|
||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
it('should initialize the context with default values', () => { | ||
expect(webContext.body).to.be.null; | ||
expect(webContext.params).to.deep.equal({}); | ||
expect(webContext.query).to.deep.equal({}); | ||
}); | ||
it('should return response property when accessed via proxy', () => { | ||
// Mocking a response property (e.g., "statusCode") | ||
mockResponse.statusCode = 200; | ||
|
||
const statusCode = webContext.ctx.statusCode; | ||
|
||
expect(statusCode).to.equal(200); | ||
}); | ||
|
||
it('should set request or response properties correctly', () => { | ||
// Setting a response property | ||
webContext.ctx.statusCode = 404; | ||
expect(mockResponse.statusCode).to.equal(404); | ||
|
||
// Setting a request property | ||
webContext.ctx.method = 'POST'; | ||
expect(webContext.ctx.method).to.equal('POST'); | ||
}); | ||
it('should return true if property exists in request or response', () => { | ||
mockRequest.method = 'GET'; | ||
expect('method' in webContext.ctx).to.be.true; | ||
}); | ||
|
||
it('should return false if property does not exist in request or response', () => { | ||
expect('nonexistent' in webContext.ctx).to.be.false; | ||
}); | ||
|
||
it('should delete properties from request or response', () => { | ||
expect(delete webContext.ctx.method).to.be.true; | ||
expect(webContext.ctx.method).to.be.undefined; | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { expect } from 'chai'; | ||
import * as sinon from 'sinon'; | ||
import { mid, mids, Route, getEx } from '../decorators'; | ||
import { ServerUtils } from '../../helper'; | ||
import Reflect from '../../metadata'; | ||
import { routes } from '../router'; | ||
|
||
describe('Decorators', () => { | ||
let reflectInitStub: sinon.SinonStub; | ||
let serverUtilsNormalizeStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
reflectInitStub = sinon.stub(Reflect, 'init'); | ||
serverUtilsNormalizeStub = sinon.stub(ServerUtils, 'normalize').callsFake((middleware: any) => (Array.isArray(middleware) ? middleware : [middleware])); | ||
}); | ||
|
||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
|
||
describe('mid decorator', () => { | ||
it('should add middleware to the method using Reflect', () => { | ||
const middleware = sinon.stub(); | ||
const target = {}; | ||
const propertyKey = 'someMethod'; | ||
const descriptor = {}; | ||
const mockGet = sinon.stub(Reflect, 'get').returns([]); | ||
|
||
mid(middleware)(target, propertyKey, descriptor as PropertyDescriptor); | ||
|
||
expect(mockGet.calledOnceWith('middlewares', target.constructor.prototype, propertyKey)).to.be.true; | ||
expect(serverUtilsNormalizeStub.calledOnceWith(middleware)).to.be.true; | ||
expect(reflectInitStub.calledOnceWith('middlewares', [middleware], target.constructor.prototype, propertyKey)).to.be.true; | ||
|
||
mockGet.restore(); | ||
}); | ||
}); | ||
describe('mids decorator', () => { | ||
it('should add class-level middleware using Reflect', () => { | ||
const middlewareArray = [sinon.stub(), sinon.stub()]; | ||
class SomeClass {} | ||
mids(middlewareArray)(SomeClass); | ||
|
||
expect(serverUtilsNormalizeStub.calledOnceWith(middlewareArray)).to.be.true; | ||
expect(reflectInitStub.calledOnceWith('classMiddlewares', middlewareArray, SomeClass.prototype)).to.be.true; | ||
}); | ||
}); | ||
|
||
describe('Route decorator', () => { | ||
it('should register the route and path in Reflect and routes', () => { | ||
const path = '/test-path'; | ||
class SomeClass {} | ||
Route(path)(SomeClass); | ||
|
||
expect(reflectInitStub.calledOnceWith('route', path, SomeClass.prototype)).to.be.true; | ||
expect(routes.get(path)).to.equal(SomeClass); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { expect } from 'chai'; | ||
import * as sinon from 'sinon'; | ||
import { Gmids, midManager } from '../middleware'; | ||
import { ServerUtils } from '../../helper'; | ||
import { Gland } from '../../types/index'; | ||
|
||
describe('Gmids and midManager', () => { | ||
let serverUtilsNormalizeStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
serverUtilsNormalizeStub = sinon.stub(ServerUtils, 'normalize').callsFake((middleware: any) => (Array.isArray(middleware) ? middleware : [middleware])); | ||
(Gmids as any).mids = []; | ||
}); | ||
|
||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
|
||
describe('Gmids', () => { | ||
describe('set', () => { | ||
it('should add middleware to the mids array', () => { | ||
const middleware = sinon.stub(); | ||
Gmids.set(middleware); | ||
|
||
expect(serverUtilsNormalizeStub.calledOnceWith(middleware)).to.be.true; | ||
expect(Gmids.get()).to.include(middleware); | ||
}); | ||
|
||
it('should handle multiple middlewares', () => { | ||
const middlewareArray = [sinon.stub(), sinon.stub()]; | ||
Gmids.set(middlewareArray); | ||
expect(serverUtilsNormalizeStub.calledOnceWith(middlewareArray)).to.be.true; | ||
expect(Gmids.get()).to.deep.equal(middlewareArray); | ||
}); | ||
}); | ||
|
||
describe('get', () => { | ||
it('should return an empty array when no middlewares are set', () => { | ||
sinon.stub(Gmids, 'get').returns([]); | ||
expect(Gmids.get()).to.deep.equal([]); | ||
}); | ||
|
||
it('should return the current middlewares', () => { | ||
const middleware = sinon.stub(); | ||
Gmids.set(middleware); | ||
expect(Gmids.get()).to.include(middleware); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('midManager', () => { | ||
let middlewares: Gland.Middleware[]; | ||
|
||
beforeEach(() => { | ||
middlewares = []; | ||
}); | ||
|
||
describe('process', () => { | ||
it('should throw an error for invalid middleware/handler signature', () => { | ||
const path = '/test'; | ||
const invalidHandler: any = sinon.stub().callsFake(() => {}); | ||
|
||
expect(() => midManager.process(path, [invalidHandler], middlewares)).to.throw('Invalid middleware/handler function signature'); | ||
}); | ||
|
||
it('should handle non-string path and add unique middlewares', () => { | ||
const handler1: Gland.GlandMiddleware = sinon.stub(); | ||
const handler2: Gland.GlandMiddleware = sinon.stub(); | ||
|
||
midManager.process(handler1, [handler1, handler2], middlewares); | ||
|
||
expect(middlewares).to.have.lengthOf(2); | ||
expect(middlewares).to.include(handler1); | ||
expect(middlewares).to.include(handler2); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { IncomingMessage, ServerResponse } from 'http'; | ||
import { Context, RQ, RS } from '../types'; | ||
import './router/request'; | ||
import { Queue } from '../helper/queue'; | ||
export class WebContext { | ||
public rq: RQ; | ||
public rs: RS; | ||
public ctx: Context; | ||
public body: any; | ||
public params: any; | ||
public query: any; | ||
constructor(request: IncomingMessage, response: ServerResponse) { | ||
this.rq = request; | ||
this.rs = response; | ||
this.body = null; | ||
this.params = {}; | ||
this.query = {}; | ||
this.ctx = new Proxy(this, this.opts()) as WebContext & Context; | ||
} | ||
private opts() { | ||
return { | ||
get: (t: WebContext, p: string | symbol) => { | ||
// Explicitly handle `on` method | ||
if (p === 'on' || p === 'once') { | ||
// Check if this method is being used in the request or response context | ||
if (typeof t.rq[p as keyof RQ] === 'function') { | ||
return t.rq[p as keyof RQ].bind(t.rq); | ||
} | ||
if (typeof t.rs[p as keyof RS] === 'function') { | ||
return t.rs[p as keyof RS].bind(t.rs); | ||
} | ||
} | ||
if (p in t) return t[p as keyof WebContext]; | ||
if (p in t.rs) return typeof t.rs[p as keyof RS] === 'function' ? t.rs[p as keyof RS].bind(t.rs) : t.rs[p as keyof RS]; | ||
if (p in t.rq) return t.rq[p as keyof RQ]; | ||
}, | ||
set: (t: WebContext, p: string | symbol, v: any) => { | ||
if (p in t.rs) { | ||
t.rs[p as keyof RS] = v; | ||
} else if (p in t.rq) { | ||
t.rq[p as keyof RQ] = v; | ||
} else { | ||
t[p as keyof WebContext] = v; | ||
} | ||
return true; | ||
}, | ||
has: (t: WebContext, p: string | symbol) => { | ||
return p in t.rs || p in t.rq; | ||
}, | ||
deleteperty: (t: any, p: string | symbol) => { | ||
if (p in (t || t.rq)) { | ||
return delete (t || t.rq)[p as keyof WebContext]; | ||
} | ||
return false; | ||
}, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { ServerUtils } from '../helper'; | ||
import Reflect from '../metadata'; | ||
import { MidsFn, RouteHandler } from '../types'; | ||
import { routes } from './router'; | ||
const classes: Set<any> = new Set(); | ||
function getEx(): any[] { | ||
return Array.from(classes); | ||
} | ||
export { getEx }; | ||
export function mid(middleware: MidsFn | MidsFn[]): MethodDecorator | any { | ||
return (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void => { | ||
const existingMids = Reflect.get('middlewares', target.constructor.prototype, propertyKey) || []; | ||
const newMids = ServerUtils.normalize(middleware); | ||
Reflect.init('middlewares', [...existingMids, ...newMids], target.constructor.prototype, propertyKey); | ||
}; | ||
} | ||
|
||
export function mids(middlewareArray: MidsFn[] | MidsFn): ClassDecorator { | ||
return (target: any) => { | ||
const newMids = ServerUtils.normalize(middlewareArray); | ||
Reflect.init('classMiddlewares', newMids, target.prototype); | ||
}; | ||
} | ||
|
||
export function Route(path: string): ClassDecorator { | ||
return (target: Function): void => { | ||
Reflect.init('route', path, target.prototype); | ||
routes.set(path, target as RouteHandler); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { ServerUtils } from '../helper'; | ||
import { Context, MidsFn, NxtFunction } from '../types'; | ||
import { Gland } from '../types'; | ||
import { Router } from './router'; | ||
export namespace Gmids { | ||
export let mids: MidsFn[] = []; | ||
|
||
export function set(middleware: MidsFn | MidsFn[]) { | ||
mids = [...mids, ...ServerUtils.normalize(middleware)]; | ||
} | ||
|
||
export function get(): MidsFn[] | [] { | ||
return mids || []; | ||
} | ||
} | ||
export namespace midManager { | ||
export function process(path: string | Gland.Middleware, handlers: (Gland.Middleware | Gland.Middleware[])[], middlewares: Gland.Middleware[]) { | ||
if (typeof path === 'string') { | ||
handlers.flat().forEach((handler) => { | ||
Router.set(handler as any, path); | ||
if (handler.length === 2 || handler.length === 3) { | ||
middlewares.push(async (ctx: Context, next: NxtFunction) => { | ||
if (ctx.url!.startsWith(path)) { | ||
if (handler.length === 2) { | ||
await (handler as Gland.GlandMiddleware)(ctx, next); | ||
} else if (handler.length === 3) { | ||
await new Promise<void>((resolve, reject) => { | ||
(handler as Gland.ExpressMiddleware)(ctx.rq, ctx.rs, (err?: any) => { | ||
if (err) reject(err); | ||
else resolve(); | ||
}); | ||
}); | ||
} else { | ||
throw new Error('Invalid middleware/handler function signature'); | ||
} | ||
} else { | ||
await next(); | ||
} | ||
}); | ||
} else if (handler.length === 1) { | ||
middlewares.push(handler as Gland.GlandMiddleware); | ||
} else { | ||
throw new Error('Invalid middleware/handler function signature'); | ||
} | ||
}); | ||
} else { | ||
// Handle when path is not a string | ||
const allMiddlewares = [path, ...handlers].flat(); | ||
const uniqueMiddlewares = Array.from(new Set(allMiddlewares)); | ||
middlewares.push(...uniqueMiddlewares); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters