From e05abafddb0347d55cffa48402075c764e4dd7c5 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Wed, 26 Aug 2026 12:43:15 +0200 Subject: [PATCH] Fix PPR test module mocks leaking into the rest of the suite `mock.module` replaces a module for the whole bun test process, so mocking `@/lib/context` and `jwt-decode` wholesale in the PPR route params test broke every later test file that imported them. Spread the real context module and sign a real JWT instead. Also restore the `x-gitbook-route-site` debug header, commented out by mistake. --- packages/gitbook/src/app/utils.test.ts | 34 +++++++++++++++----------- packages/gitbook/src/middleware.ts | 2 +- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/gitbook/src/app/utils.test.ts b/packages/gitbook/src/app/utils.test.ts index 3a339c3a4..89b988b30 100644 --- a/packages/gitbook/src/app/utils.test.ts +++ b/packages/gitbook/src/app/utils.test.ts @@ -1,19 +1,17 @@ import { describe, expect, it, mock } from 'bun:test'; +import jwt from 'jsonwebtoken'; import rison from 'rison'; +import * as realContext from '@/lib/context'; + mock.module('server-only', () => ({})); -mock.module('@/lib/adaptive', () => ({ - getVisitorAuthClaims: () => ({}), - getVisitorAuthClaimsFromToken: () => ({}), - getPPRVisitorAuthClaimsFromToken: () => ({ scope: 'site-structure' }), -})); +// Only the lookup is stubbed: mocking the whole module would leak into the other test files, +// as `mock.module` replaces it for the entire test process. mock.module('@/lib/context', () => ({ + ...realContext, getBaseContext: (input: unknown) => input, fetchSiteContextByURLLookup: async (_baseContext: unknown, data: unknown) => data, })); -mock.module('jwt-decode', () => ({ - jwtDecode: () => ({}), -})); const { getPPRHeaderRouteParams, @@ -24,12 +22,20 @@ const { } = await import('./utils'); type PPRRouteParams = import('./utils').PPRRouteParams; +const apiToken = jwt.sign( + { + exp: Math.floor(Date.now() / 1000) + 3600, + siteStructureClaims: { scope: 'site-structure' }, + }, + 'secret' +); + const routeParams: PPRRouteParams = { mode: 'url', siteURL: 'docs.example.com', siteData: encodeURIComponent( rison.encode({ - apiToken: 'ppr-api-token', + apiToken, site: 'site-id', siteSection: 'page-site-section-id', siteSpace: 'page-site-space-id', @@ -65,7 +71,7 @@ describe('getPPRRouteParams', () => { expect(params).not.toHaveProperty('revalidationId'); expect(params).not.toHaveProperty('pprDefaults'); expect(getSiteURLDataFromParams(params)).toMatchObject({ - apiToken: 'ppr-api-token', + apiToken, site: 'site-id', space: 'space-id', revision: 'ppr-revision-id', @@ -79,7 +85,7 @@ describe('PPR cache region params', () => { ...routeParams, siteData: encodeURIComponent( rison.encode({ - apiToken: 'new-ppr-api-token', + apiToken: jwt.sign({ siteStructureClaims: {} }, 'other-secret'), site: 'site-id', siteSection: 'new-page-site-section-id', siteSpace: 'new-page-site-space-id', @@ -99,7 +105,7 @@ describe('PPR cache region params', () => { ); expect(headerData).toMatchObject({ - apiToken: 'ppr-api-token', + apiToken, siteSection: 'default-site-section-id', siteSpace: 'default-site-space-id', space: 'default-space-id', @@ -140,7 +146,7 @@ describe('PPR cache region params', () => { ); expect(tocData).toMatchObject({ - apiToken: 'ppr-api-token', + apiToken, siteSection: 'page-site-section-id', siteSpace: 'page-site-space-id', space: 'space-id', @@ -172,7 +178,7 @@ describe('getPPRStaticSiteContext', () => { ); expect(context).toMatchObject({ - apiToken: 'ppr-api-token', + apiToken, revision: 'ppr-revision-id', }); expect(visitorAuthClaims).toEqual({ scope: 'site-structure' }); diff --git a/packages/gitbook/src/middleware.ts b/packages/gitbook/src/middleware.ts index 2d2b42c23..4b61a33a6 100644 --- a/packages/gitbook/src/middleware.ts +++ b/packages/gitbook/src/middleware.ts @@ -584,7 +584,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) { response.headers.set('x-content-type-options', 'nosniff'); // Debug header response.headers.set('x-gitbook-route-type', routeType); - // response.headers.set('x-gitbook-route-site', siteURLWithoutProtocol); + response.headers.set('x-gitbook-route-site', siteURLWithoutProtocol); // noindex search/assistant deep links, kept crawlable so Google sees the directive. if (rewrittenURL.searchParams.has('ask') || rewrittenURL.searchParams.has('q')) {