From 3f4eef14e2de0e6692e273c27f2e900fa506926b Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Wed, 18 Oct 2023 15:08:39 -0400 Subject: [PATCH 01/23] simplified header rules --- .../src/utils/adapter/__tests__/manager.ts | 80 +++++++++++++- .../gatsby/src/utils/adapter/constants.ts | 6 +- packages/gatsby/src/utils/adapter/manager.ts | 102 ++++++++++++++---- packages/gatsby/src/utils/adapter/types.ts | 6 ++ 4 files changed, 168 insertions(+), 26 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/__tests__/manager.ts b/packages/gatsby/src/utils/adapter/__tests__/manager.ts index 8fc17c0e90b60..b04007f2d931a 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/manager.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/manager.ts @@ -50,7 +50,7 @@ describe(`getRoutesManifest`, () => { process.chdir(fixturesDir) setWebpackAssets(new Set([`app-123.js`])) - const routesManifest = getRoutesManifest() + const { routes: routesManifest } = getRoutesManifest() expect(routesManifest).toMatchSnapshot() }) @@ -62,7 +62,7 @@ describe(`getRoutesManifest`, () => { process.chdir(fixturesDir) setWebpackAssets(new Set([`app-123.js`])) - const routesManifest = getRoutesManifest() + const { routes: routesManifest } = getRoutesManifest() expect(routesManifest).toEqual( expect.arrayContaining([ @@ -81,7 +81,7 @@ describe(`getRoutesManifest`, () => { process.chdir(fixturesDir) setWebpackAssets(new Set([`app-123.js`])) - const routesManifest = getRoutesManifest() + const { routes: routesManifest } = getRoutesManifest() expect(routesManifest).toEqual( expect.arrayContaining([ @@ -92,6 +92,80 @@ describe(`getRoutesManifest`, () => { ]) ) }) + + it(`should return header rules`, () => { + mockStoreState(stateDefault, { + config: { + ...stateDefault.config, + trailingSlash: `always`, + headers: [ + { + source: `/ssr/*`, + headers: [ + { + key: "x-ssr-header", + value: "my custom header value from config", + }, + ], + }, + ], + }, + }) + process.chdir(fixturesDir) + setWebpackAssets(new Set([`app-123.js`, `static/app-456.js`])) + + const { headers } = getRoutesManifest() + + expect(headers).toContainEqual({ + headers: [ + { key: "x-xss-protection", value: "1; mode=block" }, + { key: "x-content-type-options", value: "nosniff" }, + { key: "referrer-policy", value: "same-origin" }, + { key: "x-frame-options", value: "DENY" }, + ], + path: "/*", + }) + expect(headers).toContainEqual({ + headers: [ + { + key: "cache-control", + value: "public, max-age=31536000, immutable", + }, + ], + path: "/static/*", + }) + expect(headers).toContainEqual({ + headers: [ + { + key: "cache-control", + value: "public, max-age=0, must-revalidate", + }, + ], + path: "/page-data/index/page-data.json", + }) + expect(headers).toContainEqual({ + headers: [ + { + key: "cache-control", + value: "public, max-age=31536000, immutable", + }, + ], + path: "/app-123.js", + }) + expect(headers).not.toContainEqual({ + headers: [ + { key: "x-xss-protection", value: "1; mode=block" }, + { key: "x-content-type-options", value: "nosniff" }, + { key: "referrer-policy", value: "same-origin" }, + { key: "x-frame-options", value: "DENY" }, + ], + path: "/ssr/*", + }) + + expect(headers).not.toContain( + expect.objectContaining({ path: "/static/app-456.js" }) + ) + }) }) describe(`getFunctionsManifest`, () => { diff --git a/packages/gatsby/src/utils/adapter/constants.ts b/packages/gatsby/src/utils/adapter/constants.ts index ff396fcebb415..88098a419fd41 100644 --- a/packages/gatsby/src/utils/adapter/constants.ts +++ b/packages/gatsby/src/utils/adapter/constants.ts @@ -27,10 +27,14 @@ export const MUST_REVALIDATE_HEADERS: IHeader["headers"] = [ ...BASE_HEADERS, ] -export const PERMAMENT_CACHING_HEADERS: IHeader["headers"] = [ +export const PERMANENT_CACHE_CONTROL_HEADER: IHeader["headers"] = [ { key: `cache-control`, value: `public, max-age=31536000, immutable`, }, +] + +export const PERMAMENT_CACHING_HEADERS: IHeader["headers"] = [ + ...PERMANENT_CACHE_CONTROL_HEADER, ...BASE_HEADERS, ] diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 5dd77a631c564..dda4ee88c237a 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -17,6 +17,7 @@ import type { IAdapter, IAdapterFinalConfig, IAdapterConfig, + HeaderRoutes, } from "./types" import { store, readState } from "../../redux" import { getPageMode } from "../page-mode" @@ -31,6 +32,7 @@ import { BASE_HEADERS, MUST_REVALIDATE_HEADERS, PERMAMENT_CACHING_HEADERS, + PERMANENT_CACHE_CONTROL_HEADER, } from "./constants" import { createHeadersMatcher } from "./create-headers" import { HTTP_STATUS_CODE } from "../../redux/types" @@ -201,10 +203,13 @@ export async function initAdapterManager(): Promise { let _routesManifest: RoutesManifest | undefined = undefined let _functionsManifest: FunctionsManifest | undefined = undefined + let _headerRoutes: HeaderRoutes | undefined = undefined const adaptContext: IAdaptContext = { get routesManifest(): RoutesManifest { if (!_routesManifest) { - _routesManifest = getRoutesManifest() + const { routes, headers } = getRoutesManifest() + _routesManifest = routes + _headerRoutes = headers } return _routesManifest @@ -216,6 +221,15 @@ export async function initAdapterManager(): Promise { return _functionsManifest }, + get headerRoutes(): HeaderRoutes { + if (!_headerRoutes) { + const { routes, headers } = getRoutesManifest() + _routesManifest = routes + _headerRoutes = headers + } + + return _headerRoutes + }, reporter, // Our internal Gatsby config allows this to be undefined but for the adapter we should always pass through the default values and correctly show this in the TypeScript types trailingSlash: trailingSlash as TrailingSlash, @@ -261,11 +275,45 @@ export function setWebpackAssets(assets: Set): void { type RouteWithScore = { score: number } & Route -function getRoutesManifest(): RoutesManifest { +const headersAreEqual = (a, b) => a.key === b.key && a.value === b.value + +const defaultHeaderRoutes: HeaderRoutes = [ + { + path: "/*", + headers: BASE_HEADERS, + }, + { + path: "/static/*", + headers: PERMANENT_CACHE_CONTROL_HEADER, + }, +] + +const customHeaderFilter = (route: Route) => (h: IHeader["headers"][0]) => { + for (const baseHeader of BASE_HEADERS) { + if (headersAreEqual(baseHeader, h)) { + return false + } + } + if (route.path.startsWith(`/static/`)) { + for (const cachingHeader of PERMAMENT_CACHING_HEADERS) { + if (headersAreEqual(cachingHeader, h)) { + return false + } + } + } + return true +} + +function getRoutesManifest(): { + routes: RoutesManifest + headers: HeaderRoutes +} { const routes: Array = [] const state = store.getState() const createHeaders = createHeadersMatcher(state.config.headers) + const headerRoutes: HeaderRoutes = [...defaultHeaderRoutes] + const fileAssets = new Set( globSync(`**/**`, { cwd: posix.join(process.cwd(), `public`), @@ -290,11 +338,19 @@ function getRoutesManifest(): RoutesManifest { if (route.type !== `function`) { route.headers = createHeaders(route.path, route.headers) + console.log(`route.headers`, route.headers) + const customHeaders = route.headers.filter(customHeaderFilter(route)) + if (customHeaders.length > 0) { + headerRoutes.push({ path: route.path, headers: customHeaders }) + } } - ;(route as RouteWithScore).score = rankRoute(route.path) + const routeWithScore: RouteWithScore = { + ...route, + score: rankRoute(route.path), + } - routes.push(route as RouteWithScore) + routes.push(routeWithScore) } function addStaticRoute({ @@ -506,25 +562,27 @@ function getRoutesManifest(): RoutesManifest { }) } - return ( - routes - .sort((a, b) => { - // The higher the score, the higher the specificity of our path - const order = b.score - a.score - if (order !== 0) { - return order - } + const sortedRoutes = routes + .sort((a, b) => { + // The higher the score, the higher the specificity of our path + const order = b.score - a.score + if (order !== 0) { + return order + } - // if specificity is the same we do lexigraphic comparison of path to ensure - // deterministic order regardless of order pages where created - return a.path.localeCompare(b.path) - }) - // The score should be internal only, so we remove it from the final manifest - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .map(({ score, ...rest }): Route => { - return { ...rest } - }) - ) + // if specificity is the same we do lexigraphic comparison of path to ensure + // deterministic order regardless of order pages where created + return a.path.localeCompare(b.path) + }) + // The score should be internal only, so we remove it from the final manifest + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .map(({ score, ...rest }): Route => { + return { ...rest } + }) + return { + routes: sortedRoutes, + headers: headerRoutes, + } } function getFunctionsManifest(): FunctionsManifest { diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index b9915427228eb..e7e9352d8c09a 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -67,6 +67,11 @@ export type Route = IStaticRoute | IFunctionRoute | IRedirectRoute export type RoutesManifest = Array +export interface HeaderRoute extends IBaseRoute { + headers: IHeader["headers"] +} + +export type HeaderRoutes = HeaderRoute[] export interface IFunctionDefinition { /** * Unique identifier of this function. Corresponds to the `functionId` inside the `routesManifest`. @@ -99,6 +104,7 @@ interface IDefaultContext { export interface IAdaptContext extends IDefaultContext { routesManifest: RoutesManifest functionsManifest: FunctionsManifest + headerRoutes: HeaderRoutes /** * @see https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/#pathprefix */ From 6bf878262fa4136419bc40b23073b7d8f4a89c68 Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Wed, 18 Oct 2023 17:05:31 -0400 Subject: [PATCH 02/23] lint --- packages/gatsby/src/utils/adapter/manager.ts | 31 +++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index dda4ee88c237a..2c67827789b2d 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -275,34 +275,37 @@ export function setWebpackAssets(assets: Set): void { type RouteWithScore = { score: number } & Route -const headersAreEqual = (a, b) => a.key === b.key && a.value === b.value +const headersAreEqual = (a, b): boolean => + a.key === b.key && a.value === b.value const defaultHeaderRoutes: HeaderRoutes = [ { - path: "/*", + path: `/*`, headers: BASE_HEADERS, }, { - path: "/static/*", + path: `/static/*`, headers: PERMANENT_CACHE_CONTROL_HEADER, }, ] -const customHeaderFilter = (route: Route) => (h: IHeader["headers"][0]) => { - for (const baseHeader of BASE_HEADERS) { - if (headersAreEqual(baseHeader, h)) { - return false - } - } - if (route.path.startsWith(`/static/`)) { - for (const cachingHeader of PERMAMENT_CACHING_HEADERS) { - if (headersAreEqual(cachingHeader, h)) { +const customHeaderFilter = + (route: Route) => + (h: IHeader["headers"][0]): boolean => { + for (const baseHeader of BASE_HEADERS) { + if (headersAreEqual(baseHeader, h)) { return false } } + if (route.path.startsWith(`/static/`)) { + for (const cachingHeader of PERMAMENT_CACHING_HEADERS) { + if (headersAreEqual(cachingHeader, h)) { + return false + } + } + } + return true } - return true -} function getRoutesManifest(): { routes: RoutesManifest From 20cc896b25ee0ce728cdacd369df43e5ce85621f Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Wed, 18 Oct 2023 17:37:45 -0400 Subject: [PATCH 03/23] lint --- .../src/utils/adapter/__tests__/manager.ts | 46 +++++++++---------- packages/gatsby/src/utils/adapter/types.ts | 4 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/__tests__/manager.ts b/packages/gatsby/src/utils/adapter/__tests__/manager.ts index 40ee0ccfa80ed..55b5f28b2d4ac 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/manager.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/manager.ts @@ -92,7 +92,7 @@ describe(`getRoutesManifest`, () => { ]) ) }) - + it(`should not prepend '\\' to external redirects`, () => { mockStoreState(stateDefault) process.chdir(fixturesDir) @@ -117,8 +117,8 @@ describe(`getRoutesManifest`, () => { source: `/ssr/*`, headers: [ { - key: "x-ssr-header", - value: "my custom header value from config", + key: `x-ssr-header`, + value: `my custom header value from config`, }, ], }, @@ -132,52 +132,52 @@ describe(`getRoutesManifest`, () => { expect(headers).toContainEqual({ headers: [ - { key: "x-xss-protection", value: "1; mode=block" }, - { key: "x-content-type-options", value: "nosniff" }, - { key: "referrer-policy", value: "same-origin" }, - { key: "x-frame-options", value: "DENY" }, + { key: `x-xss-protection`, value: `1; mode=block` }, + { key: `x-content-type-options`, value: `nosniff` }, + { key: `referrer-policy`, value: `same-origin` }, + { key: `x-frame-options`, value: `DENY` }, ], - path: "/*", + path: `/*`, }) expect(headers).toContainEqual({ headers: [ { - key: "cache-control", - value: "public, max-age=31536000, immutable", + key: `cache-control`, + value: `public, max-age=31536000, immutable`, }, ], - path: "/static/*", + path: `/static/*`, }) expect(headers).toContainEqual({ headers: [ { - key: "cache-control", - value: "public, max-age=0, must-revalidate", + key: `cache-control`, + value: `public, max-age=0, must-revalidate`, }, ], - path: "/page-data/index/page-data.json", + path: `/page-data/index/page-data.json`, }) expect(headers).toContainEqual({ headers: [ { - key: "cache-control", - value: "public, max-age=31536000, immutable", + key: `cache-control`, + value: `public, max-age=31536000, immutable`, }, ], - path: "/app-123.js", + path: `/app-123.js`, }) expect(headers).not.toContainEqual({ headers: [ - { key: "x-xss-protection", value: "1; mode=block" }, - { key: "x-content-type-options", value: "nosniff" }, - { key: "referrer-policy", value: "same-origin" }, - { key: "x-frame-options", value: "DENY" }, + { key: `x-xss-protection`, value: `1; mode=block` }, + { key: `x-content-type-options`, value: `nosniff` }, + { key: `referrer-policy`, value: `same-origin` }, + { key: `x-frame-options`, value: `DENY` }, ], - path: "/ssr/*", + path: `/ssr/*`, }) expect(headers).not.toContain( - expect.objectContaining({ path: "/static/app-456.js" }) + expect.objectContaining({ path: `/static/app-456.js` }) ) }) }) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index e7e9352d8c09a..8e004b93d16b1 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -67,11 +67,11 @@ export type Route = IStaticRoute | IFunctionRoute | IRedirectRoute export type RoutesManifest = Array -export interface HeaderRoute extends IBaseRoute { +export interface IHeaderRoute extends IBaseRoute { headers: IHeader["headers"] } -export type HeaderRoutes = HeaderRoute[] +export type HeaderRoutes = Array export interface IFunctionDefinition { /** * Unique identifier of this function. Corresponds to the `functionId` inside the `routesManifest`. From b58b9f751b99b40f478b419636e1f27b023e5dcb Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Wed, 18 Oct 2023 21:41:08 -0400 Subject: [PATCH 04/23] update test/snapshot --- .../utils/adapter/__tests__/__snapshots__/manager.ts.snap | 6 +++--- packages/gatsby/src/utils/adapter/__tests__/manager.ts | 5 ++--- packages/gatsby/src/utils/adapter/manager.ts | 1 - 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap b/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap index 2243c0842050e..eb9e0a4757c6d 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap +++ b/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap @@ -146,7 +146,7 @@ Array [ }, Object { "functionId": "static-index-js", - "path": "/api/static/", + "path": "/api/static", "type": "function", }, Object { @@ -233,7 +233,7 @@ Array [ Object { "cache": true, "functionId": "ssr-engine", - "path": "/dsg/", + "path": "/dsg", "type": "function", }, Object { @@ -263,7 +263,7 @@ Array [ }, Object { "functionId": "ssr-engine", - "path": "/ssr/", + "path": "/ssr", "type": "function", }, Object { diff --git a/packages/gatsby/src/utils/adapter/__tests__/manager.ts b/packages/gatsby/src/utils/adapter/__tests__/manager.ts index 55b5f28b2d4ac..93c4a84bb826e 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/manager.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/manager.ts @@ -98,8 +98,8 @@ describe(`getRoutesManifest`, () => { process.chdir(fixturesDir) setWebpackAssets(new Set([`app-123.js`])) - const routesManifest = getRoutesManifest() - expect(routesManifest).toEqual( + const { routes } = getRoutesManifest() + expect(routes).toEqual( expect.arrayContaining([ expect.objectContaining({ path: `https://old-url` }), expect.objectContaining({ path: `http://old-url` }), @@ -111,7 +111,6 @@ describe(`getRoutesManifest`, () => { mockStoreState(stateDefault, { config: { ...stateDefault.config, - trailingSlash: `always`, headers: [ { source: `/ssr/*`, diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index b9a1792ba51e0..459f0d9a4da7e 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -344,7 +344,6 @@ function getRoutesManifest(): { if (route.type !== `function`) { route.headers = createHeaders(route.path, route.headers) - console.log(`route.headers`, route.headers) const customHeaders = route.headers.filter(customHeaderFilter(route)) if (customHeaders.length > 0) { headerRoutes.push({ path: route.path, headers: customHeaders }) From 45b6f5e4a54f311dfab51de0224d417c4e510ce1 Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Thu, 19 Oct 2023 10:28:16 -0400 Subject: [PATCH 05/23] update snapshot --- .../utils/adapter/__tests__/__snapshots__/manager.ts.snap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap b/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap index eb9e0a4757c6d..2243c0842050e 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap +++ b/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap @@ -146,7 +146,7 @@ Array [ }, Object { "functionId": "static-index-js", - "path": "/api/static", + "path": "/api/static/", "type": "function", }, Object { @@ -233,7 +233,7 @@ Array [ Object { "cache": true, "functionId": "ssr-engine", - "path": "/dsg", + "path": "/dsg/", "type": "function", }, Object { @@ -263,7 +263,7 @@ Array [ }, Object { "functionId": "ssr-engine", - "path": "/ssr", + "path": "/ssr/", "type": "function", }, Object { From 5254cb866343e794c2734ae0ec8c991049a82ef8 Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Fri, 20 Oct 2023 10:24:50 -0400 Subject: [PATCH 06/23] add snapshot for headerRoutes --- .../__tests__/__snapshots__/manager.ts.snap | 107 ++++++++++++++++++ .../src/utils/adapter/__tests__/manager.ts | 4 +- 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap b/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap index 2243c0842050e..b622b18d0df61 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap +++ b/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap @@ -322,3 +322,110 @@ Array [ }, ] `; + +exports[`getRoutesManifest should return routes manifest 2`] = ` +Array [ + Object { + "headers": Array [ + Object { + "key": "x-xss-protection", + "value": "1; mode=block", + }, + Object { + "key": "x-content-type-options", + "value": "nosniff", + }, + Object { + "key": "referrer-policy", + "value": "same-origin", + }, + Object { + "key": "x-frame-options", + "value": "DENY", + }, + ], + "path": "/*", + }, + Object { + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=31536000, immutable", + }, + ], + "path": "/static/*", + }, + Object { + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + ], + "path": "/", + }, + Object { + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + ], + "path": "/page-data/index/page-data.json", + }, + Object { + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + ], + "path": "/page-data/sq/d/1.json", + }, + Object { + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + ], + "path": "/page-data/app-data.json", + }, + Object { + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=31536000, immutable", + }, + ], + "path": "/app-123.js", + }, + Object { + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + ], + "path": "/chunk-map.json", + }, + Object { + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + ], + "path": "/webpack.stats.json", + }, + Object { + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + ], + "path": "/_gatsby/slices/_gatsby-scripts-1.html", + }, +] +`; diff --git a/packages/gatsby/src/utils/adapter/__tests__/manager.ts b/packages/gatsby/src/utils/adapter/__tests__/manager.ts index 93c4a84bb826e..601b18f88bd35 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/manager.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/manager.ts @@ -50,9 +50,11 @@ describe(`getRoutesManifest`, () => { process.chdir(fixturesDir) setWebpackAssets(new Set([`app-123.js`])) - const { routes: routesManifest } = getRoutesManifest() + const { routes: routesManifest, headers: headerRoutes } = + getRoutesManifest() expect(routesManifest).toMatchSnapshot() + expect(headerRoutes).toMatchSnapshot() }) it(`should respect "never" trailingSlash config option`, () => { From c1d54ae03c6961075dbfde8c422c92e661b8314d Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Fri, 20 Oct 2023 11:06:15 -0400 Subject: [PATCH 07/23] adapter use headerRoutes --- e2e-tests/adapters/debug-adapter.ts | 34 ++++++++++++------- packages/gatsby-adapter-netlify/src/index.ts | 9 +++-- .../src/route-handler.ts | 30 +++++++++++----- packages/gatsby/index.d.ts | 1 + 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/e2e-tests/adapters/debug-adapter.ts b/e2e-tests/adapters/debug-adapter.ts index 9333d1bff0733..944a9f08d6d25 100644 --- a/e2e-tests/adapters/debug-adapter.ts +++ b/e2e-tests/adapters/debug-adapter.ts @@ -1,7 +1,7 @@ import { inspect } from "util" import type { AdapterInit } from "gatsby" -const createTestingAdapter: AdapterInit = (adapterOptions) => { +const createTestingAdapter: AdapterInit = adapterOptions => { return { name: `gatsby-adapter-debug`, cache: { @@ -10,10 +10,11 @@ const createTestingAdapter: AdapterInit = (adapterOptions) => { }, store({ directories, reporter }) { reporter.info(`[gatsby-adapter-debug] cache.store() ${directories}`) - } + }, }, adapt({ routesManifest, + headerRoutes, functionsManifest, pathPrefix, trailingSlash, @@ -21,17 +22,24 @@ const createTestingAdapter: AdapterInit = (adapterOptions) => { }) { reporter.info(`[gatsby-adapter-debug] adapt()`) - console.log(`[gatsby-adapter-debug] adapt()`, inspect({ - routesManifest, - functionsManifest, - pathPrefix, - trailingSlash, - }, { - depth: Infinity, - colors: true - })) - } + console.log( + `[gatsby-adapter-debug] adapt()`, + inspect( + { + routesManifest, + headerRoutes, + functionsManifest, + pathPrefix, + trailingSlash, + }, + { + depth: Infinity, + colors: true, + } + ) + ) + }, } } -export default createTestingAdapter \ No newline at end of file +export default createTestingAdapter diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 65494e7df3dbf..2276471ef63db 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -53,9 +53,14 @@ const createNetlifyAdapter: AdapterInit = options => { } }, }, - async adapt({ routesManifest, functionsManifest }): Promise { + async adapt({ + routesManifest, + functionsManifest, + headerRoutes, + }): Promise { const { lambdasThatUseCaching } = await handleRoutesManifest( - routesManifest + routesManifest, + headerRoutes ) // functions handling diff --git a/packages/gatsby-adapter-netlify/src/route-handler.ts b/packages/gatsby-adapter-netlify/src/route-handler.ts index d9e5a6b6d84a2..06f9ebcaa5b7d 100644 --- a/packages/gatsby-adapter-netlify/src/route-handler.ts +++ b/packages/gatsby-adapter-netlify/src/route-handler.ts @@ -1,4 +1,4 @@ -import type { RoutesManifest } from "gatsby" +import type { RoutesManifest, HeaderRoutes } from "gatsby" import { tmpdir } from "os" import { Transform } from "stream" import { join, basename } from "path" @@ -130,8 +130,16 @@ export async function injectEntries( await fs.move(tmpFile, fileName) } +function buildHeaderString(path, headers): string { + return `${encodeURI(path)}\n${headers.reduce((acc, curr) => { + acc += ` ${curr.key}: ${curr.value}\n` + return acc + }, ``)}` +} + export async function handleRoutesManifest( - routesManifest: RoutesManifest + routesManifest: RoutesManifest, + headerRoutes: HeaderRoutes ): Promise<{ lambdasThatUseCaching: Map }> { @@ -213,16 +221,20 @@ export async function handleRoutesManifest( )} 200\n` } - _headers += `${encodeURI(fromPath)}\n${route.headers.reduce( - (acc, curr) => { - acc += ` ${curr.key}: ${curr.value}\n` - return acc - }, - `` - )}` + if (!headerRoutes) { + // don't generate _headers from routesManifest if headerRoutes are provided + _headers += buildHeaderString(route.path, route.headers) + } } } + if (headerRoutes) { + _headers = headerRoutes.reduce((acc, curr) => { + acc += buildHeaderString(curr.path, curr.headers) + return acc + }, ``) + } + await injectEntries(`public/_redirects`, _redirects) await injectEntries(`public/_headers`, _headers) diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index 00bb8ed93ef5f..3adbc7584892e 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -42,6 +42,7 @@ export { IRedirectRoute, IFunctionDefinition, RoutesManifest, + HeaderRoutes, FunctionsManifest, IAdapterConfig, } from "./dist/utils/adapter/types" From feee86932c45203710c3592b50a08cb93aaacb46 Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Fri, 20 Oct 2023 11:11:41 -0400 Subject: [PATCH 08/23] export type --- packages/gatsby/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index 00bb8ed93ef5f..3adbc7584892e 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -42,6 +42,7 @@ export { IRedirectRoute, IFunctionDefinition, RoutesManifest, + HeaderRoutes, FunctionsManifest, IAdapterConfig, } from "./dist/utils/adapter/types" From 333e9609dee70749c7d0c6e5d18a872b5ccffe0f Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Tue, 24 Oct 2023 11:41:36 -0400 Subject: [PATCH 09/23] first pass at headers tests --- e2e-tests/adapters/cypress/e2e/headers.cy.ts | 94 ++++++++++++++++++++ e2e-tests/adapters/gatsby-config.ts | 36 +++++++- 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 e2e-tests/adapters/cypress/e2e/headers.cy.ts diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts new file mode 100644 index 0000000000000..e711ada306400 --- /dev/null +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -0,0 +1,94 @@ +import { title } from "../../constants" + +describe("Headers", () => { + beforeEach(() => { + beforeEach(() => { + cy.reload(true) + }) + cy.visit("/").waitForRouteChange() + }) + + it("should contain correct headers for index page", () => { + cy.intercept("/").as("index") + + cy.visit("/") + + cy.wait("@index") + .its("response.headers.x-custom-header") + .should("eq", "my custom header value") + cy.wait("@index") + .its("response.headers.x-xss-protection") + .should("eq", "1; mode=block") + cy.wait("@index") + .its("response.headers.x-content-type-options") + .should("eq", "nosniff") + cy.wait("@index") + .its("response.headers.referrer-policy") + .should("eq", "same-origin") + cy.wait("@index") + .its("response.headers.x-frame-options") + .should("eq", "DENY") + }) + + it("should contain correct headers for static assest", () => { + cy.intercept("/static/astro-**.png").as("img-import") + + cy.visit("/") + + cy.wait("@img-import") + .its("response.headers.cache-control") + .should("eq", "public, max-age=31536000, immutable") + cy.wait("@img-import") + .its("response.headers.x-xss-protection") + .should("eq", "1; mode=block") + cy.wait("@img-import") + .its("response.headers.x-content-type-options") + .should("eq", "nosniff") + cy.wait("@img-import") + .its("response.headers.referrer-policy") + .should("eq", "same-origin") + cy.wait("@img-import") + .its("response.headers.x-frame-options") + .should("eq", "DENY") + }) + + it("should contain correct headers for ssr page", () => { + cy.intercept("/ssr/static").as("ssr") + + cy.visit("/ssr/static") + + cy.wait("@ssr") + .its("response.headers.x-ssr-header") + .should("eq", "my custom header value from config") + cy.wait("@ssr") + .its("response.headers.x-xss-protection") + .should("eq", "1; mode=block") + cy.wait("@ssr") + .its("response.headers.x-content-type-options") + .should("eq", "nosniff") + cy.wait("@ssr") + .its("response.headers.referrer-policy") + .should("eq", "same-origin") + cy.wait("@ssr").its("response.headers.x-frame-options").should("eq", "DENY") + }) + + it("should contain correct headers for dsg page", () => { + cy.intercept("/dsg/static").as("dsg") + + cy.visit("/dsg/static") + + cy.wait("@dsg") + .its("response.headers.x-dsg-header") + .should("eq", "my custom header value") + cy.wait("@dsg") + .its("response.headers.x-xss-protection") + .should("eq", "1; mode=block") + cy.wait("@dsg") + .its("response.headers.x-content-type-options") + .should("eq", "nosniff") + cy.wait("@dsg") + .its("response.headers.referrer-policy") + .should("eq", "same-origin") + cy.wait("@dsg").its("response.headers.x-frame-options").should("eq", "DENY") + }) +}) diff --git a/e2e-tests/adapters/gatsby-config.ts b/e2e-tests/adapters/gatsby-config.ts index 8ccc03130cc54..231f741c1c0c2 100644 --- a/e2e-tests/adapters/gatsby-config.ts +++ b/e2e-tests/adapters/gatsby-config.ts @@ -3,7 +3,8 @@ import debugAdapter from "./debug-adapter" import { siteDescription, title } from "./constants" const shouldUseDebugAdapter = process.env.USE_DEBUG_ADAPTER ?? false -const trailingSlash = (process.env.TRAILING_SLASH || `never`) as GatsbyConfig["trailingSlash"] +const trailingSlash = (process.env.TRAILING_SLASH || + `never`) as GatsbyConfig["trailingSlash"] let configOverrides: GatsbyConfig = {} @@ -21,6 +22,39 @@ const config: GatsbyConfig = { }, trailingSlash, plugins: [], + headers: [ + { + source: `/*`, + headers: [ + { + key: "x-custom-header", + value: "my custom header value", + }, + ], + }, + { + source: `/ssr/*`, + headers: [ + { + key: "x-ssr-header", + value: "my custom header value from config", + }, + { + key: "x-ssr-header-overwrite", + value: "my custom header value from config", + }, + ], + }, + { + source: `/dsg/*`, + headers: [ + { + key: "x-dsg-header", + value: "my custom header value", + }, + ], + }, + ], ...configOverrides, } From d08a337da54afd20e6e7bb1dec076865850de10a Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Tue, 24 Oct 2023 12:07:55 -0400 Subject: [PATCH 10/23] merge conflict fix --- .../src/route-handler.ts | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/route-handler.ts b/packages/gatsby-adapter-netlify/src/route-handler.ts index c7841fdae2c60..2faf3216f59f6 100644 --- a/packages/gatsby-adapter-netlify/src/route-handler.ts +++ b/packages/gatsby-adapter-netlify/src/route-handler.ts @@ -130,7 +130,6 @@ export async function injectEntries( await fs.move(tmpFile, fileName) } - function buildHeaderString(path, headers): string { return `${encodeURI(path)}\n${headers.reduce((acc, curr) => { acc += ` ${curr.key}: ${curr.value}\n` @@ -138,7 +137,10 @@ function buildHeaderString(path, headers): string { }, ``)}` } -export function processRoutesManifest(routesManifest: RoutesManifest): { +export function processRoutesManifest( + routesManifest: RoutesManifest, + headerRoutes: HeaderRoutes +): { redirects: string headers: string lambdasThatUseCaching: Map @@ -225,29 +227,28 @@ export function processRoutesManifest(routesManifest: RoutesManifest): { _headers += buildHeaderString(route.path, route.headers) } } - } - return { redirects: _redirects, headers: _headers, lambdasThatUseCaching } -} - - if (headerRoutes) { - _headers = headerRoutes.reduce((acc, curr) => { - acc += buildHeaderString(curr.path, curr.headers) - return acc - }, ``) + if (headerRoutes) { + _headers = headerRoutes.reduce((acc, curr) => { + acc += buildHeaderString(curr.path, curr.headers) + return acc + }, ``) + } } - - await injectEntries(`public/_redirects`, _redirects) - await injectEntries(`public/_headers`, _headers) + return { redirects: _redirects, headers: _headers, lambdasThatUseCaching } } export async function handleRoutesManifest( - routesManifest: RoutesManifest + routesManifest: RoutesManifest, + headerRoutes: HeaderRoutes ): Promise<{ lambdasThatUseCaching: Map }> { - const { redirects, headers, lambdasThatUseCaching } = - processRoutesManifest(routesManifest) + const { redirects, headers, lambdasThatUseCaching } = processRoutesManifest( + routesManifest, + headerRoutes + ) + await injectEntries(`public/_redirects`, redirects) await injectEntries(`public/_headers`, headers) From 0beb1ab19f692784d1182c13c5d5ec935d239909 Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Tue, 24 Oct 2023 12:13:32 -0400 Subject: [PATCH 11/23] lint error --- packages/gatsby/src/utils/adapter/__tests__/manager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/__tests__/manager.ts b/packages/gatsby/src/utils/adapter/__tests__/manager.ts index e3ea6d4f58980..2ea7545ab39f6 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/manager.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/manager.ts @@ -182,7 +182,6 @@ describe(`getRoutesManifest`, () => { ) }) - it(`should respect "force" redirects parameter`, () => { mockStoreState(stateDefault, { config: { ...stateDefault.config }, From d3555e68a3f6f458f70fc28a11ee8991dcadae9c Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Tue, 24 Oct 2023 12:40:14 -0400 Subject: [PATCH 12/23] remove accidental nesting --- e2e-tests/adapters/cypress/e2e/headers.cy.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index e711ada306400..b6d333961d24e 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -2,9 +2,7 @@ import { title } from "../../constants" describe("Headers", () => { beforeEach(() => { - beforeEach(() => { - cy.reload(true) - }) + cy.reload(true) cy.visit("/").waitForRouteChange() }) From 763cd13c5a43f10d900e32fe5900ead1f26a86e9 Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Thu, 26 Oct 2023 11:47:21 -0400 Subject: [PATCH 13/23] tests --- e2e-tests/adapters/cypress/e2e/headers.cy.ts | 110 +++++++------------ 1 file changed, 40 insertions(+), 70 deletions(-) diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index b6d333961d24e..910964017ebaf 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -1,92 +1,62 @@ import { title } from "../../constants" describe("Headers", () => { - beforeEach(() => { - cy.reload(true) - cy.visit("/").waitForRouteChange() - }) - - it("should contain correct headers for index page", () => { + function checkHeaders(routeAlias, expectedHeaders) { + cy.wait(routeAlias).then(interception => { + Object.keys(expectedHeaders).forEach(headerKey => { + expect(interception.response.headers[headerKey]).to.eq( + expectedHeaders[headerKey] + ) + }) + }) + } + + const defaultHeaders = { + "x-xss-protection": "1; mode=block", + "x-content-type-options": "nosniff", + "referrer-policy": "same-origin", + "x-frame-options": "DENY", + } + + it("should contain correct headers for index page and static assets", () => { cy.intercept("/").as("index") - cy.visit("/") - cy.wait("@index") - .its("response.headers.x-custom-header") - .should("eq", "my custom header value") - cy.wait("@index") - .its("response.headers.x-xss-protection") - .should("eq", "1; mode=block") - cy.wait("@index") - .its("response.headers.x-content-type-options") - .should("eq", "nosniff") - cy.wait("@index") - .its("response.headers.referrer-policy") - .should("eq", "same-origin") - cy.wait("@index") - .its("response.headers.x-frame-options") - .should("eq", "DENY") + checkHeaders("@index", { + ...defaultHeaders, + "x-custom-header": "my custom header value", + }) }) - it("should contain correct headers for static assest", () => { - cy.intercept("/static/astro-**.png").as("img-import") - - cy.visit("/") + // it("should contain correct headers for static assets", () => { + // cy.intercept("/static/astro-5459bfacab7ae1bcfb22dc9100754547.png").as( + // "img-import" + // ) + // cy.visit("/static/astro-5459bfacab7ae1bcfb22dc9100754547.png") - cy.wait("@img-import") - .its("response.headers.cache-control") - .should("eq", "public, max-age=31536000, immutable") - cy.wait("@img-import") - .its("response.headers.x-xss-protection") - .should("eq", "1; mode=block") - cy.wait("@img-import") - .its("response.headers.x-content-type-options") - .should("eq", "nosniff") - cy.wait("@img-import") - .its("response.headers.referrer-policy") - .should("eq", "same-origin") - cy.wait("@img-import") - .its("response.headers.x-frame-options") - .should("eq", "DENY") - }) + // checkHeaders("@img-import", { + // ...defaultHeaders, + // "cache-control": "public, max-age=31536000, immutable", + // }) + // }) it("should contain correct headers for ssr page", () => { cy.intercept("/ssr/static").as("ssr") - cy.visit("/ssr/static") - cy.wait("@ssr") - .its("response.headers.x-ssr-header") - .should("eq", "my custom header value from config") - cy.wait("@ssr") - .its("response.headers.x-xss-protection") - .should("eq", "1; mode=block") - cy.wait("@ssr") - .its("response.headers.x-content-type-options") - .should("eq", "nosniff") - cy.wait("@ssr") - .its("response.headers.referrer-policy") - .should("eq", "same-origin") - cy.wait("@ssr").its("response.headers.x-frame-options").should("eq", "DENY") + checkHeaders("@ssr", { + ...defaultHeaders, + "x-ssr-header": "my custom header value from config", + }) }) it("should contain correct headers for dsg page", () => { cy.intercept("/dsg/static").as("dsg") - cy.visit("/dsg/static") - cy.wait("@dsg") - .its("response.headers.x-dsg-header") - .should("eq", "my custom header value") - cy.wait("@dsg") - .its("response.headers.x-xss-protection") - .should("eq", "1; mode=block") - cy.wait("@dsg") - .its("response.headers.x-content-type-options") - .should("eq", "nosniff") - cy.wait("@dsg") - .its("response.headers.referrer-policy") - .should("eq", "same-origin") - cy.wait("@dsg").its("response.headers.x-frame-options").should("eq", "DENY") + checkHeaders("@dsg", { + ...defaultHeaders, + "x-dsg-header": "my custom header value", + }) }) }) From 9578dec0b285a3580db5bea81595b171ce250490 Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Thu, 26 Oct 2023 12:37:56 -0400 Subject: [PATCH 14/23] tests --- e2e-tests/adapters/cypress/e2e/headers.cy.ts | 14 +++++++------- e2e-tests/adapters/gatsby-config.ts | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index 910964017ebaf..0265c5f0d1702 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -18,7 +18,7 @@ describe("Headers", () => { "x-frame-options": "DENY", } - it("should contain correct headers for index page and static assets", () => { + it("should contain correct headers for index page", () => { cy.intercept("/").as("index") cy.visit("/") @@ -29,10 +29,10 @@ describe("Headers", () => { }) // it("should contain correct headers for static assets", () => { - // cy.intercept("/static/astro-5459bfacab7ae1bcfb22dc9100754547.png").as( + // cy.intercept("/static/astro-*.png").as( // "img-import" // ) - // cy.visit("/static/astro-5459bfacab7ae1bcfb22dc9100754547.png") + // cy.visit("/.png") // checkHeaders("@img-import", { // ...defaultHeaders, @@ -41,8 +41,8 @@ describe("Headers", () => { // }) it("should contain correct headers for ssr page", () => { - cy.intercept("/ssr/static").as("ssr") - cy.visit("/ssr/static") + cy.intercept("routes/ssr/static").as("ssr") + cy.visit("routes/ssr/static") checkHeaders("@ssr", { ...defaultHeaders, @@ -51,8 +51,8 @@ describe("Headers", () => { }) it("should contain correct headers for dsg page", () => { - cy.intercept("/dsg/static").as("dsg") - cy.visit("/dsg/static") + cy.intercept("routes/dsg/static").as("dsg") + cy.visit("routes/dsg/static") checkHeaders("@dsg", { ...defaultHeaders, diff --git a/e2e-tests/adapters/gatsby-config.ts b/e2e-tests/adapters/gatsby-config.ts index 231f741c1c0c2..a7165350b300f 100644 --- a/e2e-tests/adapters/gatsby-config.ts +++ b/e2e-tests/adapters/gatsby-config.ts @@ -33,7 +33,7 @@ const config: GatsbyConfig = { ], }, { - source: `/ssr/*`, + source: `routes/ssr/*`, headers: [ { key: "x-ssr-header", @@ -46,7 +46,7 @@ const config: GatsbyConfig = { ], }, { - source: `/dsg/*`, + source: `routes/dsg/*`, headers: [ { key: "x-dsg-header", From fe00ca5e1fdf63b63aaf2587b49a1effb5eadd23 Mon Sep 17 00:00:00 2001 From: kathmbeck Date: Thu, 26 Oct 2023 13:08:17 -0400 Subject: [PATCH 15/23] static assets todo --- e2e-tests/adapters/cypress/e2e/headers.cy.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index 0265c5f0d1702..cff3d9eaeae69 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -29,15 +29,7 @@ describe("Headers", () => { }) // it("should contain correct headers for static assets", () => { - // cy.intercept("/static/astro-*.png").as( - // "img-import" - // ) - // cy.visit("/.png") - - // checkHeaders("@img-import", { - // ...defaultHeaders, - // "cache-control": "public, max-age=31536000, immutable", - // }) + // @todo // }) it("should contain correct headers for ssr page", () => { From c1a9ffdd9d19a8e659dc41b9ec6eb0f44ae518dd Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 30 Oct 2023 13:22:19 +0100 Subject: [PATCH 16/23] example of permanent caching header assertion --- e2e-tests/adapters/cypress/e2e/basics.cy.ts | 14 +++------ e2e-tests/adapters/cypress/e2e/headers.cy.ts | 29 ++++++++++++++----- .../utils/dont-cache-responses-in-browser.ts | 21 ++++++++++++++ 3 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 e2e-tests/adapters/cypress/utils/dont-cache-responses-in-browser.ts diff --git a/e2e-tests/adapters/cypress/e2e/basics.cy.ts b/e2e-tests/adapters/cypress/e2e/basics.cy.ts index 28bdb3c31c044..a3f0ef00b455f 100644 --- a/e2e-tests/adapters/cypress/e2e/basics.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/basics.cy.ts @@ -1,18 +1,12 @@ import { title } from "../../constants" +import { WorkaroundCachedResponse } from "../utils/dont-cache-responses-in-browser" describe("Basics", () => { beforeEach(() => { cy.intercept("/gatsby-icon.png").as("static-folder-image") - cy.intercept("/static/astro-**.png", req => { - req.on("before:response", res => { - // this generally should be permamently cached, but that cause problems with intercepting - // see https://docs.cypress.io/api/commands/intercept#cyintercept-and-request-caching - // so we disable caching for this response - // tests for cache-control headers should be done elsewhere - - res.headers["cache-control"] = "no-store" - }) - }).as("img-import") + cy.intercept("/static/astro-**.png", WorkaroundCachedResponse).as( + "img-import" + ) cy.visit("/").waitForRouteChange() }) diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index cff3d9eaeae69..449c49e2bb925 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -1,4 +1,4 @@ -import { title } from "../../constants" +import { WorkaroundCachedResponse } from "../utils/dont-cache-responses-in-browser" describe("Headers", () => { function checkHeaders(routeAlias, expectedHeaders) { @@ -18,36 +18,51 @@ describe("Headers", () => { "x-frame-options": "DENY", } - it("should contain correct headers for index page", () => { + beforeEach(() => { cy.intercept("/").as("index") + cy.intercept("/static/astro-**.png", WorkaroundCachedResponse).as( + "img-import" + ) + cy.intercept("routes/ssr/static").as("ssr") + cy.intercept("routes/dsg/static").as("dsg") + }) + + it("should contain correct headers for index page", () => { cy.visit("/") checkHeaders("@index", { ...defaultHeaders, "x-custom-header": "my custom header value", + "cache-control": "public,max-age=0,must-revalidate", }) }) - // it("should contain correct headers for static assets", () => { - // @todo - // }) + it("should contain correct headers for static assets", () => { + cy.visit("/") + + checkHeaders("@img-import", { + ...defaultHeaders, + "x-custom-header": "my custom header value", + "cache-control": "public,max-age=31536000,immutable", + }) + }) it("should contain correct headers for ssr page", () => { - cy.intercept("routes/ssr/static").as("ssr") cy.visit("routes/ssr/static") checkHeaders("@ssr", { ...defaultHeaders, + "x-custom-header": "my custom header value", "x-ssr-header": "my custom header value from config", }) }) it("should contain correct headers for dsg page", () => { - cy.intercept("routes/dsg/static").as("dsg") cy.visit("routes/dsg/static") checkHeaders("@dsg", { ...defaultHeaders, + "x-custom-header": "my custom header value", "x-dsg-header": "my custom header value", }) }) diff --git a/e2e-tests/adapters/cypress/utils/dont-cache-responses-in-browser.ts b/e2e-tests/adapters/cypress/utils/dont-cache-responses-in-browser.ts new file mode 100644 index 0000000000000..7122b390e67d9 --- /dev/null +++ b/e2e-tests/adapters/cypress/utils/dont-cache-responses-in-browser.ts @@ -0,0 +1,21 @@ +import { CyHttpMessages } from "cypress/types/net-stubbing" + +/** + * https://docs.cypress.io/api/commands/intercept#cyintercept-and-request-caching + * + * For responses that are to be cached we need to use a trick so browser doesn't cache them + * So this enforces `no-store` cache-control header before response hits the browser + * and then restore original cache-control value for assertions. + */ +export const WorkaroundCachedResponse = ( + req: CyHttpMessages.IncomingHttpRequest +): void | Promise => { + req.on("before:response", res => { + res.headers["x-original-cache-control"] = res.headers["cache-control"] + res.headers["cache-control"] = "no-store" + }) + req.on("after:response", res => { + res.headers["cache-control"] = res.headers["x-original-cache-control"] + delete res.headers["x-original-cache-control"] + }) +} From 90694c1b4444e78a859c8102bcda54cc98fe70d6 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 30 Oct 2023 13:46:21 +0100 Subject: [PATCH 17/23] ensure getServerData header has priority over config header for SSR pages --- e2e-tests/adapters/cypress/e2e/headers.cy.ts | 2 ++ e2e-tests/adapters/gatsby-config.ts | 2 +- .../adapters/src/pages/routes/ssr/static.jsx | 18 ++++++++++++------ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index 449c49e2bb925..36f7d1a6f9fee 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -54,6 +54,8 @@ describe("Headers", () => { ...defaultHeaders, "x-custom-header": "my custom header value", "x-ssr-header": "my custom header value from config", + "x-ssr-header-getserverdata": "my custom header value from getServerData", + "x-ssr-header-overwrite": "getServerData wins", }) }) diff --git a/e2e-tests/adapters/gatsby-config.ts b/e2e-tests/adapters/gatsby-config.ts index a7165350b300f..b9a70fcf6c0bb 100644 --- a/e2e-tests/adapters/gatsby-config.ts +++ b/e2e-tests/adapters/gatsby-config.ts @@ -41,7 +41,7 @@ const config: GatsbyConfig = { }, { key: "x-ssr-header-overwrite", - value: "my custom header value from config", + value: "config wins", }, ], }, diff --git a/e2e-tests/adapters/src/pages/routes/ssr/static.jsx b/e2e-tests/adapters/src/pages/routes/ssr/static.jsx index 971c95cbd4827..2af8965092c81 100644 --- a/e2e-tests/adapters/src/pages/routes/ssr/static.jsx +++ b/e2e-tests/adapters/src/pages/routes/ssr/static.jsx @@ -7,15 +7,17 @@ const SSR = ({ serverData }) => {

SSR

-
-            {JSON.stringify({ serverData }, null, 2)}
-          
+
{JSON.stringify({ serverData }, null, 2)}
-
{JSON.stringify(serverData?.arg?.query)}
-
{JSON.stringify(serverData?.arg?.params)}
+
+            {JSON.stringify(serverData?.arg?.query)}
+          
+
+            {JSON.stringify(serverData?.arg?.params)}
+          
@@ -32,5 +34,9 @@ export function getServerData(arg) { ssr: true, arg, }, + headers: { + "x-ssr-header-getserverdata": "my custom header value from getServerData", + "x-ssr-header-overwrite": "getServerData wins", + }, } -} \ No newline at end of file +} From 5063e03b87131310ce354fe927ece346a6fa51e4 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 30 Oct 2023 15:46:04 +0100 Subject: [PATCH 18/23] normalize header values before assertions --- e2e-tests/adapters/cypress/e2e/headers.cy.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index 36f7d1a6f9fee..fa011a33bd290 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -1,11 +1,26 @@ import { WorkaroundCachedResponse } from "../utils/dont-cache-responses-in-browser" describe("Headers", () => { + // `ntl serve` and actual deploy seem to have possible slight differences around header value formatting + // so this just remove spaces around commas to make it easier to compare + function normalizeHeaderValue(value: string | undefined): string | undefined { + if (typeof value === "undefined") { + return value + } + // Remove spaces around commas + return value.replace(/\s*,\s*/gm, `,`) + } function checkHeaders(routeAlias, expectedHeaders) { cy.wait(routeAlias).then(interception => { Object.keys(expectedHeaders).forEach(headerKey => { - expect(interception.response.headers[headerKey]).to.eq( - expectedHeaders[headerKey] + const headers = interception.response.headers[headerKey] + + const firstHeader: string = Array.isArray(headers) + ? headers[0] + : headers + + expect(normalizeHeaderValue(firstHeader)).to.eq( + normalizeHeaderValue(expectedHeaders[headerKey]) ) }) }) From 6257dd832e9d7e318d6069de80d71366424b97fd Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 30 Oct 2023 17:02:04 +0100 Subject: [PATCH 19/23] add page- and app-data header checks --- e2e-tests/adapters/cypress/e2e/headers.cy.ts | 43 ++++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index fa011a33bd290..a550eedd4ce61 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -34,26 +34,34 @@ describe("Headers", () => { } beforeEach(() => { - cy.intercept("/").as("index") + cy.intercept("/", WorkaroundCachedResponse).as("index") + cy.intercept("**/page-data.json", WorkaroundCachedResponse).as("page-data") + cy.intercept("**/app-data.json", WorkaroundCachedResponse).as("app-data") cy.intercept("/static/astro-**.png", WorkaroundCachedResponse).as( "img-import" ) - cy.intercept("routes/ssr/static").as("ssr") - cy.intercept("routes/dsg/static").as("dsg") + cy.intercept("routes/ssr/static", WorkaroundCachedResponse).as("ssr") + cy.intercept("routes/dsg/static", WorkaroundCachedResponse).as("dsg") }) it("should contain correct headers for index page", () => { - cy.visit("/") + cy.visit("/").waitForRouteChange() checkHeaders("@index", { ...defaultHeaders, "x-custom-header": "my custom header value", "cache-control": "public,max-age=0,must-revalidate", }) - }) - it("should contain correct headers for static assets", () => { - cy.visit("/") + checkHeaders("@app-data", { + ...defaultHeaders, + "cache-control": "public,max-age=0,must-revalidate", + }) + + checkHeaders("@page-data", { + ...defaultHeaders, + "cache-control": "public,max-age=0,must-revalidate", + }) checkHeaders("@img-import", { ...defaultHeaders, @@ -63,7 +71,7 @@ describe("Headers", () => { }) it("should contain correct headers for ssr page", () => { - cy.visit("routes/ssr/static") + cy.visit("routes/ssr/static").waitForRouteChange() checkHeaders("@ssr", { ...defaultHeaders, @@ -72,15 +80,32 @@ describe("Headers", () => { "x-ssr-header-getserverdata": "my custom header value from getServerData", "x-ssr-header-overwrite": "getServerData wins", }) + + checkHeaders("@app-data", { + ...defaultHeaders, + "cache-control": "public,max-age=0,must-revalidate", + }) + + // page-data is baked into SSR page so it's not fetched and we don't assert it }) it("should contain correct headers for dsg page", () => { - cy.visit("routes/dsg/static") + cy.visit("routes/dsg/static").waitForRouteChange() checkHeaders("@dsg", { ...defaultHeaders, "x-custom-header": "my custom header value", "x-dsg-header": "my custom header value", }) + + checkHeaders("@app-data", { + ...defaultHeaders, + "cache-control": "public,max-age=0,must-revalidate", + }) + + checkHeaders("@page-data", { + ...defaultHeaders, + "cache-control": "public,max-age=0,must-revalidate", + }) }) }) From 58d94cbbdab6bfbb23eb4823cf6dd1c4380f7bd4 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 30 Oct 2023 17:04:29 +0100 Subject: [PATCH 20/23] tmp: skip deleting deploys for easier iteration --- .../scripts/deploy-and-run/netlify.mjs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/e2e-tests/adapters/scripts/deploy-and-run/netlify.mjs b/e2e-tests/adapters/scripts/deploy-and-run/netlify.mjs index ed09a06299eac..644dba2ee83f4 100644 --- a/e2e-tests/adapters/scripts/deploy-and-run/netlify.mjs +++ b/e2e-tests/adapters/scripts/deploy-and-run/netlify.mjs @@ -37,24 +37,21 @@ console.log(`Deployed to ${deployInfo.deploy_url}`) try { await execa(`npm`, [`run`, npmScriptToRun], { stdio: `inherit` }) } finally { - if (!process.env.GATSBY_TEST_SKIP_CLEANUP) { - console.log(`Deleting project with deploy_id ${deployInfo.deploy_id}`) - - const deleteResponse = await execa("ntl", [ - "api", - "deleteDeploy", - "--data", - `{ "deploy_id": "${deployInfo.deploy_id}" }`, - ]) - - if (deleteResponse.exitCode !== 0) { - throw new Error( - `Failed to delete project ${deleteResponse.stdout} ${deleteResponse.stderr} (${deleteResponse.exitCode})` - ) - } - - console.log( - `Successfully deleted project with deploy_id ${deployInfo.deploy_id}` - ) - } + // if (!process.env.GATSBY_TEST_SKIP_CLEANUP) { + // console.log(`Deleting project with deploy_id ${deployInfo.deploy_id}`) + // const deleteResponse = await execa("ntl", [ + // "api", + // "deleteDeploy", + // "--data", + // `{ "deploy_id": "${deployInfo.deploy_id}" }`, + // ]) + // if (deleteResponse.exitCode !== 0) { + // throw new Error( + // `Failed to delete project ${deleteResponse.stdout} ${deleteResponse.stderr} (${deleteResponse.exitCode})` + // ) + // } + // console.log( + // `Successfully deleted project with deploy_id ${deployInfo.deploy_id}` + // ) + // } } From e05986d72781fc5aaf2cd909cabe806a95941eaa Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 30 Oct 2023 17:59:31 +0100 Subject: [PATCH 21/23] refactor test a bit so it's easier to assert same thing in multiple tests + add assertions for js assets --- e2e-tests/adapters/cypress/e2e/headers.cy.ts | 94 +++++++++++--------- 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index a550eedd4ce61..d4731a053e00d 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -1,6 +1,33 @@ import { WorkaroundCachedResponse } from "../utils/dont-cache-responses-in-browser" describe("Headers", () => { + const defaultHeaders = { + "x-xss-protection": "1; mode=block", + "x-content-type-options": "nosniff", + "referrer-policy": "same-origin", + "x-frame-options": "DENY", + } + + // DRY for repeated assertions in multple tests + const expectedHeadersByRouteAlias = { + "@app-data": { + ...defaultHeaders, + "cache-control": "public,max-age=0,must-revalidate", + }, + "@page-data": { + ...defaultHeaders, + "cache-control": "public,max-age=0,must-revalidate", + }, + "@img-webpack-import": { + ...defaultHeaders, + "cache-control": "public,max-age=31536000,immutable", + }, + "@js": { + ...defaultHeaders, + "cache-control": "public,max-age=31536000,immutable", + }, + } + // `ntl serve` and actual deploy seem to have possible slight differences around header value formatting // so this just remove spaces around commas to make it easier to compare function normalizeHeaderValue(value: string | undefined): string | undefined { @@ -10,7 +37,18 @@ describe("Headers", () => { // Remove spaces around commas return value.replace(/\s*,\s*/gm, `,`) } - function checkHeaders(routeAlias, expectedHeaders) { + function checkHeaders( + routeAlias: string, + expectedHeaders?: Record + ) { + if (!expectedHeaders) { + expectedHeaders = expectedHeadersByRouteAlias[routeAlias] + } + + if (!expectedHeaders) { + throw new Error(`No expected headers provided for "${routeAlias}`) + } + cy.wait(routeAlias).then(interception => { Object.keys(expectedHeaders).forEach(headerKey => { const headers = interception.response.headers[headerKey] @@ -26,22 +64,17 @@ describe("Headers", () => { }) } - const defaultHeaders = { - "x-xss-protection": "1; mode=block", - "x-content-type-options": "nosniff", - "referrer-policy": "same-origin", - "x-frame-options": "DENY", - } - beforeEach(() => { cy.intercept("/", WorkaroundCachedResponse).as("index") + cy.intercept("routes/ssr/static", WorkaroundCachedResponse).as("ssr") + cy.intercept("routes/dsg/static", WorkaroundCachedResponse).as("dsg") + cy.intercept("**/page-data.json", WorkaroundCachedResponse).as("page-data") cy.intercept("**/app-data.json", WorkaroundCachedResponse).as("app-data") cy.intercept("/static/astro-**.png", WorkaroundCachedResponse).as( - "img-import" + "img-webpack-import" ) - cy.intercept("routes/ssr/static", WorkaroundCachedResponse).as("ssr") - cy.intercept("routes/dsg/static", WorkaroundCachedResponse).as("dsg") + cy.intercept("*.js", WorkaroundCachedResponse).as("js") }) it("should contain correct headers for index page", () => { @@ -53,21 +86,11 @@ describe("Headers", () => { "cache-control": "public,max-age=0,must-revalidate", }) - checkHeaders("@app-data", { - ...defaultHeaders, - "cache-control": "public,max-age=0,must-revalidate", - }) - - checkHeaders("@page-data", { - ...defaultHeaders, - "cache-control": "public,max-age=0,must-revalidate", - }) - - checkHeaders("@img-import", { - ...defaultHeaders, - "x-custom-header": "my custom header value", - "cache-control": "public,max-age=31536000,immutable", - }) + checkHeaders("@app-data") + checkHeaders("@page-data") + // index page is only one showing webpack imported image + checkHeaders("@img-webpack-import") + checkHeaders("@js") }) it("should contain correct headers for ssr page", () => { @@ -81,11 +104,8 @@ describe("Headers", () => { "x-ssr-header-overwrite": "getServerData wins", }) - checkHeaders("@app-data", { - ...defaultHeaders, - "cache-control": "public,max-age=0,must-revalidate", - }) - + checkHeaders("@app-data") + checkHeaders("@js") // page-data is baked into SSR page so it's not fetched and we don't assert it }) @@ -98,14 +118,8 @@ describe("Headers", () => { "x-dsg-header": "my custom header value", }) - checkHeaders("@app-data", { - ...defaultHeaders, - "cache-control": "public,max-age=0,must-revalidate", - }) - - checkHeaders("@page-data", { - ...defaultHeaders, - "cache-control": "public,max-age=0,must-revalidate", - }) + checkHeaders("@app-data") + checkHeaders("@page-data") + checkHeaders("@js") }) }) From 0353fe922eaef231aa46856629c1bac18bdc3afa Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 30 Oct 2023 18:03:05 +0100 Subject: [PATCH 22/23] add slice-data headers check --- e2e-tests/adapters/cypress/e2e/headers.cy.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index d4731a053e00d..399af61fdd359 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -18,6 +18,10 @@ describe("Headers", () => { ...defaultHeaders, "cache-control": "public,max-age=0,must-revalidate", }, + "@slice-data": { + ...defaultHeaders, + "cache-control": "public,max-age=0,must-revalidate", + }, "@img-webpack-import": { ...defaultHeaders, "cache-control": "public,max-age=31536000,immutable", @@ -71,6 +75,9 @@ describe("Headers", () => { cy.intercept("**/page-data.json", WorkaroundCachedResponse).as("page-data") cy.intercept("**/app-data.json", WorkaroundCachedResponse).as("app-data") + cy.intercept("**/slice-data/*.json", WorkaroundCachedResponse).as( + "slice-data" + ) cy.intercept("/static/astro-**.png", WorkaroundCachedResponse).as( "img-webpack-import" ) @@ -88,6 +95,7 @@ describe("Headers", () => { checkHeaders("@app-data") checkHeaders("@page-data") + checkHeaders("@slice-data") // index page is only one showing webpack imported image checkHeaders("@img-webpack-import") checkHeaders("@js") @@ -105,8 +113,9 @@ describe("Headers", () => { }) checkHeaders("@app-data") - checkHeaders("@js") // page-data is baked into SSR page so it's not fetched and we don't assert it + checkHeaders("@slice-data") + checkHeaders("@js") }) it("should contain correct headers for dsg page", () => { @@ -120,6 +129,7 @@ describe("Headers", () => { checkHeaders("@app-data") checkHeaders("@page-data") + checkHeaders("@slice-data") checkHeaders("@js") }) }) From 150e4e97a2ab40b9ca82a9b2bdade2090e19fe25 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 30 Oct 2023 18:07:21 +0100 Subject: [PATCH 23/23] add static query result header test --- e2e-tests/adapters/cypress/e2e/headers.cy.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/e2e-tests/adapters/cypress/e2e/headers.cy.ts b/e2e-tests/adapters/cypress/e2e/headers.cy.ts index 399af61fdd359..6ff0002d19ce6 100644 --- a/e2e-tests/adapters/cypress/e2e/headers.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/headers.cy.ts @@ -22,6 +22,10 @@ describe("Headers", () => { ...defaultHeaders, "cache-control": "public,max-age=0,must-revalidate", }, + "@static-query-result": { + ...defaultHeaders, + "cache-control": "public,max-age=0,must-revalidate", + }, "@img-webpack-import": { ...defaultHeaders, "cache-control": "public,max-age=31536000,immutable", @@ -78,6 +82,10 @@ describe("Headers", () => { cy.intercept("**/slice-data/*.json", WorkaroundCachedResponse).as( "slice-data" ) + cy.intercept("**/page-data/sq/d/*.json", WorkaroundCachedResponse).as( + "static-query-result" + ) + cy.intercept("/static/astro-**.png", WorkaroundCachedResponse).as( "img-webpack-import" ) @@ -96,6 +104,8 @@ describe("Headers", () => { checkHeaders("@app-data") checkHeaders("@page-data") checkHeaders("@slice-data") + checkHeaders("@static-query-result") + // index page is only one showing webpack imported image checkHeaders("@img-webpack-import") checkHeaders("@js") @@ -115,6 +125,7 @@ describe("Headers", () => { checkHeaders("@app-data") // page-data is baked into SSR page so it's not fetched and we don't assert it checkHeaders("@slice-data") + checkHeaders("@static-query-result") checkHeaders("@js") }) @@ -130,6 +141,7 @@ describe("Headers", () => { checkHeaders("@app-data") checkHeaders("@page-data") checkHeaders("@slice-data") + checkHeaders("@static-query-result") checkHeaders("@js") }) })