Skip to content

Commit

Permalink
Add tests for App Router utils (#1554)
Browse files Browse the repository at this point in the history
* * If code is not present in token handler pass `undefined` to the authorize endpoint
* `tokenHandler` tests
* `faustRouteHandler` tests

* `fetchAccessToken` tests
  • Loading branch information
blakewilson authored Aug 29, 2023
1 parent 567a439 commit fae75bd
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function tokenHandler(req: Request) {
}

const { url } = req;
const code = new URL(url).searchParams.get('code');
const code = new URL(url).searchParams.get('code') ?? undefined;

const cookieStore = cookies();
const cookieName = `${getWpUrl()}-rt`;
Expand Down Expand Up @@ -80,7 +80,7 @@ export async function tokenHandler(req: Request) {
* and expiration.
*/

const res = NextResponse.json(data, {
const res = new NextResponse(JSON.stringify(data), {
status: 200,
});

Expand Down
113 changes: 113 additions & 0 deletions packages/experimental-app-router/tests/auth/fetchAccessToken.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import 'isomorphic-fetch';
import * as fetchAccessToken from '../../src/server/auth/fetchAccessToken.js';
import fetchMock from 'fetch-mock';
import { cookies } from 'next/headers.js';

// // https://github.com/aelbore/esbuild-jest/issues/26#issuecomment-893763840
const nextHeaders = { cookies };

jest.mock('next/headers.js');

describe('fetchAccessToken', () => {
const envBackup = process.env;

beforeEach(() => {
process.env = { ...envBackup };
});

afterEach(() => {
jest.clearAllMocks();
fetchMock.restore();
});

afterAll(() => {
process.env = envBackup;
});

it('returns null if no code or refresh token is present', async () => {
const cookiesSpy = jest.spyOn(nextHeaders, 'cookies');

// No refresh token
cookiesSpy.mockReturnValue({
has() {
return false;
},
} as any);

const token = await fetchAccessToken.fetchAccessToken();

expect(token).toBe(null);
});

it('makes a request to the token endpoint with the code if given', async () => {
process.env.NEXT_PUBLIC_URL = 'http://localhost:3000';

const cookiesSpy = jest.spyOn(nextHeaders, 'cookies');

// No refresh token
cookiesSpy.mockReturnValue({
has() {
return false;
},
} as any);

const code = 'my code';

// Ensures proper URL encoding
fetchMock.get(`http://localhost:3000/api/faust/token?code=my%20code`, {
status: 200,
body: {
accessToken: 'valid-token',
},
});

const token = await fetchAccessToken.fetchAccessToken(code);

expect(token).toBe('valid-token');
});

it('returns null if the token response was not ok', async () => {
process.env.NEXT_PUBLIC_URL = 'http://localhost:3000';

const cookiesSpy = jest.spyOn(nextHeaders, 'cookies');

// No refresh token
cookiesSpy.mockReturnValue({
has() {
return true;
},
} as any);

fetchMock.get(`http://localhost:3000/api/faust/token`, {
status: 401,
});

const token = await fetchAccessToken.fetchAccessToken();

expect(token).toBeNull();
});

it('properly returns the access token', async () => {
process.env.NEXT_PUBLIC_URL = 'http://localhost:3000';

const cookiesSpy = jest.spyOn(nextHeaders, 'cookies');

// No refresh token
cookiesSpy.mockReturnValue({
has() {
return true;
},
} as any);

fetchMock.get(`http://localhost:3000/api/faust/token`, {
status: 200,
body: {
accessToken: 'valid-token'
}
});

const token = await fetchAccessToken.fetchAccessToken();

expect(token).toBe('valid-token');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'isomorphic-fetch';
import { NextRequest } from 'next/server';
import * as faustRouteHandler from '../../../src/server/routeHandler/index.js';
import * as tokenHandler from '../../../src/server/routeHandler/tokenHandler.js';
import * as nextNavigation from 'next/navigation.js';
jest.mock('next/navigation.js');

describe('faustRouteHandler', () => {
it('Returns 404 if there are no matching endpoints', async () => {
const notFoundSpy = jest
.spyOn(nextNavigation, 'notFound')
.mockImplementation();

const request = new NextRequest(
new Request('http://localhost:3000/api/faust/testing'),
);

const response = await faustRouteHandler.faustRouteHandler.GET(request);

expect(notFoundSpy).toHaveBeenCalledTimes(1);
});

it('returns the token endpoint given the correct request url', async () => {
const tokenHandlerSpy = jest
.spyOn(tokenHandler, 'tokenHandler')
.mockImplementation();

const request = new NextRequest(
new Request('http://localhost:3000/api/faust/token'),
);

const response = await faustRouteHandler.faustRouteHandler.GET(request);

expect(tokenHandlerSpy).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import 'isomorphic-fetch';
import * as tokenHandler from '../../../src/server/routeHandler/tokenHandler';
jest.mock('next/headers.js');
import { cookies } from 'next/headers.js';
import fetchMock from 'fetch-mock';
import { NextRequest } from 'next/server';

// // https://github.com/aelbore/esbuild-jest/issues/26#issuecomment-893763840
const nextHeaders = { cookies };

describe('tokenHandler', () => {
const envBackup = process.env;

beforeEach(() => {
process.env = { ...envBackup };
});

afterEach(() => {
jest.clearAllMocks();
fetchMock.restore();
});

afterAll(() => {
process.env = envBackup;
});

it('throws a 500 error if the secret key is not set', async () => {
const req = new Request('http://localhost:3000/api/faust/token');

const response = await tokenHandler.tokenHandler(req);

expect(response.status).toBe(500);
expect(await response.json()).toStrictEqual({
error: 'Internal Server Error',
});
});

it('throws a 401 when the refresh token or code is not present', async () => {
const cookiesSpy = jest.spyOn(nextHeaders, 'cookies');

// No refresh token
cookiesSpy.mockReturnValue({
get() {
return {};
},
} as any);

process.env.FAUST_SECRET_KEY = 'xxxx';

const req = new Request('http://localhost:3000/api/faust/token');

const response = await tokenHandler.tokenHandler(req);

expect(response.status).toBe(401);
expect(await response.json()).toStrictEqual({
error: 'Unauthorized',
});
});

it('returns 401 if wp endpoint response was not ok', async () => {
process.env.NEXT_PUBLIC_WORDPRESS_URL = 'http://headless.local';
process.env.FAUST_SECRET_KEY = 'xxxx';

const cookiesSpy = jest.spyOn(nextHeaders, 'cookies');

// No refresh token
cookiesSpy.mockReturnValue({
get() {
return { value: 'my-invalid-rt' };
},
} as any);

fetchMock.post('http://headless.local/?rest_route=/faustwp/v1/authorize', {
status: 401,
});

const req = new NextRequest(
new Request('http://localhost:3000/api/faust/token'),
);

const response = await tokenHandler.tokenHandler(req);

expect(response.status).toBe(401);
expect(await response.json()).toStrictEqual({ error: 'Unauthorized' });
});

it('successfully returns tokens using refresh token', async () => {
process.env.NEXT_PUBLIC_WORDPRESS_URL = 'http://headless.local';
process.env.FAUST_SECRET_KEY = 'xxxx';

const validResponse: tokenHandler.AuthorizeResponse = {
accessToken: 'at',
accessTokenExpiration: 1234,
refreshToken: 'rt',
refreshTokenExpiration: 1234,
};

const cookiesSpy = jest.spyOn(nextHeaders, 'cookies');

// No refresh token
cookiesSpy.mockReturnValue({
get() {
return { value: 'my-valid-rt' };
},
} as any);

fetchMock.post(
{
url: 'http://headless.local/?rest_route=/faustwp/v1/authorize',
headers: {
'Content-Type': 'application/json',
'x-faustwp-secret': 'xxxx',
},
body: {
refreshToken: 'my-valid-rt',
code: 'my-code',
},
},
{
status: 200,
body: JSON.stringify(validResponse),
},
);

const req = new NextRequest(
new Request('http://localhost:3000/api/faust/token?code=my-code'),
);

const response = await tokenHandler.tokenHandler(req);

expect(response.status).toBe(200);
expect(await response.json()).toStrictEqual(validResponse);
});
});

1 comment on commit fae75bd

@headless-platform-by-wp-engine

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out the recent updates to your Atlas environment:

App Environment URL Build
faustjs canary https://hg…wered.com ✅ (logs)

Learn more about building on Atlas in our documentation.

Please sign in to comment.