Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(nextjs-component): experimental - allow serving API pages from d…
…efault lambda (#1632)
- Loading branch information
Showing
53 changed files
with
8,119 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
cypress/videos | ||
cypress/screenshots |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"baseUrl": "http://localhost:3000", | ||
"supportFile": "cypress/support/index.ts", | ||
"responseTimeout": 15000, | ||
"requestTimeout": 15000, | ||
"experimentalFetchPolyfill": true, | ||
"retries": 4, | ||
"video": false | ||
} |
72 changes: 72 additions & 0 deletions
72
packages/e2e-tests/next-app-experimental/cypress/integration/api-routes.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
describe("API Routes Tests", () => { | ||
before(() => { | ||
cy.ensureAllRoutesNotErrored(); | ||
}); | ||
|
||
describe("Basic API", () => { | ||
const path = "/api/basic-api"; | ||
|
||
["DELETE", "POST", "GET", "PUT", "PATCH", "OPTIONS", "HEAD"].forEach( | ||
(method) => { | ||
it(`serves API request for path ${path} and method ${method}`, () => { | ||
cy.request({ url: path, method: method }).then((response) => { | ||
expect(response.status).to.equal(200); | ||
cy.verifyResponseCacheStatus(response, false); | ||
|
||
if (method === "HEAD") { | ||
expect(response.body).to.be.empty; | ||
} else { | ||
expect(response.body).to.deep.equal({ | ||
name: "This is a basic API route.", | ||
method: method | ||
}); | ||
} | ||
}); | ||
}); | ||
} | ||
); | ||
}); | ||
|
||
describe("Dynamic + Nested API", () => { | ||
const base = "api/nested/"; | ||
|
||
["DELETE", "POST", "GET", "PUT", "PATCH", "OPTIONS", "HEAD"].forEach( | ||
(method) => { | ||
const id = "1"; | ||
const path = base + id; | ||
|
||
it(`serves API request for path ${path} and method ${method}`, () => { | ||
cy.request({ url: path, method: method }).then((response) => { | ||
expect(response.status).to.equal(200); | ||
cy.verifyResponseCacheStatus(response, false); | ||
|
||
if (method === "HEAD") { | ||
expect(response.body).to.be.empty; | ||
} else { | ||
expect(response.body).to.deep.equal({ | ||
id: id, | ||
name: `User ${id}`, | ||
method: method | ||
}); | ||
} | ||
}); | ||
}); | ||
} | ||
); | ||
|
||
["1", "2", "3", "4", "5"].forEach((id) => { | ||
const path = base + id; | ||
it(`serves API request for path ${path} for different IDs`, () => { | ||
cy.request({ url: path, method: "GET" }).then((response) => { | ||
expect(response.status).to.equal(200); | ||
expect(response.body).to.deep.equal({ | ||
id: id, | ||
name: `User ${id}`, | ||
method: "GET" | ||
}); | ||
cy.verifyResponseCacheStatus(response, false); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
89 changes: 89 additions & 0 deletions
89
packages/e2e-tests/next-app-experimental/cypress/integration/data-requests.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
describe("Data Requests", () => { | ||
const buildId = Cypress.env("NEXT_BUILD_ID"); | ||
|
||
describe("SSG data requests", () => { | ||
[{ path: "/ssg-page.json" }, { path: "/index.json" }].forEach( | ||
({ path }) => { | ||
const fullPath = `/_next/data/${buildId}${path}`; | ||
|
||
it(`serves the SSG data request for path ${fullPath}`, () => { | ||
// Hit two times, and check that the response should definitely be cached after 2nd time | ||
for (let i = 0; i < 2; i++) { | ||
cy.request(fullPath).then((response) => { | ||
expect(response.status).to.equal(200); | ||
expect(response.headers["cache-control"]).to.not.be.undefined; | ||
|
||
if (i === 1) { | ||
cy.verifyResponseCacheStatus(response, true); | ||
} else { | ||
expect(response.headers["x-cache"]).to.be.oneOf([ | ||
"Miss from cloudfront", | ||
"Hit from cloudfront" | ||
]); | ||
} | ||
}); | ||
} | ||
}); | ||
|
||
["HEAD", "GET"].forEach((method) => { | ||
it(`allows HTTP method for path ${fullPath}: ${method}`, () => { | ||
cy.request({ url: fullPath, method: method }).then((response) => { | ||
expect(response.status).to.equal(200); | ||
}); | ||
}); | ||
}); | ||
|
||
["DELETE", "POST", "OPTIONS", "PUT", "PATCH"].forEach((method) => { | ||
it(`disallows HTTP method for path ${fullPath} with 4xx error: ${method}`, () => { | ||
cy.request({ | ||
url: fullPath, | ||
method: method, | ||
failOnStatusCode: false | ||
}).then((response) => { | ||
expect(response.status).to.be.at.least(400); | ||
expect(response.status).to.be.lessThan(500); | ||
}); | ||
}); | ||
}); | ||
} | ||
); | ||
}); | ||
|
||
describe("SSR data requests", () => { | ||
[{ path: "/ssr-page-2.json" }].forEach(({ path }) => { | ||
const fullPath = `/_next/data/${buildId}${path}`; | ||
|
||
it(`serves the SSR data request for path ${fullPath}`, () => { | ||
// Hit two times, both of which, the response should not be cached | ||
for (let i = 0; i < 2; i++) { | ||
cy.request(fullPath).then((response) => { | ||
expect(response.status).to.equal(200); | ||
cy.verifyResponseCacheStatus(response, false); | ||
expect(response.headers["cache-control"]).to.be.undefined; | ||
}); | ||
} | ||
}); | ||
|
||
["HEAD", "GET"].forEach((method) => { | ||
it(`allows HTTP method for path ${fullPath}: ${method}`, () => { | ||
cy.request({ url: fullPath, method: method }).then((response) => { | ||
expect(response.status).to.equal(200); | ||
}); | ||
}); | ||
}); | ||
|
||
["DELETE", "POST", "OPTIONS", "PUT", "PATCH"].forEach((method) => { | ||
it(`disallows HTTP method for path ${fullPath} with 4xx error: ${method}`, () => { | ||
cy.request({ | ||
url: fullPath, | ||
method: method, | ||
failOnStatusCode: false | ||
}).then((response) => { | ||
expect(response.status).to.be.at.least(400); | ||
expect(response.status).to.be.lessThan(500); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
45 changes: 45 additions & 0 deletions
45
packages/e2e-tests/next-app-experimental/cypress/integration/headers.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
describe("Headers Tests", () => { | ||
describe("Custom headers defined in next.config.js", () => { | ||
[ | ||
{ | ||
path: "/ssr-page", | ||
expectedHeaders: { "x-custom-header-ssr-page": "custom" } | ||
}, | ||
{ | ||
path: "/ssg-page", | ||
expectedHeaders: { "x-custom-header-ssg-page": "custom" } | ||
}, | ||
{ | ||
path: "/", | ||
expectedHeaders: { "x-custom-header-all": "custom" } | ||
}, | ||
{ | ||
path: "/not-found", | ||
expectedHeaders: { "x-custom-header-all": "custom" } | ||
}, | ||
{ | ||
path: "/api/basic-api", | ||
expectedHeaders: { "x-custom-header-api": "custom" } | ||
}, | ||
{ | ||
path: "/app-store-badge.png", | ||
expectedHeaders: { "x-custom-header-public-file": "custom" } | ||
} | ||
].forEach(({ path, expectedHeaders }) => { | ||
it(`add headers ${JSON.stringify( | ||
expectedHeaders | ||
)} for path ${path}`, () => { | ||
cy.request({ | ||
url: path, | ||
failOnStatusCode: false | ||
}).then((response) => { | ||
for (const expectedHeader in expectedHeaders) { | ||
expect(response.headers[expectedHeader]).to.equal( | ||
expectedHeaders[expectedHeader] | ||
); | ||
} | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
106 changes: 106 additions & 0 deletions
106
packages/e2e-tests/next-app-experimental/cypress/integration/image-optimizer.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
describe("Image Optimizer Tests", () => { | ||
describe("image optimization", () => { | ||
[{ contentType: "image/webp" }, { contentType: "image/png" }].forEach( | ||
({ contentType }) => { | ||
it(`serves image app-store-badge.png with content-type: ${contentType}`, () => { | ||
cy.request({ | ||
url: "/_next/image?url=%2Fapp-store-badge.png&w=256&q=100", | ||
method: "GET", | ||
headers: { accept: contentType } | ||
}).then((response) => { | ||
// TODO: not sure why this is failing in CI | ||
//expect(response.headers["content-type"]).to.equal(contentType); | ||
expect(response.headers["cache-control"]).to.equal( | ||
"public, max-age=31536000, must-revalidate" | ||
); | ||
}); | ||
}); | ||
} | ||
); | ||
|
||
// Higher quality should have higher file size | ||
[ | ||
{ quality: "100", expectedContentLength: "5742" }, | ||
{ quality: "50", expectedContentLength: "2654" } | ||
].forEach(({ quality, expectedContentLength }) => { | ||
it(`serves image app-store-badge.png with quality: ${quality}`, () => { | ||
cy.request({ | ||
url: `/_next/image?url=%2Fapp-store-badge.png&w=256&q=${quality}`, | ||
method: "GET", | ||
headers: { accept: "image/webp" } | ||
}).then((response) => { | ||
// TODO: not sure why this is failing in CI | ||
// expect(response.headers["content-length"]).to.equal( | ||
// expectedContentLength | ||
// ); | ||
expect(response.headers["cache-control"]).to.equal( | ||
"public, max-age=31536000, must-revalidate" | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
// Higher width should have higher file size | ||
[ | ||
{ width: "128", expectedContentLength: "2600" }, | ||
{ width: "64", expectedContentLength: "1192" } | ||
].forEach(({ width, expectedContentLength }) => { | ||
it(`serves image app-store-badge.png with width: ${width}`, () => { | ||
cy.request({ | ||
url: `/_next/image?url=%2Fapp-store-badge.png&w=${width}&q=100`, | ||
method: "GET", | ||
headers: { accept: "image/webp" } | ||
}).then((response) => { | ||
// TODO: not sure why this is failing in CI | ||
// expect(response.headers["content-length"]).to.equal( | ||
// expectedContentLength | ||
// ); | ||
expect(response.headers["cache-control"]).to.equal( | ||
"public, max-age=31536000, must-revalidate" | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
[ | ||
{ | ||
path: "/_next/image?url=https%3A%2F%2Fraw.githubusercontent.com%2Fserverless-nextjs%2Fserverless-next.js%2Fmaster%2Fpackages%2Fe2e-tests%2Fnext-app-experimental%2Fpublic%2Fapp-store-badge.png&q=100&w=128" | ||
} | ||
].forEach(({ path }) => { | ||
it(`serves external image: ${path}`, () => { | ||
cy.request({ url: path, method: "GET" }); | ||
}); | ||
}); | ||
|
||
[ | ||
{ path: "/_next/image" }, | ||
{ path: "/_next/image?w=256&q=100" }, | ||
{ path: "/_next/image?url=%2Fapp-store-badge.png&w=256" }, | ||
{ path: "/_next/image?url=%2Fapp-store-badge.png&q=100" } | ||
].forEach(({ path }) => { | ||
it(`missing query parameter fails with 400 status code: ${path}`, () => { | ||
cy.request({ url: path, method: "GET", failOnStatusCode: false }).then( | ||
(response) => { | ||
expect(response.status).to.equal(400); | ||
} | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
describe("image component page", () => { | ||
[{ path: "/image-component" }].forEach(({ path }) => { | ||
it(`serves page with image component and caches the image: ${path}`, () => { | ||
cy.ensureAllRoutesNotErrored(); // Visit routes only | ||
|
||
cy.visit(path); | ||
|
||
cy.ensureRouteCached( | ||
"/_next/image?url=%2Fapp-store-badge.png&w=1200&q=75" | ||
); | ||
|
||
cy.visit(path); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.