Skip to content

Commit

Permalink
feat: refactor tests for handleNextRequest
Browse files Browse the repository at this point in the history
  • Loading branch information
thgh committed Jul 9, 2023
1 parent 20bb2fe commit be9ba42
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 38 deletions.
69 changes: 61 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"sideEffects": false,
"scripts": {
"build": "tscd --entry index.js",
"test": "c8 tap",
"test": "c8 tap test/*.test.ts",
"prepublishOnly": "npm run clean && npm run test && npm run build",
"coverage": "c8 report --reporter=lcov",
"lint": "eslint ./src --ext json,ts --ignore-path .gitignore",
Expand All @@ -49,6 +49,7 @@
"devDependencies": {
"@types/express": "^4.17.17",
"@types/node": "^18.16.3",
"@types/react": "18.2.14",
"@types/tap": "^15.0.8",
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
Expand All @@ -59,6 +60,8 @@
"express": "^4.18.2",
"next": "^13.4.9",
"prettier": "^2.8.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tap": "^16.3.4",
"tinyspy": "^2.1.0",
"ts-node": "^10.9.1",
Expand Down
24 changes: 11 additions & 13 deletions src/app-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { ValueOrPromise } from "./types.js";
import type { NextResponse } from "next/server.js";
import type { NextRequest } from "next/server.js";
import { Socket } from "net";
import { Readable } from "node:stream";

export type AppRouteRequestHandler = (
req: NextRequest
Expand All @@ -19,7 +18,7 @@ export type AppRouteRequestHandler = (
// };
// ```

export const handleNextRequest = (
export const handleNextRequest = async (
req: NextRequest | Request,
app: Express.Application
) => {
Expand All @@ -33,25 +32,24 @@ export const handleNextRequest = (
});
incoming.method = req.method;
incoming.url = req.url;
if (req.body) {
const bodyStream = Readable.from([
typeof req.body === "string" ? req.body : JSON.stringify(req.body),
]);
incoming.push(bodyStream);
incoming.push(null); // Signal the end of the stream

const text = await req.text();
if (text) {
incoming.push(text);
incoming.push(null);
}

const response = new ServerResponse(incoming);
return new Promise<Response>((resolve, reject) => {
// TODO: overwrite response.end instead
response.json = (body) => {
const res = new Response(JSON.stringify(body));
const nativeEnd = response.end;
response.end = (body, encoding) => {
const res = new Response(body);
res.headers.set("Content-Type", "application/json");
// TODO: set other headers
resolve(res);
nativeEnd(body, encoding);
};

app.handle(incoming, res, (err) => {
app.handle(incoming, response, (err) => {
// This should never happen?
if (err) {
reject(err);
Expand Down
123 changes: 107 additions & 16 deletions test/next-app.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,111 @@
import express from "express";
import { test } from "tap";
import { handleNextRequest } from "../src/app-route.js";

test("simple", async (t) => {
const request = new Request("http://example/foo/bar");
const app = express();
app.get("/foo/:hello", (req, res) => {
console.log("handle inner");
res.json({ message: req.params.hello });
import { spawn } from "node:child_process";

test("real server", async (t) => {
t.before(async () => {
await new Promise((resolve) => {
const child = spawn("rm", ["-rf", "test/next/.next"], {});
child.on("exit", resolve);
});
});

const nextApp = await next({ dir: "test/next" });
t.teardown(nextApp.close);
console.log("next url", nextApp.address);

const query = await fetch(nextApp.address + "/query?message=bar").then((r) =>
r.json()
);
t.same(query, { message: "bar" });

const headers = await fetch(nextApp.address + "/headers", {
headers: {
example: "bar",
Uppercase: "BAR",
"X-Custom": "custom",
},
}).then((r) => r.json());
t.has(headers, {
example: "bar",
uppercase: "BAR",
"x-custom": "custom",
});

try {
console.log("handle outer");
const response = await handleNextRequest(request, app);
t.same(response.body, { message: "bar" }, "returns the body");
} catch (error) {
console.log("error", error.message);
}
const body = await fetch(nextApp.address + "/body", {
headers: { "content-type": "application/json" },
method: "POST",
body: JSON.stringify({ message: "bar" }),
}).then((r) => r.json());
t.same(body, { message: "bar" });

const bodyUrlEncoded = await fetch(nextApp.address + "/body", {
method: "POST",
// url encoded
headers: { "content-type": "application/x-www-form-urlencoded" },
body: "message=bar",
}).then((r) => r.json());
t.same(bodyUrlEncoded, { message: "bar" });

// TODO
const sendFile = await fetch(nextApp.address + "/end", {}).then((r) =>
r.text()
);
t.same(sendFile, "barfile");
});

// Cannot run next() from 'next' because of ts transpilation
async function next({ dir }: { dir: string }) {
return new Promise<{ close: () => void; address: string }>(
(resolve, reject) => {
// Start server in child process
let started = false;

// TODO: random port?
console.log("starting next", dir);
const child = spawn("npx", ["next"], {
cwd: process.cwd() + "/" + dir,
env: {
PATH: process.env.PATH,
PORT: Math.floor(Math.random() * 50000 + 10000).toString(),
},
detached: false,
});

child.stdout.setEncoding("utf8");
child.stdout.on("data", async function (data) {
// console.log("|", data);
if (!started && data.includes("started server on")) {
started = true;
resolve({
address: data.split("url:")[1].trim(),
close: kill,
});
}
});
child.stderr.setEncoding("utf8");
child.stderr.on("data", function (data) {
console.log("stderr: ", data);
});

function kill() {
return new Promise<void>((resolve) => {
if (child.exitCode === null) {
child.on("exit", resolve);
child.on("close", (d) => console.log("close", d));
child.on("error", (d) => console.log("error", d));
} else {
return resolve();
}

child.kill();
setTimeout(() => {
if (child.exitCode === null) {
console.log("SIGKILL");
child.kill("SIGKILL");
}
}, 2000);
});
}
}
);
}
Loading

0 comments on commit be9ba42

Please sign in to comment.