diff --git a/packages/integrations/node/CHANGELOG.md b/packages/integrations/node/CHANGELOG.md index 01e61307f9..95b8e8ad18 100644 --- a/packages/integrations/node/CHANGELOG.md +++ b/packages/integrations/node/CHANGELOG.md @@ -39,6 +39,15 @@ - Updated dependencies [[`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81), [`76ddef19c`](https://github.com/withastro/astro/commit/76ddef19ccab6e5f7d3a5740cd41acf10e334b38), [`9b4f70a62`](https://github.com/withastro/astro/commit/9b4f70a629f55e461759ba46f68af7097a2e9215), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`2f951cd40`](https://github.com/withastro/astro/commit/2f951cd403dfcc2c3ca6aae618ae3e1409516e32), [`c022a4217`](https://github.com/withastro/astro/commit/c022a4217a805d223c1494e9eda4e48bbf810388), [`67becaa58`](https://github.com/withastro/astro/commit/67becaa580b8f787df58de66b7008b7098f1209c), [`bc37331d8`](https://github.com/withastro/astro/commit/bc37331d8154e3e95a8df9131e4e014e78a7a9e7), [`dfc2d93e3`](https://github.com/withastro/astro/commit/dfc2d93e3c645995379358fabbdfa9aab99f43d8), [`3dc1ca2fa`](https://github.com/withastro/astro/commit/3dc1ca2fac8d9965cc5085a5d09e72ed87b4281a), [`1be84dfee`](https://github.com/withastro/astro/commit/1be84dfee3ce8e6f5cc624f99aec4e980f6fde37), [`35f01df79`](https://github.com/withastro/astro/commit/35f01df797d23315f2bee2fc3fd795adb0559c58), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`78de801f2`](https://github.com/withastro/astro/commit/78de801f21fd4ca1653950027d953bf08614566b), [`59d6e569f`](https://github.com/withastro/astro/commit/59d6e569f63e175c97e82e94aa7974febfb76f7c), [`7723c4cc9`](https://github.com/withastro/astro/commit/7723c4cc93298c2e6530e55da7afda048f22cf81), [`fb5cd6b56`](https://github.com/withastro/astro/commit/fb5cd6b56dc27a71366ed5e1ab8bfe9b8f96bac5), [`631b9c410`](https://github.com/withastro/astro/commit/631b9c410d5d66fa384674027ba95d69ebb5063f)]: - astro@3.0.0-beta.0 +## 5.3.4 + +### Patch Changes + +- [#8084](https://github.com/withastro/astro/pull/8084) [`560e45924`](https://github.com/withastro/astro/commit/560e45924622141206ff5b47d134cb343d6d2a71) Thanks [@hbgl](https://github.com/hbgl)! - Stream request body instead of buffering it in memory. + +- Updated dependencies [[`c19987df0`](https://github.com/withastro/astro/commit/c19987df0be3520cf774476cea270c03edd08354), [`560e45924`](https://github.com/withastro/astro/commit/560e45924622141206ff5b47d134cb343d6d2a71), [`afc45af20`](https://github.com/withastro/astro/commit/afc45af2022f7c43fbb6c5c04983695f3819e47e), [`d1f7143f9`](https://github.com/withastro/astro/commit/d1f7143f9caf2ffa0e87cc55c0e05339d3501db3), [`3e46634fd`](https://github.com/withastro/astro/commit/3e46634fd540e5b967d2e5c9abd6235452cee2f2), [`a12027b6a`](https://github.com/withastro/astro/commit/a12027b6af411be39700919ca47e240a335e9887)]: + - astro@2.10.8 + ## 5.3.3 ### Patch Changes diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json index 999ce294ac..c3ef496377 100644 --- a/packages/integrations/node/package.json +++ b/packages/integrations/node/package.json @@ -48,6 +48,7 @@ "chai": "^4.3.7", "cheerio": "1.0.0-rc.12", "mocha": "^9.2.2", - "node-mocks-http": "^1.12.2" + "node-mocks-http": "^1.13.0", + "undici": "^5.22.1" } } diff --git a/packages/integrations/node/test/api-route.test.js b/packages/integrations/node/test/api-route.test.js index 7fbd957763..c830eee2d6 100644 --- a/packages/integrations/node/test/api-route.test.js +++ b/packages/integrations/node/test/api-route.test.js @@ -1,6 +1,7 @@ import nodejs from '../dist/index.js'; import { loadFixture, createRequestAndResponse } from './test-utils.js'; import { expect } from 'chai'; +import crypto from 'node:crypto'; describe('API routes', () => { /** @type {import('./test-utils').Fixture} */ @@ -22,9 +23,11 @@ describe('API routes', () => { url: '/recipes', }); - handler(req, res); + req.once('async_iterator', () => { + req.send(JSON.stringify({ id: 2 })); + }); - req.send(JSON.stringify({ id: 2 })); + handler(req, res); let [buffer] = await done; @@ -43,11 +46,47 @@ describe('API routes', () => { url: '/binary', }); + req.once('async_iterator', () => { + req.send(Buffer.from(new Uint8Array([1, 2, 3, 4, 5]))); + }); + handler(req, res); - req.send(Buffer.from(new Uint8Array([1, 2, 3, 4, 5]))); let [out] = await done; let arr = Array.from(new Uint8Array(out.buffer)); expect(arr).to.deep.equal([5, 4, 3, 2, 1]); }); + + it('Can post large binary data', async () => { + const { handler } = await import('./fixtures/api-route/dist/server/entry.mjs'); + + let { req, res, done } = createRequestAndResponse({ + method: 'POST', + url: '/hash', + }); + + handler(req, res); + + let expectedDigest = null; + req.once('async_iterator', () => { + // Send 256MB of garbage data in 256KB chunks. This should be fast (< 1sec). + let remainingBytes = 256 * 1024 * 1024; + const chunkSize = 256 * 1024; + + const hash = crypto.createHash('sha256'); + while (remainingBytes > 0) { + const size = Math.min(remainingBytes, chunkSize); + const chunk = Buffer.alloc(size, Math.floor(Math.random() * 256)); + hash.update(chunk); + req.emit('data', chunk); + remainingBytes -= size; + } + + req.emit('end'); + expectedDigest = hash.digest(); + }); + + let [out] = await done; + expect(new Uint8Array(out.buffer)).to.deep.equal(expectedDigest); + }); }); diff --git a/packages/integrations/node/test/fixtures/api-route/src/pages/hash.ts b/packages/integrations/node/test/fixtures/api-route/src/pages/hash.ts new file mode 100644 index 0000000000..fbf44c5478 --- /dev/null +++ b/packages/integrations/node/test/fixtures/api-route/src/pages/hash.ts @@ -0,0 +1,16 @@ +import crypto from 'node:crypto'; + +export async function post({ request }: { request: Request }) { + const hash = crypto.createHash('sha256'); + + const iterable = request.body as unknown as AsyncIterable; + for await (const chunk of iterable) { + hash.update(chunk); + } + + return new Response(hash.digest(), { + headers: { + 'Content-Type': 'application/octet-stream' + } + }); +} diff --git a/packages/integrations/node/test/fixtures/prerender-404/package.json b/packages/integrations/node/test/fixtures/prerender-404-500/package.json similarity index 67% rename from packages/integrations/node/test/fixtures/prerender-404/package.json rename to packages/integrations/node/test/fixtures/prerender-404-500/package.json index dfd109c914..f962fe991e 100644 --- a/packages/integrations/node/test/fixtures/prerender-404/package.json +++ b/packages/integrations/node/test/fixtures/prerender-404-500/package.json @@ -1,7 +1,8 @@ { - "name": "@test/nodejs-prerender-404", + "name": "@test/nodejs-prerender-404-500", "version": "0.0.0", "private": true, + "type": "module", "dependencies": { "astro": "workspace:*", "@astrojs/node": "workspace:*" diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/external-stylesheet.css b/packages/integrations/node/test/fixtures/prerender-404-500/src/external-stylesheet.css new file mode 100644 index 0000000000..5f331948a6 --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/external-stylesheet.css @@ -0,0 +1,3 @@ +body { + background-color: ivory; +} diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-404.ts b/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-404.ts new file mode 100644 index 0000000000..1795c26b07 --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-404.ts @@ -0,0 +1,17 @@ +// This module is only used by the prerendered 404.astro. +// It exhibits different behavior if it's called more than once, +// which is detected by a test and interpreted as a failure. + +let usedOnce = false +let dynamicMessage = "Page was not prerendered" + +export default function () { + if (usedOnce === false) { + usedOnce = true + return "Page does not exist" + } + + dynamicMessage += "+" + + return dynamicMessage +} diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-500.ts b/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-500.ts new file mode 100644 index 0000000000..8f8024a608 --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/nondeterminism-500.ts @@ -0,0 +1,17 @@ +// This module is only used by the prerendered 500.astro. +// It exhibits different behavior if it's called more than once, +// which is detected by a test and interpreted as a failure. + +let usedOnce = false +let dynamicMessage = "Page was not prerendered" + +export default function () { + if (usedOnce === false) { + usedOnce = true + return "Something went wrong" + } + + dynamicMessage += "+" + + return dynamicMessage +} diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/404.astro b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/404.astro new file mode 100644 index 0000000000..37fd1c1d39 --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/404.astro @@ -0,0 +1,5 @@ +--- +import message from "../nondeterminism-404" +export const prerender = true; +--- +{message()} diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/500.astro b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/500.astro new file mode 100644 index 0000000000..ef91ad0ffa --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/500.astro @@ -0,0 +1,6 @@ +--- +import "../external-stylesheet.css" +import message from "../nondeterminism-500" +export const prerender = true +--- +

{message()}

diff --git a/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/fivehundred.astro b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/fivehundred.astro new file mode 100644 index 0000000000..99d103567d --- /dev/null +++ b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/fivehundred.astro @@ -0,0 +1,4 @@ +--- +return new Response(null, { status: 500 }) +--- +

This html will not be served

diff --git a/packages/integrations/node/test/fixtures/prerender-404/src/pages/static.astro b/packages/integrations/node/test/fixtures/prerender-404-500/src/pages/static.astro similarity index 100% rename from packages/integrations/node/test/fixtures/prerender-404/src/pages/static.astro rename to packages/integrations/node/test/fixtures/prerender-404-500/src/pages/static.astro diff --git a/packages/integrations/node/test/fixtures/prerender-404/src/pages/404.astro b/packages/integrations/node/test/fixtures/prerender-404/src/pages/404.astro deleted file mode 100644 index 230402bbcc..0000000000 --- a/packages/integrations/node/test/fixtures/prerender-404/src/pages/404.astro +++ /dev/null @@ -1,5 +0,0 @@ ---- -export const prerender = true; ---- - -Page does not exist diff --git a/packages/integrations/node/test/prerender-404.test.js b/packages/integrations/node/test/prerender-404-500.test.js similarity index 50% rename from packages/integrations/node/test/prerender-404.test.js rename to packages/integrations/node/test/prerender-404-500.test.js index 3a39a94705..8816ebe4c1 100644 --- a/packages/integrations/node/test/prerender-404.test.js +++ b/packages/integrations/node/test/prerender-404-500.test.js @@ -9,10 +9,11 @@ import * as cheerio from 'cheerio'; async function load() { const mod = await import( - `./fixtures/prerender-404/dist/server/entry.mjs?dropcache=${Date.now()}` + `./fixtures/prerender-404-500/dist/server/entry.mjs?dropcache=${Date.now()}` ); return mod; } + describe('Prerender 404', () => { /** @type {import('./test-utils').Fixture} */ let fixture; @@ -24,8 +25,12 @@ describe('Prerender 404', () => { process.env.PRERENDER = true; fixture = await loadFixture({ + // inconsequential config that differs between tests + // to bust cache and prevent modules and their state + // from being reused + site: 'https://test.dev/', base: '/some-base', - root: './fixtures/prerender-404/', + root: './fixtures/prerender-404-500/', output: 'server', adapter: nodejs({ mode: 'standalone' }), }); @@ -51,12 +56,52 @@ describe('Prerender 404', () => { }); it('Can handle prerendered 404', async () => { - const res = await fetch(`http://${server.host}:${server.port}/some-base/missing`); - const html = await res.text(); + const url = `http://${server.host}:${server.port}/some-base/missing`; + const res1 = await fetch(url); + const res2 = await fetch(url); + const res3 = await fetch(url); + + expect(res1.status).to.equal(404); + expect(res2.status).to.equal(404); + expect(res3.status).to.equal(404); + + const html1 = await res1.text(); + const html2 = await res2.text(); + const html3 = await res3.text(); + + expect(html1).to.equal(html2); + expect(html2).to.equal(html3); + + const $ = cheerio.load(html1); + + expect($('body').text()).to.equal('Page does not exist'); + }); + + it(' Can handle prerendered 500 called indirectly', async () => { + const url = `http://${server.host}:${server.port}/some-base/fivehundred`; + const response1 = await fetch(url); + const response2 = await fetch(url); + const response3 = await fetch(url); + + expect(response1.status).to.equal(500); + + const html1 = await response1.text(); + const html2 = await response2.text(); + const html3 = await response3.text(); + + expect(html1).to.contain('Something went wrong'); + + expect(html1).to.equal(html2); + expect(html2).to.equal(html3); + }); + + it('prerendered 500 page includes expected styles', async () => { + const response = await fetch(`http://${server.host}:${server.port}/some-base/fivehundred`); + const html = await response.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(404); - expect($('body').text()).to.equal('Page does not exist'); + // length will be 0 if the stylesheet does not get included + expect($('link[rel=stylesheet]')).to.have.a.lengthOf(1); }); }); @@ -66,12 +111,16 @@ describe('Prerender 404', () => { process.env.PRERENDER = true; fixture = await loadFixture({ - root: './fixtures/prerender-404/', + // inconsequential config that differs between tests + // to bust cache and prevent modules and their state + // from being reused + site: 'https://test.info/', + root: './fixtures/prerender-404-500/', output: 'server', adapter: nodejs({ mode: 'standalone' }), }); await fixture.build(); - const { startServer } = await await load(); + const { startServer } = await load(); let res = startServer(); server = res.server; }); @@ -92,11 +141,24 @@ describe('Prerender 404', () => { }); it('Can handle prerendered 404', async () => { - const res = await fetch(`http://${server.host}:${server.port}/missing`); - const html = await res.text(); - const $ = cheerio.load(html); + const url = `http://${server.host}:${server.port}/some-base/missing`; + const res1 = await fetch(url); + const res2 = await fetch(url); + const res3 = await fetch(url); + + expect(res1.status).to.equal(404); + expect(res2.status).to.equal(404); + expect(res3.status).to.equal(404); + + const html1 = await res1.text(); + const html2 = await res2.text(); + const html3 = await res3.text(); + + expect(html1).to.equal(html2); + expect(html2).to.equal(html3); + + const $ = cheerio.load(html1); - expect(res.status).to.equal(404); expect($('body').text()).to.equal('Page does not exist'); }); }); @@ -112,13 +174,17 @@ describe('Hybrid 404', () => { process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = false; fixture = await loadFixture({ + // inconsequential config that differs between tests + // to bust cache and prevent modules and their state + // from being reused + site: 'https://test.com/', base: '/some-base', - root: './fixtures/prerender-404/', + root: './fixtures/prerender-404-500/', output: 'hybrid', adapter: nodejs({ mode: 'standalone' }), }); await fixture.build(); - const { startServer } = await await load(); + const { startServer } = await load(); let res = startServer(); server = res.server; }); @@ -139,11 +205,24 @@ describe('Hybrid 404', () => { }); it('Can handle prerendered 404', async () => { - const res = await fetch(`http://${server.host}:${server.port}/some-base/missing`); - const html = await res.text(); - const $ = cheerio.load(html); + const url = `http://${server.host}:${server.port}/some-base/missing`; + const res1 = await fetch(url); + const res2 = await fetch(url); + const res3 = await fetch(url); + + expect(res1.status).to.equal(404); + expect(res2.status).to.equal(404); + expect(res3.status).to.equal(404); + + const html1 = await res1.text(); + const html2 = await res2.text(); + const html3 = await res3.text(); + + expect(html1).to.equal(html2); + expect(html2).to.equal(html3); + + const $ = cheerio.load(html1); - expect(res.status).to.equal(404); expect($('body').text()).to.equal('Page does not exist'); }); }); @@ -153,12 +232,16 @@ describe('Hybrid 404', () => { process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = false; fixture = await loadFixture({ - root: './fixtures/prerender-404/', + // inconsequential config that differs between tests + // to bust cache and prevent modules and their state + // from being reused + site: 'https://test.net/', + root: './fixtures/prerender-404-500/', output: 'hybrid', adapter: nodejs({ mode: 'standalone' }), }); await fixture.build(); - const { startServer } = await await load(); + const { startServer } = await load(); let res = startServer(); server = res.server; }); @@ -179,11 +262,24 @@ describe('Hybrid 404', () => { }); it('Can handle prerendered 404', async () => { - const res = await fetch(`http://${server.host}:${server.port}/missing`); - const html = await res.text(); - const $ = cheerio.load(html); + const url = `http://${server.host}:${server.port}/missing`; + const res1 = await fetch(url); + const res2 = await fetch(url); + const res3 = await fetch(url); + + expect(res1.status).to.equal(404); + expect(res2.status).to.equal(404); + expect(res3.status).to.equal(404); + + const html1 = await res1.text(); + const html2 = await res2.text(); + const html3 = await res3.text(); + + expect(html1).to.equal(html2); + expect(html2).to.equal(html3); + + const $ = cheerio.load(html1); - expect(res.status).to.equal(404); expect($('body').text()).to.equal('Page does not exist'); }); });