Skip to content

Commit dbaaa91

Browse files
committed
Mock Next.js in page router tests
1 parent cecf38e commit dbaaa91

21 files changed

+128
-146
lines changed

.github/workflows/test.yml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Test
2+
3+
on: [push]
4+
5+
jobs:
6+
build:
7+
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- uses: actions/checkout@v4
12+
- name: Use Node.js
13+
uses: actions/setup-node@v3
14+
with:
15+
node-version: '18.x'
16+
- run: npm ci
17+
- run: npm test

CONTRIBUTING.md

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ Please read [Auth0's contribution guidelines](https://github.com/auth0/open-sour
1212

1313
- `npm install`: install dependencies
1414
- `npm run build`: Build the binary
15-
- `npm run build:test`: Do this once to build the test harness for the tests
1615
- `npm test`: Run the unit tests
1716
- `npm run test:watch`: Run the unit tests and watch for changes
1817
- `npm run install:example`: Install the examples

jest-base.config.js

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ module.exports = {
33
rootDir: '.',
44
moduleFileExtensions: ['ts', 'tsx', 'js'],
55
preset: 'ts-jest/presets/js-with-ts',
6-
globalSetup: './tests/global-setup.ts',
76
setupFilesAfterEnv: ['./tests/setup.ts'],
87
transformIgnorePatterns: ['/node_modules/(?!oauth4webapi)']
98
};

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
"prepack": "npm run build",
3636
"install:example": "npm i --prefix=example-app --no-package-lock",
3737
"build": "npm run clean && tsc -p tsconfig.build.json",
38-
"build:test": "next build tests/fixtures/test-app",
3938
"build:example": "npm run build --prefix=example-app",
4039
"build:vercel": "npm run install:example && npm run build && npm run build:example",
4140
"start:example": "npm run dev --prefix=example-app",

tests/auth0-session/fixtures/server.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { createServer as createHttpsServer, Server as HttpsServer } from 'https'
44
import url from 'url';
55
import nock from 'nock';
66
import { TokenSet, TokenSetParameters } from 'openid-client';
7-
import bodyParser from 'body-parser';
87
import {
98
loginHandler,
109
getConfig,
@@ -94,9 +93,10 @@ const createHandlers = (params: ConfigParameters): Handlers => {
9493
};
9594
};
9695

97-
const jsonParse = bodyParser.json();
98-
const parseJson = (req: IncomingMessage, res: ServerResponse): Promise<IncomingMessage> =>
99-
new Promise((resolve, reject) => {
96+
export const parseJson = async (req: IncomingMessage, res: ServerResponse): Promise<IncomingMessage> => {
97+
const { default: bodyParser } = await import('body-parser');
98+
const jsonParse = bodyParser.json();
99+
return await new Promise((resolve, reject) => {
100100
jsonParse(req, res, (error: Error | undefined) => {
101101
if (error) {
102102
reject(error);
@@ -105,6 +105,7 @@ const parseJson = (req: IncomingMessage, res: ServerResponse): Promise<IncomingM
105105
}
106106
});
107107
});
108+
};
108109

109110
const requestListener =
110111
(

tests/fixtures/server.ts

+106-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,115 @@
1-
import { createServer as createHttpServer, Server } from 'http';
2-
import next from 'next';
3-
import * as path from 'path';
4-
import { parse } from 'url';
1+
import { createServer as createHttpServer, IncomingMessage, Server, ServerResponse } from 'http';
2+
import { NextApiRequest, NextApiResponse } from 'next';
3+
import * as qs from 'querystring';
4+
import * as cookie from 'cookie';
55
import { AddressInfo } from 'net';
6+
import { parseJson } from '../auth0-session/fixtures/server';
67

78
let server: Server;
89

10+
const toNextApiRequest = async (req: IncomingMessage): Promise<NextApiRequest> => {
11+
const parsedReq = await parseJson(req, new ServerResponse(req));
12+
const apiReq = parsedReq as NextApiRequest;
13+
apiReq.query = qs.parse(new URL(req.url!, 'http://example.com').search.slice(1));
14+
apiReq.cookies = cookie.parse((req.headers.cookie as string) || '');
15+
return apiReq;
16+
};
17+
18+
const toNextApiResponse = async (res: ServerResponse): Promise<NextApiResponse> => {
19+
const apiRes = res as NextApiResponse;
20+
21+
apiRes.status = (statusCode) => {
22+
apiRes.statusCode = statusCode;
23+
return apiRes;
24+
};
25+
apiRes.send = apiRes.end.bind(apiRes);
26+
apiRes.json = (data) => {
27+
apiRes.setHeader('Content-Type', 'application/json; charset=utf-8');
28+
apiRes.send(JSON.stringify(data));
29+
};
30+
apiRes.redirect = (statusOrUrl: string | number, url?: string) => {
31+
if (typeof statusOrUrl === 'string') {
32+
url = statusOrUrl;
33+
statusOrUrl = 307;
34+
}
35+
apiRes.writeHead(statusOrUrl, { Location: url });
36+
apiRes.write(url);
37+
apiRes.end();
38+
return apiRes;
39+
};
40+
41+
return apiRes;
42+
};
43+
44+
const handle = async (req: NextApiRequest, res: NextApiResponse) => {
45+
const [path] = req.url!.split('?');
46+
if (path.startsWith('/api/auth')) {
47+
req.query.auth0 = path.split('/').slice(3);
48+
await (global.handleAuth?.())(req, res);
49+
return;
50+
}
51+
switch (path) {
52+
case '/api/access-token':
53+
{
54+
try {
55+
const json = await global.getAccessToken?.(req, res);
56+
res.status(200).json(json);
57+
} catch (error) {
58+
res.statusMessage = error.message;
59+
res.status(error.status || 500).end(error.message);
60+
}
61+
}
62+
break;
63+
case '/api/protected':
64+
{
65+
(
66+
await global.withApiAuthRequired?.(function protectedApiRoute() {
67+
res.status(200).json({ foo: 'bar' });
68+
})
69+
)(req, res);
70+
}
71+
break;
72+
case '/api/session':
73+
{
74+
const json = await global.getSession?.(req, res);
75+
res.status(200).json(json);
76+
}
77+
break;
78+
case '/api/touch-session':
79+
{
80+
await global.touchSession?.(req, res);
81+
const json = await global.getSession?.(req, res);
82+
res.status(200).json(json);
83+
}
84+
break;
85+
case '/api/update-session':
86+
{
87+
const session = await global.getSession?.(req, res);
88+
const updated = { ...session, ...req.body?.session };
89+
await global.updateSession?.(req, res, updated);
90+
res.status(200).json(updated);
91+
}
92+
break;
93+
case '/protected':
94+
const ret = await global.withPageAuthRequired?.()({ req, res, resolvedUrl: path });
95+
if (ret.redirect) {
96+
res.redirect(ret.redirect.destination);
97+
} else {
98+
const user = (await ret.props).user;
99+
res.send(`<div>Protected Page ${user ? user.sub : ''}</div>`);
100+
}
101+
break;
102+
default:
103+
res.status(418).end();
104+
return;
105+
}
106+
};
107+
9108
export const start = async (): Promise<string> => {
10-
const app = next({ dev: false, dir: path.join(__dirname, 'test-app') });
11-
await app.prepare();
12-
const handle = app.getRequestHandler();
13109
server = createHttpServer(async (req, res) => {
14-
const parsedUrl = parse(req.url as string, true);
15-
await handle(req, res, parsedUrl);
110+
const apiReq = await toNextApiRequest(req);
111+
const apiRes = await toNextApiResponse(res);
112+
await handle(apiReq, apiRes);
16113
});
17114
const port = await new Promise((resolve) => server.listen(0, () => resolve((server.address() as AddressInfo).port)));
18115
return `http://localhost:${port}`;

tests/fixtures/test-app/next-env.d.ts

-5
This file was deleted.

tests/fixtures/test-app/pages/_document.tsx

-14
This file was deleted.

tests/fixtures/test-app/pages/api/access-token.ts

-11
This file was deleted.

tests/fixtures/test-app/pages/api/auth/[...auth0].ts

-2
This file was deleted.

tests/fixtures/test-app/pages/api/protected.ts

-5
This file was deleted.

tests/fixtures/test-app/pages/api/session.ts

-6
This file was deleted.

tests/fixtures/test-app/pages/api/touch-session.ts

-7
This file was deleted.

tests/fixtures/test-app/pages/api/update-session.ts

-8
This file was deleted.

tests/fixtures/test-app/pages/csr-protected.tsx

-9
This file was deleted.

tests/fixtures/test-app/pages/global.d.ts

-1
This file was deleted.

tests/fixtures/test-app/pages/index.tsx

-5
This file was deleted.

tests/fixtures/test-app/pages/protected.tsx

-8
This file was deleted.

tests/fixtures/test-app/tsconfig.json

-30
This file was deleted.

tests/global-setup.ts

-10
This file was deleted.

tests/helpers/with-page-auth-required.test.ts

-9
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,6 @@ describe('with-page-auth-required ssr', () => {
170170
delete process.env.NEXT_PUBLIC_AUTH0_LOGIN;
171171
});
172172

173-
test('is a no-op when invoked as a client-side protection from the server', async () => {
174-
const baseUrl = await setup(withoutApi);
175-
const cookieJar = await login(baseUrl);
176-
const {
177-
res: { statusCode }
178-
} = await get(baseUrl, '/csr-protected', { cookieJar, fullResponse: true });
179-
expect(statusCode).toBe(200);
180-
});
181-
182173
test('should preserve multiple query params in the returnTo URL', async () => {
183174
const baseUrl = await setup(withoutApi, { withPageAuthRequiredOptions: { returnTo: '/foo?bar=baz&qux=quux' } });
184175
const {

0 commit comments

Comments
 (0)