Skip to content

Commit 9e3042e

Browse files
committed
code coverage
1 parent b078147 commit 9e3042e

7 files changed

Lines changed: 1219 additions & 170 deletions

File tree

ingest/package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,13 @@
189189
"build:tsc": "yarn build:tsc:cjs && yarn build:tsc:esm",
190190
"build:tsc:cjs": "tsc -p ./tsconfig.cjs.json",
191191
"build:tsc:esm": "tsc -p ./tsconfig.esm.json",
192-
"test": "yarn test:env nyc ts-mocha -r tsx tests/*.test.ts",
193-
"test:env": "NODE_OPTIONS=\"--disable-warning=ExperimentalWarning --experimental-loader @istanbuljs/esm-loader-hook\"",
194-
"report": "nyc report -r lcov"
192+
"test": "ts-mocha -r tsx tests/*.test.ts"
195193
},
196194
"dependencies": {
197195
"@stackpress/lib": "0.10.3",
198196
"@whatwg-node/server": "0.10.17"
199197
},
200198
"devDependencies": {
201-
"@istanbuljs/esm-loader-hook": "0.3.0",
202199
"@types/chai": "4.3.20",
203200
"@types/mocha": "10.0.10",
204201
"@types/node": "25.3.5",

ingest/tests/Decorator.test.ts

Lines changed: 242 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,32 @@ import Server from '../src/Server.js';
77
import Request from '../src/Request.js';
88
import Response from '../src/Response.js';
99
import type { ActionRouteProps } from '../src/types.js';
10-
import { Controller, Get, On, Post, mount } from '../src/decorators.js';
10+
import {
11+
All,
12+
Connect,
13+
Controller,
14+
Delete,
15+
Get,
16+
Head,
17+
On,
18+
Options,
19+
Patch,
20+
Post,
21+
Put,
22+
Trace,
23+
addEvent,
24+
addRoute,
25+
assertHandler,
26+
controllerOf,
27+
hasEvent,
28+
hasRoute,
29+
metadataOf,
30+
mount,
31+
normalizePath,
32+
registerEvent,
33+
registerRoute,
34+
routeDecorator
35+
} from '../src/decorators.js';
1136

1237
type TestRouterProps = ActionRouteProps<
1338
unknown,
@@ -127,4 +152,220 @@ describe('Decorator Tests', () => {
127152
await server.emit('GET /api/users', req, res);
128153
expect(res.headers.get('x-hook')).to.equal('true');
129154
});
155+
156+
it('Should expose decorator helper utilities for direct mounting flows',
157+
async () => {
158+
class UtilityController {
159+
public prefix = 'utility';
160+
161+
public route({ res }: TestRouterProps) {
162+
res.set('text/plain', this.prefix);
163+
}
164+
165+
public event({ res }: TestRouterProps) {
166+
res.headers.set('x-utility', 'true');
167+
}
168+
}
169+
170+
const metadata = metadataOf(UtilityController);
171+
addRoute(UtilityController, {
172+
method: 'GET',
173+
path: '/utility//path',
174+
property: 'route',
175+
priority: 4
176+
});
177+
addRoute(UtilityController, {
178+
method: 'GET',
179+
path: '/utility//path',
180+
property: 'route',
181+
priority: 4
182+
});
183+
addEvent(UtilityController, {
184+
event: 'GET /api/utility/path',
185+
property: 'event',
186+
priority: 6
187+
});
188+
addEvent(UtilityController, {
189+
event: 'GET /api/utility/path',
190+
property: 'event',
191+
priority: 6
192+
});
193+
194+
const controller = controllerOf(UtilityController);
195+
const router = new Router();
196+
197+
registerRoute(router, controller, '/api/', metadata.routes[0]);
198+
registerEvent(router, controller, metadata.events[0]);
199+
200+
const req = new Request({
201+
method: 'GET',
202+
url: new URL('http://localhost/api/utility/path')
203+
});
204+
const res = new Response();
205+
await router.emit('GET /api/utility/path', req, res);
206+
207+
expect(hasRoute(metadata, metadata.routes[0])).to.equal(true);
208+
expect(hasEvent(metadata, metadata.events[0])).to.equal(true);
209+
expect(normalizePath('/api/', '/utility//path')).to.equal(
210+
'/api/utility/path'
211+
);
212+
expect(res.body).to.equal('utility');
213+
expect(res.headers.get('x-utility')).to.equal('true');
214+
});
215+
216+
it('Should reject non-function controller members when mounting', () => {
217+
const controller = { broken: 'nope' };
218+
219+
expect(() => assertHandler(controller, 'broken')).to.throw(
220+
'Controller member "broken" is not a function'
221+
);
222+
});
223+
224+
it('Should cover the remaining http method decorators', async () => {
225+
@Controller('/extra')
226+
class ExtraController {
227+
@All('/all')
228+
public all({ res }: TestRouterProps) {
229+
res.set('text/plain', 'all');
230+
}
231+
232+
@Connect('/connect')
233+
public connect({ res }: TestRouterProps) {
234+
res.set('text/plain', 'connect');
235+
}
236+
237+
@Delete('/delete')
238+
public remove({ res }: TestRouterProps) {
239+
res.set('text/plain', 'delete');
240+
}
241+
242+
@Head('/head')
243+
public head({ res }: TestRouterProps) {
244+
res.set('text/plain', 'head');
245+
}
246+
247+
@Options('/options')
248+
public options({ res }: TestRouterProps) {
249+
res.set('text/plain', 'options');
250+
}
251+
252+
@Patch('/patch')
253+
public patch({ res }: TestRouterProps) {
254+
res.set('text/plain', 'patch');
255+
}
256+
257+
@Put('/put')
258+
public put({ res }: TestRouterProps) {
259+
res.set('text/plain', 'put');
260+
}
261+
262+
@Trace('/trace')
263+
public trace({ res }: TestRouterProps) {
264+
res.set('text/plain', 'trace');
265+
}
266+
}
267+
268+
const router = new Router();
269+
mount(router, ExtraController);
270+
271+
const expectations: Array<[ string, string, string ]> = [
272+
[ 'POST', '/extra/all', 'all' ],
273+
[ 'CONNECT', '/extra/connect', 'connect' ],
274+
[ 'DELETE', '/extra/delete', 'delete' ],
275+
[ 'HEAD', '/extra/head', 'head' ],
276+
[ 'OPTIONS', '/extra/options', 'options' ],
277+
[ 'PATCH', '/extra/patch', 'patch' ],
278+
[ 'PUT', '/extra/put', 'put' ],
279+
[ 'TRACE', '/extra/trace', 'trace' ]
280+
];
281+
282+
for (const [ method, pathname, body ] of expectations) {
283+
const req = new Request({
284+
method: method as 'POST',
285+
url: new URL(`http://localhost${pathname}`)
286+
});
287+
const res = new Response();
288+
await router.emit(`${method} ${pathname}`, req, res);
289+
expect(res.body).to.equal(body);
290+
}
291+
});
292+
293+
it('Should support the standard decorator initializer path directly', () => {
294+
class StandardController {
295+
public handler() {
296+
return 'ok';
297+
}
298+
}
299+
300+
let routeInitializer: ((this: StandardController) => void)|undefined;
301+
let eventInitializer: ((this: StandardController) => void)|undefined;
302+
303+
routeDecorator('GET')('/standard', 3)(
304+
StandardController.prototype.handler,
305+
{
306+
addInitializer(callback) {
307+
routeInitializer = callback as (this: StandardController) => void;
308+
},
309+
kind: 'method',
310+
name: 'handler',
311+
static: false
312+
} as ClassMethodDecoratorContext
313+
);
314+
315+
On('GET /standard', 5)(
316+
StandardController.prototype.handler,
317+
{
318+
addInitializer(callback) {
319+
eventInitializer = callback as (this: StandardController) => void;
320+
},
321+
kind: 'method',
322+
name: 'handler',
323+
static: false
324+
} as ClassMethodDecoratorContext
325+
);
326+
327+
const controller = new StandardController();
328+
routeInitializer?.call(controller);
329+
eventInitializer?.call(controller);
330+
331+
const metadata = metadataOf(StandardController);
332+
333+
expect(metadata.routes).to.deep.equal([ {
334+
method: 'GET',
335+
path: '/standard',
336+
property: 'handler',
337+
priority: 3
338+
} ]);
339+
expect(metadata.events).to.deep.equal([ {
340+
event: 'GET /standard',
341+
property: 'handler',
342+
priority: 5
343+
} ]);
344+
});
345+
346+
it('Should reject static standard decorator targets', () => {
347+
expect(() => routeDecorator('GET')('/bad')(
348+
() => void 0,
349+
{
350+
addInitializer() {
351+
return void 0;
352+
},
353+
kind: 'method',
354+
name: 'bad',
355+
static: true
356+
} as ClassMethodDecoratorContext
357+
)).to.throw('Decorator "bad" must be an instance method');
358+
359+
expect(() => On('bad')(
360+
() => void 0,
361+
{
362+
addInitializer() {
363+
return void 0;
364+
},
365+
kind: 'method',
366+
name: 'bad',
367+
static: true
368+
} as ClassMethodDecoratorContext
369+
)).to.throw('Decorator "bad" must be an instance method');
370+
});
130371
});

0 commit comments

Comments
 (0)