From a91a47d115c8f595ded7b67c9e87688d1423c162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Oliveira?= Date: Tue, 9 Jul 2024 15:52:03 -0300 Subject: [PATCH 1/3] test: add tests for previewRouteHandler --- package-lock.json | 14 +- packages/next/jest.setup.ts | 2 +- packages/next/package.json | 2 +- .../handlers/__tests__/previewRouteHandler.ts | 149 +++++++++++++++++- .../__tests__/revalidateRouteHandler.ts | 33 ++++ 5 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 packages/next/src/rsc/handlers/__tests__/revalidateRouteHandler.ts diff --git a/package-lock.json b/package-lock.json index 885302cce..e1a9107e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13456,6 +13456,16 @@ "node": ">=0.10.0" } }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -22704,14 +22714,14 @@ "@types/react-dom": "^18", "copy-webpack-plugin": "^10.2.4", "expect-type": "^0.15.0", + "isomorphic-fetch": "^3.0.0", "jest": "^29.0.3", "jest-fetch-mock": "^3.0.3", "next-router-mock": "^0.9.13", "node-mocks-http": "^1.14.1", "ts-jest": "^29.0.1", "tsc-esm-fix": "^2.20.27", - "typescript": "^5.4.5", - "whatwg-fetch": "^3.6.20" + "typescript": "^5.4.5" }, "peerDependencies": { "next": ">= 12.0.0", diff --git a/packages/next/jest.setup.ts b/packages/next/jest.setup.ts index f2ad4c6e3..432652d8b 100644 --- a/packages/next/jest.setup.ts +++ b/packages/next/jest.setup.ts @@ -1,4 +1,4 @@ -import 'whatwg-fetch'; +import 'isomorphic-fetch'; import { server } from '@headstartwp/core/test'; diff --git a/packages/next/package.json b/packages/next/package.json index 02a925e29..af12534b5 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -71,7 +71,7 @@ "node-mocks-http": "^1.14.1", "ts-jest": "^29.0.1", "typescript": "^5.4.5", - "whatwg-fetch": "^3.6.20", + "isomorphic-fetch": "^3.0.0", "jest-fetch-mock": "^3.0.3", "tsc-esm-fix": "^2.20.27", "@types/react": "^18", diff --git a/packages/next/src/rsc/handlers/__tests__/previewRouteHandler.ts b/packages/next/src/rsc/handlers/__tests__/previewRouteHandler.ts index f9814bb52..474684fd6 100644 --- a/packages/next/src/rsc/handlers/__tests__/previewRouteHandler.ts +++ b/packages/next/src/rsc/handlers/__tests__/previewRouteHandler.ts @@ -1,8 +1,28 @@ +import nextHeaders from 'next/headers'; import { NextRequest } from 'next/server'; -import { previewRouteHandler } from '../previewRouteHandler'; +import { setHeadstartWPConfig } from '@headstartwp/core'; +import { DRAFT_POST_ID, VALID_AUTH_TOKEN } from '@headstartwp/core/test'; +import { COOKIE_NAME, previewRouteHandler } from '../previewRouteHandler'; + +jest.mock('next/headers', () => ({ + draftMode: () => ({ isEnabled: false, enable: jest.fn() }), + cookies: jest.fn(() => ({ + get: jest.fn(), + has: jest.fn(), + })), +})); + +const config = { + sourceUrl: 'https://js1.10up.com', + useWordPressPlugin: true, +}; describe('previewRouteHandler', () => { - it.skip('does not accepts POST requests', async () => { + beforeAll(() => { + setHeadstartWPConfig(config); + }); + + it('does not accepts POST requests', async () => { const req = new NextRequest('http://test.com', { method: 'POST', body: JSON.stringify({}), @@ -12,4 +32,129 @@ describe('previewRouteHandler', () => { expect(res.status).toBe(401); }); + + it('does not accepts invalid params', async () => { + const req = new NextRequest('http://test.com'); + + const res = await previewRouteHandler(req); + + expect(res.status).toBe(401); + }); + + it('fails if a valid auth token is not provided', async () => { + const searchParams = new URLSearchParams({ + post_id: DRAFT_POST_ID.toString(), + token: 'test', + post_type: 'post', + }); + + const req = new NextRequest(`https://js1.10up.com?${searchParams.toString()}`); + + await expect(() => previewRouteHandler(req)).rejects.toThrow( + 'Sorry, you are not allowed to view this post.', + ); + }); + + it('works if a valid auth token is provided', async () => { + const searchParams = new URLSearchParams({ + post_id: DRAFT_POST_ID.toString(), + token: VALID_AUTH_TOKEN.toString(), + post_type: 'post', + }); + + const req = new NextRequest(`https://js1.10up.com?${searchParams.toString()}`); + + const previewDataPayload = JSON.stringify({ + id: DRAFT_POST_ID, + postType: 'post', + revision: false, + authToken: VALID_AUTH_TOKEN, + }); + + // @ts-expect-error + nextHeaders.cookies.mockReturnValue({ + set: jest.fn(), + get: jest.fn(() => ({ value: previewDataPayload, name: COOKIE_NAME })), + has: jest.fn(() => true), + }); + + await expect(() => previewRouteHandler(req)).rejects.toThrow('NEXT_REDIRECT'); + + expect(nextHeaders.cookies().set).toHaveBeenCalledWith(COOKIE_NAME, previewDataPayload, { + httpOnly: true, + maxAge: 300, + path: '/modi-qui-dignissimos-sed-assumenda-sint-iusto', + }); + }); + + it('preview works for custom post types', async () => { + setHeadstartWPConfig({ + ...config, + customPostTypes: [ + { + slug: 'book', + // reuse existing posts endpoint + endpoint: '/wp-json/wp/v2/posts', + // these should match your file-system routing + single: '/book', + archive: '/books', + }, + ], + }); + + const searchParams = new URLSearchParams({ + post_id: DRAFT_POST_ID.toString(), + token: VALID_AUTH_TOKEN.toString(), + post_type: 'book', + }); + + const previewDataPayload = JSON.stringify({ + id: DRAFT_POST_ID, + postType: 'book', + revision: false, + authToken: VALID_AUTH_TOKEN, + }); + + const req = new NextRequest(`https://js1.10up.com?${searchParams.toString()}`); + + await expect(() => previewRouteHandler(req)).rejects.toThrow('NEXT_REDIRECT'); + expect(nextHeaders.cookies().set).toHaveBeenCalledWith(COOKIE_NAME, previewDataPayload, { + httpOnly: true, + maxAge: 300, + path: '/book/modi-qui-dignissimos-sed-assumenda-sint-iusto', + }); + + /* const { req: reqWithLocale, res: resWithLocale } = createMocks< + NextApiRequest, + NextApiResponse + >({ + method: 'GET', + query: { + post_id: DRAFT_POST_ID, + token: VALID_AUTH_TOKEN, + post_type: 'book', + locale: 'es', + }, + }); + + resWithLocale.setPreviewData = jest.fn(); + await previewHandler(reqWithLocale, resWithLocale); + + expect(resWithLocale.setPreviewData).toHaveBeenCalledWith( + { + authToken: 'this is a valid auth', + id: 57, + postType: 'book', + revision: false, + }, + { + maxAge: 300, + path: '/es/book/modi-qui-dignissimos-sed-assumenda-sint-iusto-preview=true', + }, + ); + expect(resWithLocale._getStatusCode()).toBe(302); + expect(resWithLocale._getRedirectUrl()).toBe( + '/es/book/modi-qui-dignissimos-sed-assumenda-sint-iusto-preview=true', + ); */ + }); }); diff --git a/packages/next/src/rsc/handlers/__tests__/revalidateRouteHandler.ts b/packages/next/src/rsc/handlers/__tests__/revalidateRouteHandler.ts new file mode 100644 index 000000000..401a2a936 --- /dev/null +++ b/packages/next/src/rsc/handlers/__tests__/revalidateRouteHandler.ts @@ -0,0 +1,33 @@ +import { setHeadstartWPConfig } from '@headstartwp/core'; +import { NextRequest } from 'next/server'; +import { revalidateRouteHandler } from '../revalidateRouterHandler'; + +const config = { + sourceUrl: 'https://js1.10up.com', + useWordPressPlugin: true, +}; + +describe('revalidateRouteHandler', () => { + beforeAll(() => { + setHeadstartWPConfig(config); + }); + + it('does not accepts POST requests', async () => { + const req = new NextRequest('http://test.com', { + method: 'POST', + body: JSON.stringify({}), + }); + + const res = await revalidateRouteHandler(req); + + expect(res.status).toBe(401); + }); + + it('does not accepts invalid params', async () => { + const req = new NextRequest('http://test.com'); + + const res = await revalidateRouteHandler(req); + + expect(res.status).toBe(401); + }); +}); From b58550021879777e735c3d9bb5563a4895431d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Oliveira?= Date: Thu, 11 Jul 2024 14:55:45 -0300 Subject: [PATCH 2/3] test: previewRouteHandler tests --- .../handlers/__tests__/previewRouteHandler.ts | 110 +++++++++++++----- 1 file changed, 84 insertions(+), 26 deletions(-) diff --git a/packages/next/src/rsc/handlers/__tests__/previewRouteHandler.ts b/packages/next/src/rsc/handlers/__tests__/previewRouteHandler.ts index 474684fd6..8326053bc 100644 --- a/packages/next/src/rsc/handlers/__tests__/previewRouteHandler.ts +++ b/packages/next/src/rsc/handlers/__tests__/previewRouteHandler.ts @@ -2,6 +2,7 @@ import nextHeaders from 'next/headers'; import { NextRequest } from 'next/server'; import { setHeadstartWPConfig } from '@headstartwp/core'; import { DRAFT_POST_ID, VALID_AUTH_TOKEN } from '@headstartwp/core/test'; +import { redirect } from 'next/navigation'; import { COOKIE_NAME, previewRouteHandler } from '../previewRouteHandler'; jest.mock('next/headers', () => ({ @@ -18,7 +19,7 @@ const config = { }; describe('previewRouteHandler', () => { - beforeAll(() => { + beforeEach(() => { setHeadstartWPConfig(config); }); @@ -87,7 +88,7 @@ describe('previewRouteHandler', () => { }); }); - it('preview works for custom post types', async () => { + it('works for custom post types', async () => { setHeadstartWPConfig({ ...config, customPostTypes: [ @@ -123,38 +124,95 @@ describe('previewRouteHandler', () => { maxAge: 300, path: '/book/modi-qui-dignissimos-sed-assumenda-sint-iusto', }); + }); - /* const { req: reqWithLocale, res: resWithLocale } = createMocks< - NextApiRequest, - NextApiResponse - >({ - method: 'GET', - query: { - post_id: DRAFT_POST_ID, - token: VALID_AUTH_TOKEN, - post_type: 'book', - locale: 'es', - }, + it('works for custom post types with locale', async () => { + setHeadstartWPConfig({ + ...config, + customPostTypes: [ + { + slug: 'book', + // reuse existing posts endpoint + endpoint: '/wp-json/wp/v2/posts', + // these should match your file-system routing + single: '/book', + archive: '/books', + }, + ], }); - resWithLocale.setPreviewData = jest.fn(); - await previewHandler(reqWithLocale, resWithLocale); + const searchParams = new URLSearchParams({ + post_id: DRAFT_POST_ID.toString(), + token: VALID_AUTH_TOKEN.toString(), + post_type: 'book', + locale: 'es', + }); - expect(resWithLocale.setPreviewData).toHaveBeenCalledWith( - { - authToken: 'this is a valid auth', - id: 57, - postType: 'book', + const previewDataPayload = JSON.stringify({ + id: DRAFT_POST_ID, + postType: 'book', + revision: false, + authToken: VALID_AUTH_TOKEN, + }); + + const req = new NextRequest(`https://js1.10up.com?${searchParams.toString()}`); + + await expect(() => previewRouteHandler(req)).rejects.toThrow('NEXT_REDIRECT'); + + expect(nextHeaders.cookies().set).toHaveBeenCalledWith(COOKIE_NAME, previewDataPayload, { + httpOnly: true, + maxAge: 300, + path: '/es/book/modi-qui-dignissimos-sed-assumenda-sint-iusto', + }); + }); + + it('correctly takes into account `options`', async () => { + const searchParams = new URLSearchParams({ + post_id: DRAFT_POST_ID.toString(), + token: VALID_AUTH_TOKEN.toString(), + post_type: 'post', + }); + + const req = new NextRequest(`https://js1.10up.com?${searchParams.toString()}`); + + const onRedirect = jest.fn(() => { + redirect('/'); + }); + + await expect(() => + previewRouteHandler(req, { + preparePreviewData({ previewData }) { + return { ...previewData, myCustomData: true }; + }, + getRedirectPath({ defaultRedirectPath }) { + return `${defaultRedirectPath}-preview=true`; + }, + onRedirect, + }), + ).rejects.toThrow('NEXT_REDIRECT'); + + expect(nextHeaders.cookies().set).toHaveBeenCalledWith( + COOKIE_NAME, + JSON.stringify({ + id: DRAFT_POST_ID, + postType: 'post', revision: false, - }, + authToken: VALID_AUTH_TOKEN, + myCustomData: true, + }), { + httpOnly: true, maxAge: 300, - path: '/es/book/modi-qui-dignissimos-sed-assumenda-sint-iusto-preview=true', + path: '/modi-qui-dignissimos-sed-assumenda-sint-iusto-preview=true', }, ); - expect(resWithLocale._getStatusCode()).toBe(302); - expect(resWithLocale._getRedirectUrl()).toBe( - '/es/book/modi-qui-dignissimos-sed-assumenda-sint-iusto-preview=true', - ); */ + + expect(onRedirect).toHaveBeenCalledWith({ + redirectPath: '/modi-qui-dignissimos-sed-assumenda-sint-iusto-preview=true', + post: expect.any(Object), + postTypeDef: expect.any(Object), + previewData: expect.any(Object), + req, + }); }); }); From 68db922d0692fb720d251fc85b324aa9aabc2b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Oliveira?= Date: Thu, 11 Jul 2024 15:43:22 -0300 Subject: [PATCH 3/3] test: tests for revalidateRouteHandler --- packages/core/test/server-handlers.ts | 25 +++++++++++ packages/core/test/server.ts | 19 ++++++++- .../__tests__/revalidateRouteHandler.ts | 42 +++++++++++++++++++ .../rsc/handlers/revalidateRouterHandler.ts | 1 + 4 files changed, 85 insertions(+), 2 deletions(-) diff --git a/packages/core/test/server-handlers.ts b/packages/core/test/server-handlers.ts index d44b7d4a1..dabb52e80 100644 --- a/packages/core/test/server-handlers.ts +++ b/packages/core/test/server-handlers.ts @@ -11,6 +11,9 @@ interface TestEndpointResponse { export const VALID_AUTH_TOKEN = 'this is a valid auth'; export const DRAFT_POST_ID = 57; +export const VALID_REVALIDATE_AUTH_TOKEN = 'this is a valid revalidate auth token'; +export const REVALIDATE_PATH = '/revalidate-path'; +export const REVALIDATE_POST_ID = 57; const handlers = [ rest.head('http://example.com/redirect-test', (req, res) => { @@ -348,6 +351,28 @@ const handlers = [ return res(ctx.json([])); }), + + rest.get('https://js1.10up.com/wp-json/headless-wp/v1/token', (req, res, ctx) => { + if ( + (req.headers.has('Authorization') && + req.headers.get('Authorization') === `Bearer ${VALID_REVALIDATE_AUTH_TOKEN}`) || + (req.headers.has('X-HeadstartWP-Authorization') && + req.headers.get('X-HeadstartWP-Authorization') === + `Bearer ${VALID_REVALIDATE_AUTH_TOKEN}`) + ) { + return res(ctx.json({ post_id: REVALIDATE_POST_ID, path: REVALIDATE_PATH })); + } + + return res( + ctx.json({ + code: 'rest_cannot_read', + message: 'Sorry, you are not allowed to do this.', + data: { + status: 401, + }, + }), + ); + }), ]; export { handlers }; diff --git a/packages/core/test/server.ts b/packages/core/test/server.ts index 07a4c8461..2e41a3de8 100644 --- a/packages/core/test/server.ts +++ b/packages/core/test/server.ts @@ -1,6 +1,21 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { handlers, VALID_AUTH_TOKEN, DRAFT_POST_ID } from './server-handlers'; +import { + handlers, + VALID_AUTH_TOKEN, + DRAFT_POST_ID, + VALID_REVALIDATE_AUTH_TOKEN, + REVALIDATE_PATH, + REVALIDATE_POST_ID, +} from './server-handlers'; const server = setupServer(...handlers); -export { server, rest, VALID_AUTH_TOKEN, DRAFT_POST_ID }; +export { + server, + rest, + VALID_AUTH_TOKEN, + DRAFT_POST_ID, + VALID_REVALIDATE_AUTH_TOKEN, + REVALIDATE_PATH, + REVALIDATE_POST_ID, +}; diff --git a/packages/next/src/rsc/handlers/__tests__/revalidateRouteHandler.ts b/packages/next/src/rsc/handlers/__tests__/revalidateRouteHandler.ts index 401a2a936..ba446eddf 100644 --- a/packages/next/src/rsc/handlers/__tests__/revalidateRouteHandler.ts +++ b/packages/next/src/rsc/handlers/__tests__/revalidateRouteHandler.ts @@ -1,7 +1,17 @@ import { setHeadstartWPConfig } from '@headstartwp/core'; import { NextRequest } from 'next/server'; +import { + REVALIDATE_PATH, + REVALIDATE_POST_ID, + VALID_REVALIDATE_AUTH_TOKEN, +} from '@headstartwp/core/test'; +import nextCache from 'next/cache'; import { revalidateRouteHandler } from '../revalidateRouterHandler'; +jest.mock('next/cache', () => ({ + revalidatePath: jest.fn(), +})); + const config = { sourceUrl: 'https://js1.10up.com', useWordPressPlugin: true, @@ -30,4 +40,36 @@ describe('revalidateRouteHandler', () => { expect(res.status).toBe(401); }); + + it('revalidates with auth token', async () => { + const searchParams = new URLSearchParams({ + post_id: REVALIDATE_POST_ID.toString(), + token: VALID_REVALIDATE_AUTH_TOKEN.toString(), + path: REVALIDATE_PATH, + }); + + const req = new NextRequest(`https://js1.10up.com?${searchParams.toString()}`); + + const res = await revalidateRouteHandler(req); + + expect(res.status).toBe(200); + expect(nextCache.revalidatePath).toHaveBeenCalledWith(REVALIDATE_PATH); + }); + + it('does not revalidates without auth token', async () => { + // @ts-expect-error + nextCache.revalidatePath.mockClear(); + const searchParams = new URLSearchParams({ + post_id: REVALIDATE_POST_ID.toString(), + token: 'invalid token', + path: REVALIDATE_PATH, + }); + + const req = new NextRequest(`https://js1.10up.com?${searchParams.toString()}`); + + const res = await revalidateRouteHandler(req); + + expect(res.status).toBe(500); + expect(nextCache.revalidatePath).not.toHaveBeenCalledWith(REVALIDATE_PATH); + }); }); diff --git a/packages/next/src/rsc/handlers/revalidateRouterHandler.ts b/packages/next/src/rsc/handlers/revalidateRouterHandler.ts index 9e1d5ae7c..8714e8a77 100644 --- a/packages/next/src/rsc/handlers/revalidateRouterHandler.ts +++ b/packages/next/src/rsc/handlers/revalidateRouterHandler.ts @@ -2,6 +2,7 @@ import { VerifyTokenFetchStrategy } from '@headstartwp/core'; import { revalidatePath } from 'next/cache'; import { NextRequest } from 'next/server'; import { getHostAndConfigFromRequest } from './utils'; + /** * Returns the path to revalidate *