From feccdf1fbadbe3f47489ac5b2c53279fb5b576be Mon Sep 17 00:00:00 2001 From: Soc Sieng Date: Fri, 18 Oct 2024 00:08:08 +1100 Subject: [PATCH] Add unit tests for route handler --- .../tests/core/__mocks__/next/BUILD_ID | 1 + .../__mocks__/next/prerender-manifest.json | 61 +++++ .../__mocks__/next/required-server-files.json | 135 ++++++++++ .../core/__mocks__/next/routes-manifest.json | 238 ++++++++++++++++++ .../next/server/middleware-manifest.json | 40 +++ .../__mocks__/next/server/pages-manifest.json | 33 +++ .../tests/core/routingHandler.test.ts | 94 +++++++ 7 files changed, 602 insertions(+) create mode 100644 packages/tests-unit/tests/core/__mocks__/next/BUILD_ID create mode 100644 packages/tests-unit/tests/core/__mocks__/next/prerender-manifest.json create mode 100644 packages/tests-unit/tests/core/__mocks__/next/required-server-files.json create mode 100644 packages/tests-unit/tests/core/__mocks__/next/routes-manifest.json create mode 100644 packages/tests-unit/tests/core/__mocks__/next/server/middleware-manifest.json create mode 100644 packages/tests-unit/tests/core/__mocks__/next/server/pages-manifest.json create mode 100644 packages/tests-unit/tests/core/routingHandler.test.ts diff --git a/packages/tests-unit/tests/core/__mocks__/next/BUILD_ID b/packages/tests-unit/tests/core/__mocks__/next/BUILD_ID new file mode 100644 index 000000000..65cc6666f --- /dev/null +++ b/packages/tests-unit/tests/core/__mocks__/next/BUILD_ID @@ -0,0 +1 @@ +lnRb3bjsx2nspyrLXPfpa \ No newline at end of file diff --git a/packages/tests-unit/tests/core/__mocks__/next/prerender-manifest.json b/packages/tests-unit/tests/core/__mocks__/next/prerender-manifest.json new file mode 100644 index 000000000..c6a0e89c1 --- /dev/null +++ b/packages/tests-unit/tests/core/__mocks__/next/prerender-manifest.json @@ -0,0 +1,61 @@ +{ + "version": 4, + "routes": { + "/middleware-rewrite-with-isr-destination": { + "initialRevalidateSeconds": 10, + "srcRoute": null, + "dataRoute": "/_next/data/lnRb3bjsx2nspyrLXPfpa/middleware-rewrite-with-isr-destination.json" + }, + "/isr": { + "initialRevalidateSeconds": 10, + "srcRoute": null, + "dataRoute": "/_next/data/lnRb3bjsx2nspyrLXPfpa/isr.json" + }, + "/ssg-preview/hello": { + "initialRevalidateSeconds": false, + "srcRoute": "/ssg-preview/[id]", + "dataRoute": "/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg-preview/hello.json" + }, + "/ssg-preview/world": { + "initialRevalidateSeconds": false, + "srcRoute": "/ssg-preview/[id]", + "dataRoute": "/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg-preview/world.json" + }, + "/ssg": { + "initialRevalidateSeconds": false, + "srcRoute": null, + "dataRoute": "/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg.json" + }, + "/ssg-dynamic/1": { + "initialRevalidateSeconds": false, + "srcRoute": "/ssg-dynamic/[id]", + "dataRoute": "/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg-dynamic/1.json" + } + }, + "dynamicRoutes": { + "/ssg-dynamic-fallback/[id]": { + "routeRegex": "^/ssg\\-dynamic\\-fallback/([^/]+?)(?:/)?$", + "dataRoute": "/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg-dynamic-fallback/[id].json", + "fallback": "/ssg-dynamic-fallback/[id].html", + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg\\-dynamic\\-fallback/([^/]+?)\\.json$" + }, + "/ssg-preview/[id]": { + "routeRegex": "^/ssg\\-preview/([^/]+?)(?:/)?$", + "dataRoute": "/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg-preview/[id].json", + "fallback": false, + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg\\-preview/([^/]+?)\\.json$" + }, + "/ssg-dynamic/[id]": { + "routeRegex": "^/ssg\\-dynamic/([^/]+?)(?:/)?$", + "dataRoute": "/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg-dynamic/[id].json", + "fallback": false, + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg\\-dynamic/([^/]+?)\\.json$" + } + }, + "notFoundRoutes": [], + "preview": { + "previewModeId": "a4f0a4586498ab90ef6af5c106920f7e", + "previewModeSigningKey": "61c53747d3c40a6a1e809ecae2a7c625643701bb390321f6c57813e7a78363d9", + "previewModeEncryptionKey": "d9108ce7f77f85f1f07ea2fc9e7355805cf11ddd28c85f1a61cfce1810515a60" + } +} diff --git a/packages/tests-unit/tests/core/__mocks__/next/required-server-files.json b/packages/tests-unit/tests/core/__mocks__/next/required-server-files.json new file mode 100644 index 000000000..5ea98c1d2 --- /dev/null +++ b/packages/tests-unit/tests/core/__mocks__/next/required-server-files.json @@ -0,0 +1,135 @@ +{ + "version": 1, + "config": { + "env": {}, + "webpack": null, + "eslint": { "ignoreDuringBuilds": true }, + "typescript": { + "ignoreBuildErrors": false, + "tsconfigPath": "tsconfig.json" + }, + "distDir": ".next", + "cleanDistDir": true, + "assetPrefix": "", + "configOrigin": "next.config.js", + "useFileSystemPublicRoutes": true, + "generateEtags": true, + "pageExtensions": ["tsx", "ts", "jsx", "js"], + "poweredByHeader": true, + "compress": true, + "analyticsId": "", + "images": { + "deviceSizes": [640, 750, 828, 1080, 1200, 1920, 2048, 3840], + "imageSizes": [16, 32, 48, 64, 96, 128, 256, 384], + "path": "/_next/image", + "loader": "default", + "loaderFile": "", + "domains": [], + "disableStaticImages": false, + "minimumCacheTTL": 60, + "formats": ["image/webp"], + "dangerouslyAllowSVG": false, + "contentSecurityPolicy": "script-src 'none'; frame-src 'none'; sandbox;", + "contentDispositionType": "inline", + "remotePatterns": [{ "hostname": "**.unsplash.com" }], + "unoptimized": false + }, + "devIndicators": { + "buildActivity": true, + "buildActivityPosition": "bottom-right" + }, + "onDemandEntries": { "maxInactiveAge": 60000, "pagesBufferLength": 5 }, + "amp": { "canonicalBase": "" }, + "basePath": "", + "sassOptions": {}, + "trailingSlash": false, + "i18n": null, + "productionBrowserSourceMaps": false, + "optimizeFonts": true, + "excludeDefaultMomentLocales": true, + "serverRuntimeConfig": {}, + "publicRuntimeConfig": {}, + "reactProductionProfiling": false, + "reactStrictMode": true, + "httpAgentOptions": { "keepAlive": true }, + "outputFileTracing": true, + "staticPageGenerationTimeout": 60, + "swcMinify": true, + "output": "standalone", + "modularizeImports": { + "@mui/icons-material": { "transform": "@mui/icons-material/{{member}}" }, + "date-fns": { "transform": "date-fns/{{member}}" }, + "lodash": { "transform": "lodash/{{member}}" }, + "lodash-es": { "transform": "lodash-es/{{member}}" }, + "ramda": { "transform": "ramda/es/{{member}}" }, + "react-bootstrap": { "transform": "react-bootstrap/{{member}}" }, + "antd": { "transform": "antd/lib/{{kebabCase member}}" }, + "ahooks": { "transform": "ahooks/es/{{member}}" }, + "@ant-design/icons": { + "transform": "@ant-design/icons/lib/icons/{{member}}" + } + }, + "experimental": { + "serverMinification": false, + "serverSourceMaps": false, + "caseSensitiveRoutes": false, + "useDeploymentId": false, + "useDeploymentIdServerActions": false, + "clientRouterFilter": true, + "clientRouterFilterRedirects": false, + "fetchCacheKeyPrefix": "", + "middlewarePrefetch": "flexible", + "optimisticClientCache": true, + "manualClientBasePath": false, + "legacyBrowsers": false, + "newNextLinkBehavior": true, + "cpus": 7, + "memoryBasedWorkersCount": false, + "sharedPool": true, + "isrFlushToDisk": true, + "workerThreads": false, + "pageEnv": false, + "optimizeCss": false, + "nextScriptWorkers": false, + "scrollRestoration": false, + "externalDir": false, + "disableOptimizedLoading": false, + "gzipSize": true, + "swcFileReading": true, + "craCompat": false, + "esmExternals": true, + "appDir": true, + "isrMemoryCacheSize": 52428800, + "fullySpecified": false, + "outputFileTracingRoot": "/Users/socsieng/Projects/opennextjs-aws", + "swcTraceProfiling": false, + "forceSwcTransforms": false, + "largePageDataBytes": 128000, + "adjustFontFallbacks": false, + "adjustFontFallbacksWithSizeAdjust": false, + "typedRoutes": false, + "instrumentationHook": false, + "trustHostHeader": false + }, + "configFileName": "next.config.js" + }, + "appDir": "/Users/socsieng/Projects/opennextjs-aws/example", + "relativeAppDir": "example", + "files": [ + ".next/routes-manifest.json", + ".next/server/pages-manifest.json", + ".next/build-manifest.json", + ".next/prerender-manifest.json", + ".next/server/middleware-manifest.json", + ".next/server/middleware-build-manifest.js", + ".next/server/middleware-react-loadable-manifest.js", + ".next/react-loadable-manifest.json", + ".next/server/font-manifest.json", + ".next/BUILD_ID", + ".next/server/next-font-manifest.js", + ".next/server/next-font-manifest.json" + ], + "ignore": [ + "../node_modules/next/dist/compiled/@ampproject/toolbox-optimizer/**/*" + ] +} diff --git a/packages/tests-unit/tests/core/__mocks__/next/routes-manifest.json b/packages/tests-unit/tests/core/__mocks__/next/routes-manifest.json new file mode 100644 index 000000000..9db5bf1f1 --- /dev/null +++ b/packages/tests-unit/tests/core/__mocks__/next/routes-manifest.json @@ -0,0 +1,238 @@ +{ + "version": 3, + "pages404": true, + "caseSensitive": false, + "basePath": "", + "redirects": [ + { + "source": "/:path+/", + "destination": "/:path+", + "internal": true, + "statusCode": 308, + "regex": "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))/$" + } + ], + "headers": [], + "dynamicRoutes": [ + { + "page": "/api/auth/[...nextauth]", + "regex": "^/api/auth/(.+?)(?:/)?$", + "routeKeys": { "nxtPnextauth": "nxtPnextauth" }, + "namedRegex": "^/api/auth/(?.+?)(?:/)?$" + }, + { + "page": "/ssg-dynamic/[id]", + "regex": "^/ssg\\-dynamic/([^/]+?)(?:/)?$", + "routeKeys": { "nxtPid": "nxtPid" }, + "namedRegex": "^/ssg\\-dynamic/(?[^/]+?)(?:/)?$" + }, + { + "page": "/ssg-dynamic-fallback/[id]", + "regex": "^/ssg\\-dynamic\\-fallback/([^/]+?)(?:/)?$", + "routeKeys": { "nxtPid": "nxtPid" }, + "namedRegex": "^/ssg\\-dynamic\\-fallback/(?[^/]+?)(?:/)?$" + }, + { + "page": "/ssg-preview/[id]", + "regex": "^/ssg\\-preview/([^/]+?)(?:/)?$", + "routeKeys": { "nxtPid": "nxtPid" }, + "namedRegex": "^/ssg\\-preview/(?[^/]+?)(?:/)?$" + } + ], + "staticRoutes": [ + { + "page": "/", + "regex": "^/(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/(?:/)?$" + }, + { + "page": "/404", + "regex": "^/404(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/404(?:/)?$" + }, + { + "page": "/api-route", + "regex": "^/api\\-route(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/api\\-route(?:/)?$" + }, + { + "page": "/font-css-font", + "regex": "^/font\\-css\\-font(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/font\\-css\\-font(?:/)?$" + }, + { + "page": "/font-next-font", + "regex": "^/font\\-next\\-font(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/font\\-next\\-font(?:/)?$" + }, + { + "page": "/image-html-tag", + "regex": "^/image\\-html\\-tag(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/image\\-html\\-tag(?:/)?$" + }, + { + "page": "/image-optimization-imported", + "regex": "^/image\\-optimization\\-imported(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/image\\-optimization\\-imported(?:/)?$" + }, + { + "page": "/image-optimization-remote", + "regex": "^/image\\-optimization\\-remote(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/image\\-optimization\\-remote(?:/)?$" + }, + { + "page": "/isr", + "regex": "^/isr(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/isr(?:/)?$" + }, + { + "page": "/middleware-geolocation", + "regex": "^/middleware\\-geolocation(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/middleware\\-geolocation(?:/)?$" + }, + { + "page": "/middleware-redirect", + "regex": "^/middleware\\-redirect(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/middleware\\-redirect(?:/)?$" + }, + { + "page": "/middleware-redirect-destination", + "regex": "^/middleware\\-redirect\\-destination(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/middleware\\-redirect\\-destination(?:/)?$" + }, + { + "page": "/middleware-rewrite", + "regex": "^/middleware\\-rewrite(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/middleware\\-rewrite(?:/)?$" + }, + { + "page": "/middleware-rewrite-with-isr", + "regex": "^/middleware\\-rewrite\\-with\\-isr(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/middleware\\-rewrite\\-with\\-isr(?:/)?$" + }, + { + "page": "/middleware-rewrite-with-isr-destination", + "regex": "^/middleware\\-rewrite\\-with\\-isr\\-destination(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/middleware\\-rewrite\\-with\\-isr\\-destination(?:/)?$" + }, + { + "page": "/middleware-set-header", + "regex": "^/middleware\\-set\\-header(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/middleware\\-set\\-header(?:/)?$" + }, + { + "page": "/next-auth", + "regex": "^/next\\-auth(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/next\\-auth(?:/)?$" + }, + { + "page": "/ssg", + "regex": "^/ssg(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/ssg(?:/)?$" + }, + { + "page": "/ssr", + "regex": "^/ssr(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/ssr(?:/)?$" + }, + { + "page": "/ssr-not-found", + "regex": "^/ssr\\-not\\-found(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/ssr\\-not\\-found(?:/)?$" + }, + { + "page": "/ssr-redirect", + "regex": "^/ssr\\-redirect(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/ssr\\-redirect(?:/)?$" + }, + { + "page": "/ssr-redirect-destination", + "regex": "^/ssr\\-redirect\\-destination(?:/)?$", + "routeKeys": {}, + "namedRegex": "^/ssr\\-redirect\\-destination(?:/)?$" + } + ], + "dataRoutes": [ + { + "page": "/isr", + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/isr.json$" + }, + { + "page": "/middleware-geolocation", + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/middleware-geolocation.json$" + }, + { + "page": "/middleware-rewrite", + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/middleware-rewrite.json$" + }, + { + "page": "/middleware-rewrite-with-isr-destination", + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/middleware-rewrite-with-isr-destination.json$" + }, + { + "page": "/middleware-set-header", + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/middleware-set-header.json$" + }, + { + "page": "/ssg", + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg.json$" + }, + { + "page": "/ssg-dynamic/[id]", + "routeKeys": { "nxtPid": "nxtPid" }, + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg\\-dynamic/([^/]+?)\\.json$", + "namedDataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg\\-dynamic/(?[^/]+?)\\.json$" + }, + { + "page": "/ssg-dynamic-fallback/[id]", + "routeKeys": { "nxtPid": "nxtPid" }, + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg\\-dynamic\\-fallback/([^/]+?)\\.json$", + "namedDataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg\\-dynamic\\-fallback/(?[^/]+?)\\.json$" + }, + { + "page": "/ssg-preview/[id]", + "routeKeys": { "nxtPid": "nxtPid" }, + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg\\-preview/([^/]+?)\\.json$", + "namedDataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssg\\-preview/(?[^/]+?)\\.json$" + }, + { + "page": "/ssr", + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssr.json$" + }, + { + "page": "/ssr-not-found", + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssr-not-found.json$" + }, + { + "page": "/ssr-redirect", + "dataRouteRegex": "^/_next/data/lnRb3bjsx2nspyrLXPfpa/ssr-redirect.json$" + } + ], + "rsc": { + "header": "RSC", + "varyHeader": "RSC, Next-Router-State-Tree, Next-Router-Prefetch", + "contentTypeHeader": "text/x-component" + }, + "rewrites": [] +} diff --git a/packages/tests-unit/tests/core/__mocks__/next/server/middleware-manifest.json b/packages/tests-unit/tests/core/__mocks__/next/server/middleware-manifest.json new file mode 100644 index 000000000..064c8202d --- /dev/null +++ b/packages/tests-unit/tests/core/__mocks__/next/server/middleware-manifest.json @@ -0,0 +1,40 @@ +{ + "sortedMiddleware": ["/"], + "middleware": { + "/": { + "files": ["server/edge-runtime-webpack.js", "server/middleware.js"], + "name": "middleware", + "page": "/", + "matchers": [ + { + "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/middleware-rewrite(.json)?[\\/#\\?]?$", + "originalSource": "/middleware-rewrite" + }, + { + "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/middleware-rewrite-with-isr(.json)?[\\/#\\?]?$", + "originalSource": "/middleware-rewrite-with-isr" + }, + { + "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/middleware-redirect(.json)?[\\/#\\?]?$", + "originalSource": "/middleware-redirect" + }, + { + "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/middleware-set-header(.json)?[\\/#\\?]?$", + "originalSource": "/middleware-set-header" + }, + { + "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/middleware-fetch(.json)?[\\/#\\?]?$", + "originalSource": "/middleware-fetch" + }, + { + "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?\\/middleware-geolocation(.json)?[\\/#\\?]?$", + "originalSource": "/middleware-geolocation" + } + ], + "wasm": [], + "assets": [] + } + }, + "functions": {}, + "version": 2 +} diff --git a/packages/tests-unit/tests/core/__mocks__/next/server/pages-manifest.json b/packages/tests-unit/tests/core/__mocks__/next/server/pages-manifest.json new file mode 100644 index 000000000..47986b03e --- /dev/null +++ b/packages/tests-unit/tests/core/__mocks__/next/server/pages-manifest.json @@ -0,0 +1,33 @@ +{ + "/_error": "pages/_error.js", + "/_app": "pages/_app.js", + "/404": "pages/404.html", + "/api-route": "pages/api-route.html", + "/api/hello": "pages/api/hello.js", + "/font-css-font": "pages/font-css-font.html", + "/font-next-font": "pages/font-next-font.html", + "/api/preview": "pages/api/preview.js", + "/image-html-tag": "pages/image-html-tag.html", + "/image-optimization-imported": "pages/image-optimization-imported.html", + "/": "pages/index.html", + "/image-optimization-remote": "pages/image-optimization-remote.html", + "/middleware-redirect-destination": "pages/middleware-redirect-destination.html", + "/middleware-redirect": "pages/middleware-redirect.html", + "/next-auth": "pages/next-auth.html", + "/middleware-rewrite-with-isr": "pages/middleware-rewrite-with-isr.html", + "/ssr-redirect-destination": "pages/ssr-redirect-destination.html", + "/api/auth/[...nextauth]": "pages/api/auth/[...nextauth].js", + "/middleware-geolocation": "pages/middleware-geolocation.js", + "/isr": "pages/isr.js", + "/middleware-rewrite-with-isr-destination": "pages/middleware-rewrite-with-isr-destination.js", + "/middleware-rewrite": "pages/middleware-rewrite.js", + "/middleware-set-header": "pages/middleware-set-header.js", + "/ssg-preview/[id]": "pages/ssg-preview/[id].js", + "/ssg-dynamic-fallback/[id]": "pages/ssg-dynamic-fallback/[id].js", + "/ssr-not-found": "pages/ssr-not-found.js", + "/ssg": "pages/ssg.js", + "/ssr": "pages/ssr.js", + "/ssg-dynamic/[id]": "pages/ssg-dynamic/[id].js", + "/_document": "pages/_document.js", + "/ssr-redirect": "pages/ssr-redirect.js" +} diff --git a/packages/tests-unit/tests/core/routingHandler.test.ts b/packages/tests-unit/tests/core/routingHandler.test.ts new file mode 100644 index 000000000..94cd24f21 --- /dev/null +++ b/packages/tests-unit/tests/core/routingHandler.test.ts @@ -0,0 +1,94 @@ +import "@opennextjs/aws/core/createGenericHandler.js"; + +import { convertFromQueryString } from "@opennextjs/aws/core/routing/util.js"; +import routingHandler from "@opennextjs/aws/core/routingHandler.js"; +import { + InternalEvent, + InternalResult, +} from "@opennextjs/aws/types/open-next.js"; +import fs from "fs"; +import path from "path"; +import { vi } from "vitest"; + +type PartialEvent = Partial< + Omit +> & { body?: string }; + +function createEvent(event: PartialEvent): InternalEvent { + const [rawPath, qs] = (event.url ?? "/").split("?", 1); + return { + type: "core", + method: event.method ?? "GET", + rawPath, + url: event.url ?? "/", + body: Buffer.from(event.body ?? ""), + headers: event.headers ?? {}, + query: convertFromQueryString(qs ?? ""), + cookies: event.cookies ?? {}, + remoteAddress: event.remoteAddress ?? "::1", + }; +} + +globalThis.openNextConfig = {}; + +vi.mock("fs", async () => { + const actualFs = (await vi.importActual("fs")) as typeof fs; + const actualPath = (await vi.importActual("path")) as typeof path; + + const fileExpression = + /^(?.+)\/\.(?(next|open-next)\/.+)$/; + + // remap the file path to the mock directory + const readFileSyncMock = vi.fn().mockImplementation((filename, encoding) => { + const match = fileExpression.exec(filename); + const mappedFilename = match + ? actualPath.join(__dirname, "__mocks__", match.groups!.relativePath) + : filename; + + return actualFs.readFileSync(mappedFilename, encoding); + }); + + return { + ...actualFs, + readFileSync: readFileSyncMock, + default: { + ...(actualFs as any).default, + readFileSync: readFileSyncMock, + }, + }; +}); + +beforeEach(() => { + vi.resetAllMocks(); +}); + +it("should return 404 for data requests that don't match the buildId", async () => { + const event = createEvent({ + url: "/_next/data/abc/test", + }); + + const response = (await routingHandler(event)) as InternalResult; + + expect(response.statusCode).toEqual(404); +}); + +it("should not return 404 for data requests that match the buildId", async () => { + const event = createEvent({ + url: "/_next/data/lnRb3bjsx2nspyrLXPfpa/test", + }); + + const response = (await routingHandler(event)) as InternalResult; + + expect(response.statusCode).not.toEqual(404); +}); + +it("should redirect trailing slashes", async () => { + const event = createEvent({ + url: "/api-route/", + }); + + const response = (await routingHandler(event)) as InternalResult; + + expect(response.statusCode).toEqual(308); + expect(response.headers.Location).toEqual("/api-route"); +});