import { handler } from "../../src/default-handler"; import { createCloudFrontEvent } from "../test-utils"; import { CloudFrontRequest, CloudFrontResultResponse, CloudFrontOrigin } from "aws-lambda"; jest.mock( "../../src/manifest.json", () => require("./default-build-manifest.json"), { virtual: true } ); jest.mock( "../../src/api-manifest.json", // eslint-disable-next-line @typescript-eslint/no-var-requires () => require("./api-build-manifest.json"), { virtual: true } ); jest.mock( "../../src/routes-manifest.json", () => require("./default-basepath-routes-manifest.json"), { virtual: true } ); jest.mock( "../../src/prerender-manifest.json", () => require("./prerender-manifest.json"), { virtual: true } ); const mockPageRequire = (mockPagePath: string): void => { jest.mock( `../../src/${mockPagePath}`, () => require(`../shared-fixtures/built-artifact/${mockPagePath}`), { virtual: true } ); }; describe("Lambda@Edge", () => { let consoleWarnSpy: jest.SpyInstance; beforeEach(() => { consoleWarnSpy = jest.spyOn(console, "error").mockReturnValue(); }); afterEach(() => { consoleWarnSpy.mockRestore(); }); describe("HTML pages routing", () => { it.each` path | expectedPage ${"/basepath"} | ${"/index.html"} ${"/basepath/terms"} | ${"/terms.html"} ${"/basepath/users/batman"} | ${"/users/[...user].html"} `( "serves page $expectedPage from S3 for path $path", async ({ path, expectedPage }) => { const event = createCloudFrontEvent({ uri: path, host: "mydistribution.cloudfront.net" }); const result = await handler(event); const request = result as CloudFrontRequest; expect(request.origin).toEqual({ s3: { authMethod: "origin-access-identity", domainName: "my-bucket.s3.amazonaws.com", path: "/basepath/static-pages/build-id", region: "us-east-1" } }); expect(request.uri).toEqual(expectedPage); expect(request.headers.host[0].key).toEqual("host"); expect(request.headers.host[0].value).toEqual( "my-bucket.s3.amazonaws.com" ); } ); it("terms.html should return 200 status after successful S3 Origin response", async () => { const event = createCloudFrontEvent({ uri: "/terms.html", host: "mydistribution.cloudfront.net", config: { eventType: "origin-response" } as any, response: { status: "200" } as any }); const response = (await handler(event)) as CloudFrontResultResponse; expect(response.status).toEqual("200"); }); }); describe("Public files routing", () => { it("serves public file from S3 /public folder", async () => { const event = createCloudFrontEvent({ uri: "/basepath/manifest.json", host: "mydistribution.cloudfront.net" }); const result = await handler(event); const request = result as CloudFrontRequest; expect(request.origin).toEqual({ s3: { authMethod: "origin-access-identity", domainName: "my-bucket.s3.amazonaws.com", path: "/basepath/public", region: "us-east-1" } }); expect(request.uri).toEqual("/manifest.json"); }); it("public file should return 200 status after successful S3 Origin response", async () => { const event = createCloudFrontEvent({ uri: "/manifest.json", host: "mydistribution.cloudfront.net", config: { eventType: "origin-response" } as any, response: { status: "200" } as any }); const response = (await handler(event)) as CloudFrontResultResponse; expect(response.status).toEqual("200"); }); }); describe("SSR pages routing", () => { it.each` path | expectedPage ${"/basepath/abc"} | ${"pages/[root].js"} ${"/basepath/blog/foo"} | ${"pages/blog/[id].js"} ${"/basepath/customers"} | ${"pages/customers.js"} ${"/basepath/customers/superman"} | ${"pages/customers/[customer].js"} ${"/basepath/customers/superman/howtofly"} | ${"pages/customers/[customer]/[post].js"} ${"/basepath/customers/superman/profile"} | ${"pages/customers/[customer]/profile.js"} ${"/basepath/customers/test/catch/all"} | ${"pages/customers/[...catchAll].js"} `( "renders page $expectedPage for path $path", async ({ path, expectedPage }) => { const event = createCloudFrontEvent({ uri: path, host: "mydistribution.cloudfront.net" }); mockPageRequire(expectedPage); const response = await handler(event); const cfResponse = response as CloudFrontResultResponse; const decodedBody = Buffer.from( cfResponse.body as string, "base64" ).toString("utf8"); expect(decodedBody).toEqual(expectedPage); expect(cfResponse.status).toEqual("200"); } ); }); describe("Data Requests", () => { it.each` path | expectedPage ${"/basepath/_next/data/build-id/customers.json"} | ${"pages/customers.js"} ${"/basepath/_next/data/build-id/customers/superman.json"} | ${"pages/customers/[customer].js"} `( "serves json data via SSR for SSR path $path", async ({ path, expectedPage }) => { const event = createCloudFrontEvent({ uri: path, host: "mydistribution.cloudfront.net", config: { eventType: "origin-request" } as any }); mockPageRequire(expectedPage); const result = await handler(event); const cfResponse = result as CloudFrontResultResponse; const decodedBody = Buffer.from( cfResponse.body as string, "base64" ).toString("utf8"); expect(decodedBody).toEqual(JSON.stringify({ page: expectedPage })); expect(cfResponse.status).toEqual("200"); } ); it.each` path | expectedUri ${"/basepath/_next/data/build-id"} | ${"/_next/data/build-id/index.json"} ${"/basepath/_next/data/build-id/index.json"} | ${"/_next/data/build-id/index.json"} ${"/basepath/_next/data/build-id/fallback/not-yet-built.json"} | ${"/_next/data/build-id/fallback/not-yet-built.json"} `( "serves json data via S3 for SSG path $path", async ({ path, expectedUri }) => { const event = createCloudFrontEvent({ uri: path, host: "mydistribution.cloudfront.net", config: { eventType: "origin-request" } as any }); const result = await handler(event); const request = result as CloudFrontRequest; expect(request.origin).toEqual({ s3: { authMethod: "origin-access-identity", domainName: "my-bucket.s3.amazonaws.com", path: "/basepath", region: "us-east-1" } }); expect(request.uri).toEqual(expectedUri); } ); }); it("uses default s3 endpoint when bucket region is us-east-1", async () => { const event = createCloudFrontEvent({ uri: "/basepath/terms", host: "mydistribution.cloudfront.net", s3Region: "us-east-1" }); const result = await handler(event); const request = result as CloudFrontRequest; const origin = request.origin as CloudFrontOrigin; expect(origin.s3).toEqual( expect.objectContaining({ domainName: "my-bucket.s3.amazonaws.com" }) ); expect(request.headers.host[0].key).toEqual("host"); expect(request.headers.host[0].value).toEqual("my-bucket.s3.amazonaws.com"); }); it("uses regional endpoint for static page when bucket region is not us-east-1", async () => { const event = createCloudFrontEvent({ uri: "/basepath/terms", host: "mydistribution.cloudfront.net", s3DomainName: "my-bucket.s3.amazonaws.com", s3Region: "eu-west-1" }); const result = await handler(event); const request = result as CloudFrontRequest; const origin = request.origin as CloudFrontOrigin; expect(origin).toEqual({ s3: { authMethod: "origin-access-identity", domainName: "my-bucket.s3.eu-west-1.amazonaws.com", path: "/basepath/static-pages/build-id", region: "eu-west-1" } }); expect(request.uri).toEqual("/terms.html"); expect(request.headers.host[0].key).toEqual("host"); expect(request.headers.host[0].value).toEqual( "my-bucket.s3.eu-west-1.amazonaws.com" ); }); it("uses regional endpoint for public asset when bucket region is not us-east-1", async () => { const event = createCloudFrontEvent({ uri: "/basepath/favicon.ico", host: "mydistribution.cloudfront.net", s3DomainName: "my-bucket.s3.amazonaws.com", s3Region: "eu-west-1" }); const result = await handler(event); const request = result as CloudFrontRequest; const origin = request.origin as CloudFrontOrigin; expect(origin).toEqual({ s3: { authMethod: "origin-access-identity", domainName: "my-bucket.s3.eu-west-1.amazonaws.com", path: "/basepath/public", region: "eu-west-1" } }); expect(request.uri).toEqual("/favicon.ico"); expect(request.headers.host[0].key).toEqual("host"); expect(request.headers.host[0].value).toEqual( "my-bucket.s3.eu-west-1.amazonaws.com" ); }); describe("404 page", () => { it("renders 404 page if request path can't be matched to any page / api routes", async () => { const event = createCloudFrontEvent({ uri: "/basepath/page/does/not/exist", host: "mydistribution.cloudfront.net" }); const request = (await handler(event)) as CloudFrontRequest; expect(request.uri).toEqual("/404.html"); }); // Next.js serves 404 on pages that do not have basepath prefix. It doesn't redirect whether there is trailing slash or not. it.each` path ${"/terms"} ${"/not/found"} ${"/manifest.json"} ${"/terms/"} ${"/not/found/"} ${"/manifest.json/"} `( "serves 404 page from S3 for path without basepath prefix: $path", async ({ path }) => { const event = createCloudFrontEvent({ uri: path, host: "mydistribution.cloudfront.net" }); const result = await handler(event); const request = result as CloudFrontRequest; expect(request.origin).toEqual({ s3: { authMethod: "origin-access-identity", domainName: "my-bucket.s3.amazonaws.com", path: "/basepath/static-pages/build-id", region: "us-east-1" } }); expect(request.uri).toEqual("/404.html"); expect(request.headers.host[0].key).toEqual("host"); expect(request.headers.host[0].value).toEqual( "my-bucket.s3.amazonaws.com" ); } ); it.each` path ${"/basepath/_next/data/unmatched"} `( "returns 404 page if data request can't be matched for path: $path", async ({ path }) => { const event = createCloudFrontEvent({ uri: path, origin: { s3: { domainName: "my-bucket.s3.amazonaws.com" } }, config: { eventType: "origin-request" } as any }); const request = (await handler(event)) as CloudFrontRequest; expect(request.uri).toEqual("/404.html"); } ); it("404.html should return 404 status after successful S3 Origin response", async () => { const event = createCloudFrontEvent({ uri: "/404.html", host: "mydistribution.cloudfront.net", config: { eventType: "origin-response" } as any, response: { status: "200" } as any }); const response = (await handler(event)) as CloudFrontResultResponse; expect(response.status).toEqual("404"); }); }); describe("500 page", () => { it("renders 500 page if page render has an error", async () => { const event = createCloudFrontEvent({ uri: "/basepath/erroredPage", host: "mydistribution.cloudfront.net" }); mockPageRequire("pages/_error.js"); mockPageRequire("pages/erroredPage.js"); const response = (await handler(event)) as CloudFrontResultResponse; const body = response.body as string; const decodedBody = Buffer.from(body, "base64").toString("utf8"); expect(decodedBody).toEqual("pages/_error.js - 500"); expect(response.status).toEqual("500"); }); }); });