summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/integrations/node/package.json6
-rw-r--r--packages/integrations/node/src/server.ts31
-rw-r--r--packages/integrations/node/test/api-route.test.js37
-rw-r--r--packages/integrations/node/test/fixtures/api-route/package.json9
-rw-r--r--packages/integrations/node/test/fixtures/api-route/src/pages/recipes.js24
-rw-r--r--packages/integrations/node/test/test-utils.js41
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);
+ });
+ });
+}