diff options
6 files changed, 134 insertions, 14 deletions
diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json index 7886e93fc..d39d49124 100644 --- a/packages/integrations/node/package.json +++ b/packages/integrations/node/package.json @@ -24,13 +24,15 @@ "scripts": { "build": "astro-scripts build \"src/**/*.ts\" && tsc", "build:ci": "astro-scripts build \"src/**/*.ts\"", - "dev": "astro-scripts dev \"src/**/*.ts\"" + "dev": "astro-scripts dev \"src/**/*.ts\"", + "test": "mocha --exit --timeout 20000 test/" }, "dependencies": { "@astrojs/webapi": "^0.12.0" }, "devDependencies": { "astro": "workspace:*", - "astro-scripts": "workspace:*" + "astro-scripts": "workspace:*", + "node-mocks-http": "^1.11.0" } } diff --git a/packages/integrations/node/src/server.ts b/packages/integrations/node/src/server.ts index c07b5a91b..453ecb2d2 100644 --- a/packages/integrations/node/src/server.ts +++ b/packages/integrations/node/src/server.ts @@ -12,21 +12,28 @@ export function createExports(manifest: SSRManifest) { const app = new NodeApp(manifest); return { async handler(req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) { - const route = app.match(req); + try { + const route = app.match(req); - if (route) { - try { - const response = await app.render(req); - await writeWebResponse(res, response); - } catch (err: unknown) { - if (next) { - next(err); - } else { - throw err; + if (route) { + try { + const response = await app.render(req); + await writeWebResponse(res, response); + } catch (err: unknown) { + if (next) { + next(err); + } else { + throw err; + } } + } else if (next) { + return next(); + } + } catch(err: unknown) { + if(!res.headersSent) { + res.writeHead(500, `Server error`); + res.end(); } - } else if (next) { - return next(); } }, }; diff --git a/packages/integrations/node/test/api-route.test.js b/packages/integrations/node/test/api-route.test.js new file mode 100644 index 000000000..963e0463a --- /dev/null +++ b/packages/integrations/node/test/api-route.test.js @@ -0,0 +1,37 @@ +import nodejs from '../dist/index.js'; +import { loadFixture, createRequestAndResponse, toPromise } from './test-utils.js'; +import { expect } from 'chai'; + + +describe('API routes', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/api-route/', + experimental: { + ssr: true, + }, + adapter: nodejs(), + }); + await fixture.build(); + }); + + it('Can get the request body', async () => { + const { handler } = await import('./fixtures/api-route/dist/server/entry.mjs'); + + let { req, res, done } = createRequestAndResponse({ + method: 'POST', + url: '/recipes' + }); + + handler(req, res); + req.send(JSON.stringify({ id: 2 })); + + let [ buffer ] = await done; + let json = JSON.parse(buffer.toString('utf-8')); + expect(json.length).to.equal(1); + expect(json[0].name).to.equal('Broccoli Soup'); + }); +}); diff --git a/packages/integrations/node/test/fixtures/api-route/package.json b/packages/integrations/node/test/fixtures/api-route/package.json new file mode 100644 index 000000000..c4d9bdd2b --- /dev/null +++ b/packages/integrations/node/test/fixtures/api-route/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/nodejs-api-route", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*", + "@astrojs/node": "workspace:*" + } +} diff --git a/packages/integrations/node/test/fixtures/api-route/src/pages/recipes.js b/packages/integrations/node/test/fixtures/api-route/src/pages/recipes.js new file mode 100644 index 000000000..edbd15a0e --- /dev/null +++ b/packages/integrations/node/test/fixtures/api-route/src/pages/recipes.js @@ -0,0 +1,24 @@ + +export async function post({ request }) { + let body = await request.json(); + const recipes = [ + { + id: 1, + name: 'Potato Soup' + }, + { + id: 2, + name: 'Broccoli Soup' + } + ]; + + let out = recipes.filter(r => { + return r.id === body.id; + }); + + return new Response(JSON.stringify(out), { + headers: { + 'Content-Type': 'application/json' + } + }); +} diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js new file mode 100644 index 000000000..4bd42d557 --- /dev/null +++ b/packages/integrations/node/test/test-utils.js @@ -0,0 +1,41 @@ +import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js'; +import httpMocks from 'node-mocks-http'; +import { EventEmitter } from 'events'; + +/** + * @typedef {import('../../../astro/test/test-utils').Fixture} Fixture + */ + +export function loadFixture(inlineConfig) { + if (!inlineConfig || !inlineConfig.root) + throw new Error("Must provide { root: './fixtures/...' }"); + + // resolve the relative root (i.e. "./fixtures/tailwindcss") to a full filepath + // without this, the main `loadFixture` helper will resolve relative to `packages/astro/test` + return baseLoadFixture({ + ...inlineConfig, + root: new URL(inlineConfig.root, import.meta.url).toString(), + }); +} + +export function createRequestAndResponse(reqOptions) { + let req = httpMocks.createRequest(reqOptions); + + let res = httpMocks.createResponse({ + eventEmitter: EventEmitter, + req + }); + + let done = toPromise(res); + + return { req, res, done }; +} + +export function toPromise(res) { + return new Promise(resolve => { + res.on('end', () => { + let chunks = res._getChunks(); + resolve(chunks); + }); + }); +} |