Skip to content

Commit f762a7a

Browse files
authored
src: simplify router, add cache middleware tests (#713)
Signed-off-by: flakey5 <[email protected]>
1 parent 8137114 commit f762a7a

File tree

11 files changed

+455
-247
lines changed

11 files changed

+455
-247
lines changed

src/context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ import type { Env } from './env';
44
export interface Context {
55
env: Env;
66
execution: ExecutionContext;
7-
sentry: Toucan;
7+
sentry?: Toucan;
88
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { expect, test, vi } from 'vitest';
2+
import { cached } from './cacheMiddleware';
3+
import type { Context } from '../context';
4+
import type { Middleware } from './middleware';
5+
6+
class TestMiddleware implements Middleware {
7+
#successful = true;
8+
9+
constructor(successful = true) {
10+
this.#successful = successful;
11+
}
12+
13+
async handle(): Promise<Response> {
14+
return new Response('asd', { status: this.#successful ? 200 : 500 });
15+
}
16+
}
17+
18+
test('bypasses cache when caching is disabled', async () => {
19+
const ctx: Context = {
20+
// @ts-expect-error don't care
21+
env: {
22+
CACHING: false,
23+
},
24+
// @ts-expect-error don't care
25+
execution: {
26+
waitUntil: (): void => {
27+
throw new Error('should not hit this');
28+
},
29+
},
30+
};
31+
32+
const middleware = cached(new TestMiddleware());
33+
34+
// @ts-expect-error don't need WorkerRequest instance here
35+
await middleware.handle(new Request('https://localhost'), ctx);
36+
});
37+
38+
test('returns cached response', async () => {
39+
const request = new Request('https://localhost');
40+
const cachedResponse = new Response('cached');
41+
42+
vi.stubGlobal(
43+
'caches',
44+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
45+
(() => ({
46+
open: vi.fn(() => ({
47+
match(requestToMatch: Request): Promise<Response | undefined> {
48+
expect(requestToMatch).toStrictEqual(request);
49+
return Promise.resolve(cachedResponse);
50+
},
51+
put(): Promise<void> {
52+
throw new Error('should not hit this');
53+
},
54+
})),
55+
}))()
56+
);
57+
58+
const ctx: Context = {
59+
// @ts-expect-error don't care
60+
env: {
61+
CACHING: true,
62+
},
63+
};
64+
65+
try {
66+
const middleware = cached(new TestMiddleware());
67+
68+
// @ts-expect-error don't need WorkerRequest instance here
69+
const res = await middleware.handle(new Request('https://localhost'), ctx);
70+
expect(res).toStrictEqual(cachedResponse);
71+
} finally {
72+
vi.unstubAllGlobals();
73+
}
74+
});
75+
76+
test('caches successful response', async () => {
77+
let responseCached = false;
78+
const request = new Request('https://localhost');
79+
80+
vi.stubGlobal(
81+
'caches',
82+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
83+
(() => ({
84+
open: vi.fn(() => ({
85+
match(): Promise<Response | undefined> {
86+
return Promise.resolve(undefined);
87+
},
88+
put(requestToCache: Request, _: Response): Promise<void> {
89+
responseCached = true;
90+
expect(requestToCache).toStrictEqual(request);
91+
92+
return Promise.resolve();
93+
},
94+
})),
95+
}))()
96+
);
97+
98+
const ctx: Context = {
99+
// @ts-expect-error don't care
100+
env: {
101+
CACHING: true,
102+
},
103+
// @ts-expect-error don't care
104+
execution: {
105+
waitUntil(_: Promise<unknown>): void {},
106+
},
107+
};
108+
109+
try {
110+
const middleware = cached(new TestMiddleware());
111+
112+
// @ts-expect-error don't need WorkerRequest instance here
113+
const res = await middleware.handle(request, ctx);
114+
expect(await res.text()).toStrictEqual('asd');
115+
116+
expect(responseCached).toBeTruthy();
117+
} finally {
118+
vi.unstubAllGlobals();
119+
}
120+
});
121+
122+
test('does not cache non-200 response', async () => {
123+
vi.stubGlobal(
124+
'caches',
125+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
126+
(() => ({
127+
open: vi.fn(() => ({
128+
match(): Promise<Response | undefined> {
129+
return Promise.resolve(undefined);
130+
},
131+
put(): Promise<void> {
132+
throw new Error('should not hit this');
133+
},
134+
})),
135+
}))()
136+
);
137+
138+
const ctx: Context = {
139+
// @ts-expect-error don't care
140+
env: {
141+
CACHING: true,
142+
},
143+
// @ts-expect-error don't care
144+
execution: {
145+
waitUntil(_: Promise<unknown>): void {},
146+
},
147+
};
148+
149+
try {
150+
const middleware = cached(new TestMiddleware(false));
151+
152+
// @ts-expect-error don't need WorkerRequest instance here
153+
const res = await middleware.handle(new Request('https://localhost'), ctx);
154+
expect(await res.text()).toStrictEqual('asd');
155+
} finally {
156+
vi.unstubAllGlobals();
157+
}
158+
});

src/middleware/cacheMiddleware.ts

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,39 +21,30 @@ export function cached(middleware: Middleware): Middleware {
2121
let cache: Cache;
2222

2323
const wrapper: Middleware = {
24-
async handle(request, ctx, next) {
25-
ctx.sentry.addBreadcrumb({
24+
async handle(request, ctx) {
25+
ctx.sentry?.addBreadcrumb({
2626
category: 'CacheMiddleware',
2727
data: {
2828
underlyingMiddleware: middleware.constructor.name,
2929
},
3030
});
3131

32+
// Caching globally disabled, skip
3233
if (!ctx.env.CACHING) {
33-
return middleware.handle(request, ctx, next);
34+
return middleware.handle(request, ctx);
3435
}
3536

36-
if (cache === undefined) {
37-
cache = await caches.open(middleware.constructor.name);
38-
}
37+
cache ??= await caches.open(middleware.constructor.name);
3938

4039
// Check if the request is in the cache already, return it if so
4140
let response = await cache.match(request);
4241
if (response !== undefined) {
4342
return response;
4443
}
4544

46-
// Set to true when the middleware this wraps calls next().
47-
// We only want to cache the result for this middleware,
48-
// not the one after this.
49-
let wasDeferred = false;
50-
51-
response = await middleware.handle(request, ctx, () => {
52-
wasDeferred = true;
53-
return next();
54-
});
45+
response = await middleware.handle(request, ctx);
5546

56-
if (!wasDeferred && response.status === 200) {
47+
if (response instanceof Response && response.status === 200) {
5748
// Successful request, let's cache it for next time
5849
const cachedResponse = response.clone();
5950

src/middleware/middleware.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,5 @@ export interface Middleware {
99
* @param next Calls the next middleware in the chain. This may also call
1010
* other middlewares before returning.
1111
*/
12-
handle(
13-
request: Request,
14-
ctx: Context,
15-
next: MiddlewareNext
16-
): Promise<Response>;
12+
handle(request: Request, ctx: Context): Promise<Response>;
1713
}

src/middleware/substituteMiddleware.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, test, expect } from 'vitest';
22
import { SubtitutionMiddleware } from './subtituteMiddleware';
33
import type { Request as WorkerRequest } from '../routes/request';
4-
import type { Router } from '../routes';
4+
import type { Router } from '../routes/router';
55

66
describe('SubtituteMiddleware', () => {
77
test('correctly substitutes url `/dist/latest` to `/dist/v1.0.0`', () => {
@@ -11,7 +11,7 @@ describe('SubtituteMiddleware', () => {
1111
originalRequest.urlObj = new URL(originalUrl);
1212

1313
const router: Partial<Router> = {
14-
handle: (substitutedRequest: WorkerRequest, _, unsubstitutedUrl) => {
14+
fetch: (substitutedRequest: WorkerRequest, _, unsubstitutedUrl) => {
1515
// Has the url been substituted? (latest -> v1.0.0)
1616
// strictEqual(substitutedRequest.url, 'https://localhost/dist/v1.0.0');
1717
expect(substitutedRequest.url).toStrictEqual(

src/middleware/subtituteMiddleware.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Context } from '../context';
2-
import type { Router } from '../routes';
2+
import type { Router } from '../routes/router';
33
import type { Request } from '../routes/request';
44
import type { Middleware } from './middleware';
55

@@ -25,7 +25,7 @@ export class SubtitutionMiddleware implements Middleware {
2525
handle(request: Request, ctx: Context): Promise<Response> {
2626
const newUrl = request.url.replaceAll(this.searchValue, this.replaceValue);
2727

28-
ctx.sentry.addBreadcrumb({
28+
ctx.sentry?.addBreadcrumb({
2929
type: 'navigation',
3030
category: 'SubstitutionMiddleware',
3131
data: {
@@ -39,6 +39,6 @@ export class SubtitutionMiddleware implements Middleware {
3939
new Request(request)
4040
);
4141

42-
return this.router.handle(substitutedRequest, ctx, request.urlObj);
42+
return this.router.fetch(substitutedRequest, ctx, request.urlObj);
4343
}
4444
}

src/routes/index.ts

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ export function registerRoutes(router: Router): void {
1919
'https://github.com/nodejs/corepack#readme'
2020
);
2121

22-
router.options('*', [new OptionsMiddleware()]);
22+
router.options('*', new OptionsMiddleware());
2323

24-
router.head('/metrics/?:filePath+', [r2Middleware, originMiddleware]);
25-
router.get('/metrics/?:filePath+', [cachedR2Middleware, originMiddleware]);
24+
router.head('/metrics/?:filePath+', r2Middleware, originMiddleware);
25+
router.get('/metrics/?:filePath+', cachedR2Middleware, originMiddleware);
2626

27-
router.all('/api/corepack.html', [corepackRedirectMiddleware]);
28-
router.all('/docs/latest/api/corepack.html', [corepackRedirectMiddleware]);
27+
router.all('/api/corepack.html', corepackRedirectMiddleware);
28+
router.all('/docs/latest/api/corepack.html', corepackRedirectMiddleware);
2929

3030
// Register routes for latest releases (e.g. `/dist/latest/`)
3131
for (const branch in latestVersions) {
@@ -36,39 +36,40 @@ export function registerRoutes(router: Router): void {
3636
latestVersion
3737
);
3838

39-
router.head(`/dist/${branch}*`, [subtitutionMiddleware]);
40-
router.get(`/dist/${branch}*`, [subtitutionMiddleware]);
39+
router.head(`/dist/${branch}*`, subtitutionMiddleware);
40+
router.get(`/dist/${branch}*`, subtitutionMiddleware);
4141

42-
router.head(`/download/release/${branch}*`, [subtitutionMiddleware]);
43-
router.get(`/download/release/${branch}*`, [subtitutionMiddleware]);
42+
router.head(`/download/release/${branch}*`, subtitutionMiddleware);
43+
router.get(`/download/release/${branch}*`, subtitutionMiddleware);
4444

45-
router.head(`/docs/${branch}*`, [subtitutionMiddleware]);
46-
router.get(`/docs/${branch}*`, [subtitutionMiddleware]);
45+
router.head(`/docs/${branch}*`, subtitutionMiddleware);
46+
router.get(`/docs/${branch}*`, subtitutionMiddleware);
4747
}
4848

49-
router.head('/node-config-schema.json', [r2Middleware]);
50-
router.get('/node-config-schema.json', [r2Middleware]);
49+
router.head('/node-config-schema.json', r2Middleware);
50+
router.get('/node-config-schema.json', r2Middleware);
5151

52-
router.head('/dist/?:filePath+', [r2Middleware, originMiddleware]);
53-
router.get('/dist/?:filePath+', [cachedR2Middleware, originMiddleware]);
52+
router.head('/dist/?:filePath+', r2Middleware, originMiddleware);
53+
router.get('/dist/?:filePath+', cachedR2Middleware, originMiddleware);
5454

55-
router.head('/download/?:filePath+', [r2Middleware, originMiddleware]);
56-
router.get('/download/?:filePath+', [cachedR2Middleware, originMiddleware]);
55+
router.head('/download/?:filePath+', r2Middleware, originMiddleware);
56+
router.get('/download/?:filePath+', cachedR2Middleware, originMiddleware);
5757

58-
router.head('/api/?:filePath+', [r2Middleware, originMiddleware]);
59-
router.get('/api/?:filePath+', [cachedR2Middleware, originMiddleware]);
58+
router.head('/api/?:filePath+', r2Middleware, originMiddleware);
59+
router.get('/api/?:filePath+', cachedR2Middleware, originMiddleware);
6060

61-
router.head('/docs/?:version?/:filePath+?', [r2Middleware, originMiddleware]);
62-
router.get('/docs/?:version?/:filePath+?', [
61+
router.head('/docs/?:version?/:filePath+?', r2Middleware, originMiddleware);
62+
router.get(
63+
'/docs/?:version?/:filePath+?',
6364
cachedR2Middleware,
64-
originMiddleware,
65-
]);
65+
originMiddleware
66+
);
6667

67-
router.post('/_throw', [new ThrowMiddleware()]);
68+
router.post('/_throw', new ThrowMiddleware());
6869

69-
router.get('*', [new NotFoundMiddleware()]);
70+
router.get('*', new NotFoundMiddleware());
7071

71-
router.all('*', [new MethodNotAllowedMiddleware()]);
72+
router.all('*', new MethodNotAllowedMiddleware());
7273
}
7374

7475
export * from './router';

0 commit comments

Comments
 (0)