diff --git a/packages/next-aws-lambda/__tests__/index.test.js b/packages/next-aws-lambda/__tests__/index.test.js index 89a73ddeb3..2221fc36dc 100644 --- a/packages/next-aws-lambda/__tests__/index.test.js +++ b/packages/next-aws-lambda/__tests__/index.test.js @@ -9,8 +9,13 @@ describe("next-aws-lambda", () => { const callback = () => {}; const context = {}; + // Mock due to mismatched Function types + // https://github.com/facebook/jest/issues/6329 + mockRender = jest.fn(); + mockDefault = jest.fn(); const page = { - render: jest.fn() + render: (...args) => mockRender(...args), + default: (...args) => mockDefault(...args) }; const req = {}; const res = {}; @@ -22,6 +27,33 @@ describe("next-aws-lambda", () => { compat(page)(event, context, callback); - expect(page.render).toBeCalledWith(req, res); + expect(mockRender).toBeCalledWith(req, res); + expect(mockDefault).not.toBeCalled(); + }); +}); + +describe("next-aws-lambda", () => { + it("passes request and response to next api", () => { + const event = { foo: "bar" }; + const callback = () => {}; + const context = {}; + + // Mock due to mismatched Function types + // https://github.com/facebook/jest/issues/6329 + mockDefault = jest.fn(); + const page = { + default: (...args) => mockDefault(...args) + }; + const req = {}; + const res = {}; + + compatLayer.mockReturnValueOnce({ + req, + res + }); + + compat(page)(event, context, callback); + + expect(mockDefault).toBeCalledWith(req, res); }); }); diff --git a/packages/next-aws-lambda/index.js b/packages/next-aws-lambda/index.js index 800f4b1d25..6d13cefe36 100644 --- a/packages/next-aws-lambda/index.js +++ b/packages/next-aws-lambda/index.js @@ -2,7 +2,13 @@ const reqResMapper = require("./lib/compatLayer"); const handlerFactory = page => (event, _context, callback) => { const { req, res } = reqResMapper(event, callback); - page.render(req, res); + if (page.render instanceof Function) { + // Is a React component + page.render(req, res); + } else { + // Is an API + page.default(req, res); + } }; module.exports = handlerFactory; diff --git a/packages/next-aws-lambda/lib/__tests__/compatLayer.request.test.js b/packages/next-aws-lambda/lib/__tests__/compatLayer.request.test.js index ff09fe3111..fdd9fe50ad 100644 --- a/packages/next-aws-lambda/lib/__tests__/compatLayer.request.test.js +++ b/packages/next-aws-lambda/lib/__tests__/compatLayer.request.test.js @@ -190,29 +190,6 @@ describe("compatLayer.request", () => { ]); }); - it("stream", done => { - const { req } = create({ - requestContext: { - path: "" - }, - headers: {} - }); - - let data = ""; - - req.on("data", chunk => { - data += chunk; - }); - - req.on("end", () => { - expect(data).toEqual("ok"); - done(); - }); - - req.push("ok"); - req.push(null); - }); - it("text body", done => { const { req } = create({ requestContext: { diff --git a/packages/next-aws-lambda/lib/__tests__/compatLayer.response.test.js b/packages/next-aws-lambda/lib/__tests__/compatLayer.response.test.js index 404f8b1cbf..5da061aa26 100644 --- a/packages/next-aws-lambda/lib/__tests__/compatLayer.response.test.js +++ b/packages/next-aws-lambda/lib/__tests__/compatLayer.response.test.js @@ -195,9 +195,8 @@ describe("compatLayer.response", () => { done(); } ); - req.pipe(res); - req.push("ok"); - req.push(null); + + res.end("ok"); }); it("base64 support", done => { diff --git a/packages/next-aws-lambda/lib/compatLayer.js b/packages/next-aws-lambda/lib/compatLayer.js index c6416c4744..7d19fa698d 100644 --- a/packages/next-aws-lambda/lib/compatLayer.js +++ b/packages/next-aws-lambda/lib/compatLayer.js @@ -105,8 +105,8 @@ const reqResMapper = (event, callback) => { }; if (event.body) { req.push(event.body, event.isBase64Encoded ? "base64" : undefined); - req.push(null); } + req.push(null); function fixApiGatewayMultipleHeaders() { for (const key of Object.keys(response.multiValueHeaders)) { diff --git a/packages/serverless-nextjs-plugin/__tests__/fixtures/single-api/.next/serverless/pages/api/api.js b/packages/serverless-nextjs-plugin/__tests__/fixtures/single-api/.next/serverless/pages/api/api.js new file mode 100644 index 0000000000..9b086739cb --- /dev/null +++ b/packages/serverless-nextjs-plugin/__tests__/fixtures/single-api/.next/serverless/pages/api/api.js @@ -0,0 +1 @@ +export default (req, res) => {{ test: 'test' }} \ No newline at end of file diff --git a/packages/serverless-nextjs-plugin/__tests__/fixtures/single-api/serverless.yml b/packages/serverless-nextjs-plugin/__tests__/fixtures/single-api/serverless.yml new file mode 100644 index 0000000000..785b032cdd --- /dev/null +++ b/packages/serverless-nextjs-plugin/__tests__/fixtures/single-api/serverless.yml @@ -0,0 +1,18 @@ + +service: single-api-fixture + +provider: + name: aws + runtime: nodejs8.10 + +stage: dev +region: eu-west-1 + +plugins: + - index # this works because jest modulePaths adds plugin path, see package.json + +package: + # exclude everything + # page handlers are automatically included by the plugin + exclude: + - ./** diff --git a/packages/serverless-nextjs-plugin/__tests__/single-api.test.js b/packages/serverless-nextjs-plugin/__tests__/single-api.test.js new file mode 100644 index 0000000000..9a64eff6e9 --- /dev/null +++ b/packages/serverless-nextjs-plugin/__tests__/single-api.test.js @@ -0,0 +1,111 @@ +const nextBuild = require("next/dist/build"); +const path = require("path"); +const AdmZip = require("adm-zip"); +const readCloudFormationUpdateTemplate = require("../utils/test/readCloudFormationUpdateTemplate"); +const testableServerless = require("../utils/test/testableServerless"); + +jest.mock("next/dist/build"); + +describe("single api", () => { + const fixturePath = path.join(__dirname, "./fixtures/single-api"); + + let cloudFormationUpdateResources; + + beforeAll(async () => { + nextBuild.default.mockResolvedValue(); + + await testableServerless(fixturePath, "package"); + + const cloudFormationUpdateTemplate = await readCloudFormationUpdateTemplate( + fixturePath + ); + + cloudFormationUpdateResources = cloudFormationUpdateTemplate.Resources; + }); + + describe("Page lambda function", () => { + let pageLambda; + + beforeAll(() => { + pageLambda = cloudFormationUpdateResources.ApiDashapiLambdaFunction; + }); + + it("creates lambda resource", () => { + expect(pageLambda).toBeDefined(); + }); + + it("has correct handler", () => { + expect(pageLambda.Properties.Handler).toEqual( + "sls-next-build/api/api.render" + ); + }); + }); + + describe("Api Gateway", () => { + let apiGateway; + + beforeAll(() => { + apiGateway = cloudFormationUpdateResources.ApiGatewayRestApi; + }); + + it("creates api resource", () => { + expect(apiGateway).toBeDefined(); + }); + + describe("Page route", () => { + it("creates page route resource with correct path", () => { + const apiResource = cloudFormationUpdateResources.ApiGatewayResourceApi; + + const apiPostResource = + cloudFormationUpdateResources.ApiGatewayResourceApiApi; + + expect(apiResource).toBeDefined(); + expect(apiPostResource).toBeDefined(); + expect(apiResource.Properties.PathPart).toEqual("api"); + expect(apiPostResource.Properties.PathPart).toEqual("api"); + }); + + it("creates GET http method", () => { + const httpMethod = + cloudFormationUpdateResources.ApiGatewayMethodApiApiGet; + + expect(httpMethod).toBeDefined(); + expect(httpMethod.Properties.HttpMethod).toEqual("GET"); + expect(httpMethod.Properties.ResourceId.Ref).toEqual( + "ApiGatewayResourceApiApi" + ); + }); + + it("creates HEAD http method", () => { + const httpMethod = + cloudFormationUpdateResources.ApiGatewayMethodApiApiHead; + + expect(httpMethod).toBeDefined(); + expect(httpMethod.Properties.HttpMethod).toEqual("HEAD"); + expect(httpMethod.Properties.ResourceId.Ref).toEqual( + "ApiGatewayResourceApiApi" + ); + }); + }); + }); + + describe("Zip artifact", () => { + let zipEntryNames; + + beforeAll(() => { + const zip = new AdmZip( + `${fixturePath}/.serverless/single-api-fixture.zip` + ); + const zipEntries = zip.getEntries(); + zipEntryNames = zipEntries.map(ze => ze.entryName); + }); + + it("contains next compiled page", () => { + expect(zipEntryNames).toContain(`sls-next-build/api/api.original.js`); + }); + + it("contains plugin handler", () => { + expect(zipEntryNames).toContain(`sls-next-build/api/api.js`); + }); + }); +}); diff --git a/packages/serverless-nextjs-plugin/resources/cloudfront.yml b/packages/serverless-nextjs-plugin/resources/cloudfront.yml index 582efa4c39..b77635e377 100644 --- a/packages/serverless-nextjs-plugin/resources/cloudfront.yml +++ b/packages/serverless-nextjs-plugin/resources/cloudfront.yml @@ -30,6 +30,10 @@ Resources: - GET - HEAD - OPTIONS + - PUT + - PATCH + - POST + - DELETE TargetOriginId: ApiGatewayOrigin Compress: "true" ForwardedValues: