Skip to content

Commit

Permalink
Mock Next.js in page router tests (#1489)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamjmcgrath authored Oct 12, 2023
2 parents 8373cd7 + d95d683 commit 71ec088
Show file tree
Hide file tree
Showing 20 changed files with 111 additions and 146 deletions.
1 change: 0 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Please read [Auth0's contribution guidelines](https://github.com/auth0/open-sour

- `npm install`: install dependencies
- `npm run build`: Build the binary
- `npm run build:test`: Do this once to build the test harness for the tests
- `npm test`: Run the unit tests
- `npm run test:watch`: Run the unit tests and watch for changes
- `npm run install:example`: Install the examples
Expand Down
1 change: 0 additions & 1 deletion jest-base.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module.exports = {
rootDir: '.',
moduleFileExtensions: ['ts', 'tsx', 'js'],
preset: 'ts-jest/presets/js-with-ts',
globalSetup: './tests/global-setup.ts',
setupFilesAfterEnv: ['./tests/setup.ts'],
transformIgnorePatterns: ['/node_modules/(?!oauth4webapi)']
};
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"prepack": "npm run build",
"install:example": "npm i --prefix=example-app --no-package-lock",
"build": "npm run clean && tsc -p tsconfig.build.json",
"build:test": "next build tests/fixtures/test-app",
"build:example": "npm run build --prefix=example-app",
"build:vercel": "npm run install:example && npm run build && npm run build:example",
"start:example": "npm run dev --prefix=example-app",
Expand Down
9 changes: 5 additions & 4 deletions tests/auth0-session/fixtures/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { createServer as createHttpsServer, Server as HttpsServer } from 'https'
import url from 'url';
import nock from 'nock';
import { TokenSet, TokenSetParameters } from 'openid-client';
import bodyParser from 'body-parser';
import {
loginHandler,
getConfig,
Expand Down Expand Up @@ -94,9 +93,10 @@ const createHandlers = (params: ConfigParameters): Handlers => {
};
};

const jsonParse = bodyParser.json();
const parseJson = (req: IncomingMessage, res: ServerResponse): Promise<IncomingMessage> =>
new Promise((resolve, reject) => {
export const parseJson = async (req: IncomingMessage, res: ServerResponse): Promise<IncomingMessage> => {
const { default: bodyParser } = await import('body-parser');
const jsonParse = bodyParser.json();
return await new Promise((resolve, reject) => {
jsonParse(req, res, (error: Error | undefined) => {
if (error) {
reject(error);
Expand All @@ -105,6 +105,7 @@ const parseJson = (req: IncomingMessage, res: ServerResponse): Promise<IncomingM
}
});
});
};

const requestListener =
(
Expand Down
115 changes: 106 additions & 9 deletions tests/fixtures/server.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,115 @@
import { createServer as createHttpServer, Server } from 'http';
import next from 'next';
import * as path from 'path';
import { parse } from 'url';
import { createServer as createHttpServer, IncomingMessage, Server, ServerResponse } from 'http';
import { NextApiRequest, NextApiResponse } from 'next';
import * as qs from 'querystring';
import * as cookie from 'cookie';
import { AddressInfo } from 'net';
import { parseJson } from '../auth0-session/fixtures/server';

let server: Server;

const toNextApiRequest = async (req: IncomingMessage): Promise<NextApiRequest> => {
const parsedReq = await parseJson(req, new ServerResponse(req));
const apiReq = parsedReq as NextApiRequest;
apiReq.query = qs.parse(new URL(req.url!, 'http://example.com').search.slice(1));
apiReq.cookies = cookie.parse((req.headers.cookie as string) || '');
return apiReq;
};

const toNextApiResponse = async (res: ServerResponse): Promise<NextApiResponse> => {
const apiRes = res as NextApiResponse;

apiRes.status = (statusCode) => {
apiRes.statusCode = statusCode;
return apiRes;
};
apiRes.send = apiRes.end.bind(apiRes);
apiRes.json = (data) => {
apiRes.setHeader('Content-Type', 'application/json; charset=utf-8');
apiRes.send(JSON.stringify(data));
};
apiRes.redirect = (statusOrUrl: string | number, url?: string) => {
if (typeof statusOrUrl === 'string') {
url = statusOrUrl;
statusOrUrl = 307;
}
apiRes.writeHead(statusOrUrl, { Location: url });
apiRes.write(url);
apiRes.end();
return apiRes;
};

return apiRes;
};

const handle = async (req: NextApiRequest, res: NextApiResponse) => {
const [path] = req.url!.split('?');
if (path.startsWith('/api/auth')) {
req.query.auth0 = path.split('/').slice(3);
await (global.handleAuth?.())(req, res);
return;
}
switch (path) {
case '/api/access-token':
{
try {
const json = await global.getAccessToken?.(req, res);
res.status(200).json(json);
} catch (error) {
res.statusMessage = error.message;
res.status(error.status || 500).end(error.message);
}
}
break;
case '/api/protected':
{
(
await global.withApiAuthRequired?.(function protectedApiRoute() {
res.status(200).json({ foo: 'bar' });
})
)(req, res);
}
break;
case '/api/session':
{
const json = await global.getSession?.(req, res);
res.status(200).json(json);
}
break;
case '/api/touch-session':
{
await global.touchSession?.(req, res);
const json = await global.getSession?.(req, res);
res.status(200).json(json);
}
break;
case '/api/update-session':
{
const session = await global.getSession?.(req, res);
const updated = { ...session, ...req.body?.session };
await global.updateSession?.(req, res, updated);
res.status(200).json(updated);
}
break;
case '/protected':
const ret = await global.withPageAuthRequired?.()({ req, res, resolvedUrl: path });
if (ret.redirect) {
res.redirect(ret.redirect.destination);
} else {
const user = (await ret.props).user;
res.send(`<div>Protected Page ${user ? user.sub : ''}</div>`);
}
break;
default:
res.status(418).end();
return;
}
};

export const start = async (): Promise<string> => {
const app = next({ dev: false, dir: path.join(__dirname, 'test-app') });
await app.prepare();
const handle = app.getRequestHandler();
server = createHttpServer(async (req, res) => {
const parsedUrl = parse(req.url as string, true);
await handle(req, res, parsedUrl);
const apiReq = await toNextApiRequest(req);
const apiRes = await toNextApiResponse(res);
await handle(apiReq, apiRes);
});
const port = await new Promise((resolve) => server.listen(0, () => resolve((server.address() as AddressInfo).port)));
return `http://localhost:${port}`;
Expand Down
5 changes: 0 additions & 5 deletions tests/fixtures/test-app/next-env.d.ts

This file was deleted.

14 changes: 0 additions & 14 deletions tests/fixtures/test-app/pages/_document.tsx

This file was deleted.

11 changes: 0 additions & 11 deletions tests/fixtures/test-app/pages/api/access-token.ts

This file was deleted.

2 changes: 0 additions & 2 deletions tests/fixtures/test-app/pages/api/auth/[...auth0].ts

This file was deleted.

5 changes: 0 additions & 5 deletions tests/fixtures/test-app/pages/api/protected.ts

This file was deleted.

6 changes: 0 additions & 6 deletions tests/fixtures/test-app/pages/api/session.ts

This file was deleted.

7 changes: 0 additions & 7 deletions tests/fixtures/test-app/pages/api/touch-session.ts

This file was deleted.

8 changes: 0 additions & 8 deletions tests/fixtures/test-app/pages/api/update-session.ts

This file was deleted.

9 changes: 0 additions & 9 deletions tests/fixtures/test-app/pages/csr-protected.tsx

This file was deleted.

1 change: 0 additions & 1 deletion tests/fixtures/test-app/pages/global.d.ts

This file was deleted.

5 changes: 0 additions & 5 deletions tests/fixtures/test-app/pages/index.tsx

This file was deleted.

8 changes: 0 additions & 8 deletions tests/fixtures/test-app/pages/protected.tsx

This file was deleted.

30 changes: 0 additions & 30 deletions tests/fixtures/test-app/tsconfig.json

This file was deleted.

10 changes: 0 additions & 10 deletions tests/global-setup.ts

This file was deleted.

9 changes: 0 additions & 9 deletions tests/helpers/with-page-auth-required.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,6 @@ describe('with-page-auth-required ssr', () => {
delete process.env.NEXT_PUBLIC_AUTH0_LOGIN;
});

test('is a no-op when invoked as a client-side protection from the server', async () => {
const baseUrl = await setup(withoutApi);
const cookieJar = await login(baseUrl);
const {
res: { statusCode }
} = await get(baseUrl, '/csr-protected', { cookieJar, fullResponse: true });
expect(statusCode).toBe(200);
});

test('should preserve multiple query params in the returnTo URL', async () => {
const baseUrl = await setup(withoutApi, { withPageAuthRequiredOptions: { returnTo: '/foo?bar=baz&qux=quux' } });
const {
Expand Down

0 comments on commit 71ec088

Please sign in to comment.