Skip to content

Commit

Permalink
Fix some tests, implement a val-town like pattern, better error surfa…
Browse files Browse the repository at this point in the history
…cing
  • Loading branch information
maxmcd committed Apr 18, 2024
1 parent e82a4bd commit 95f52c2
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 15 deletions.
7 changes: 6 additions & 1 deletion deno-guest/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
const scriptType = Deno.args[0];
const script = Deno.args[1];

const importURL =
scriptType == "import"
? script
: "data:text/tsx," + encodeURIComponent(script);

let handler: { default: (req: Request) => Promise<Response> | Response };
let importing = true;
let pendingRequests: {
Expand All @@ -19,7 +24,7 @@ Deno.serve(
// Now that we're listening, start executing user-provided code. We could
// import while starting the server for a small performance improvement,
// but it would complicate reading the port from the Deno logs.
handler = await import(`data:text/tsx,${encodeURIComponent(script)}`);
handler = await import(importURL);
if (!handler.default) {
throw new Error("No default export found in script.");
}
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
"vitest": "^1.5.0"
},
"dependencies": {
"got": "^14.2.1",
"http2-wrapper": "^2.2.1"
"got": "^14.2.1"
}
}
112 changes: 102 additions & 10 deletions src/DenoHTTPWorker.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { it, describe, expect } from "vitest";
import { it as _it, describe, expect } from "vitest";
import { newDenoHTTPWorker } from "./index.js";
import fs from "fs";
import path from "path";

// Uncomment this if you want to debug serial test execution
const it = _it.concurrent;
// const it = _it

describe("DenoHTTPWorker", { timeout: 1000 }, () => {
it("json response", async () => {
const echoFile = path.resolve(__dirname, "./test/echo-request.ts");
const echoScript = fs.readFileSync(echoFile, { encoding: "utf-8" });
const vtFile = path.resolve(__dirname, "./test/val-town.ts");
const vtScript = fs.readFileSync(vtFile, { encoding: "utf-8" });

it("json response multiple requests", async () => {
let worker = await newDenoHTTPWorker(`
export default async function (req: Request): Promise<Response> {
let headers = {};
Expand All @@ -12,19 +23,75 @@ describe("DenoHTTPWorker", { timeout: 1000 }, () => {
return Response.json({ ok: req.url, headers: headers })
}
`);
for (let i = 0; i < 10; i++) {
let json = await worker.client
.get("https://localhost/", { headers: {} })
.json();
expect(json).toEqual({
ok: "https://localhost/",
headers: {
accept: "application/json",
"accept-encoding": "gzip, deflate, br",
},
});
}
worker.terminate();
});

let json = await worker.client
.get("https://localhost/", { headers: {} })
.json();
expect(json).toEqual({
ok: "https://localhost/",
headers: {
accept: "application/json",
"accept-encoding": "gzip, deflate, br",
it("should be able to import script", async () => {
const file = path.resolve(__dirname, "./test/echo-request.ts");
const url = new URL(`file://${file}`);
let worker = await newDenoHTTPWorker(url, {
permissions: {
allowRead: [file],
},
});
process.stderr.on("data", (data) => {
console.log(data.toString());
});
process.stdout.on("data", (data) => {
console.log(data.toString());
});

let resp: any = await worker.client
.get("https://localhost/", {
headers: { "User-Agent": "some value" },
})
.json();
await worker.terminate();
});

it("user agent is not overwritten", async () => {
let worker = await newDenoHTTPWorker(echoScript);
let resp: any = await worker.client
.get("https://localhost/", {
headers: { "User-Agent": "some value" },
})
.json();
expect(resp["headers"]["user-agent"]).toEqual("some value");
await worker.terminate();
});

it("json response", async () => {
let worker = await newDenoHTTPWorker(echoScript);
let resp = (await worker.client
.post("https://localhost/", { json: { ok: true } })
.json()) as any;
expect(resp.body).toEqual('{"ok":true}');

// TODO: test against streaming resp as well as request body
let req = worker.client.post("https://localhost/", {
body: fs.createReadStream(import.meta.url.replace("file://", "")),
});

let body: any = await req.json();
expect(body.body).toEqual(
fs.readFileSync(import.meta.url.replace("file://", "")).toString()
);

worker.terminate();
});

it("post with body", async () => {
let worker = await newDenoHTTPWorker(`
export default async function (req: Request): Promise<Response> {
Expand Down Expand Up @@ -60,4 +127,29 @@ describe("DenoHTTPWorker", { timeout: 1000 }, () => {

expect(allStdout).toEqual("Hi, I am here\n");
});

it("can implement val town", async () => {
let worker = await newDenoHTTPWorker(vtScript);
worker.stdout.on("data", (data) => {
console.log(data.toString());
});
worker.stderr.on("data", (data) => {
console.log(data.toString());
});
let first = worker.client.post("https://localhost:8080/", {
body:
"data:text/tsx," +
encodeURIComponent(`export default async function (req: Request): Promise<Response> {
return Response.json({ ok: true })
} ${"///".repeat(8000)}`),
});
// We send a request to initialize and when the first request is in flight
// we send another request
let second = worker.client("https://foo.web.val.run");

await first.text();
expect(await second.text()).toEqual('{"ok":true}');

worker.terminate();
});
});
12 changes: 10 additions & 2 deletions src/DenoHTTPWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,11 @@ export const newDenoHTTPWorker = async (
let worker: DenoHTTPWorker;
process.on("exit", (code: number, signal: string) => {
if (!running) {
let stderr = process.stderr?.read()?.toString();
reject(
new Error(
`Deno process exited before it was ready: code: ${code}, signal: ${signal}`
`Deno process exited before it was ready: code: ${code}, signal: ${signal}` +
(stderr ? `\n${stderr}` : "")
)
);
} else {
Expand Down Expand Up @@ -367,7 +369,12 @@ export const newDenoHTTPWorker = async (
const port = match[1];
const _httpSession = http2.connect(`http://localhost:${port}`);
_httpSession.on("error", (err) => {
console.error("http2 session error", err);
if (!running) {
reject(err);
} else {
worker.terminate();
throw err;
}
});
_httpSession.on("connect", () => {
const _got = got.extend({
Expand Down Expand Up @@ -432,6 +439,7 @@ export const newDenoHTTPWorker = async (
stdout.on("readable", onReadable);
});
};
export type { DenoHTTPWorker };

class DenoHTTPWorker {
private _httpSession: http2.ClientHttp2Session;
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { newDenoHTTPWorker } from "./DenoHTTPWorker.js";
export type { DenoHTTPWorker } from "./DenoHTTPWorker.js";
12 changes: 12 additions & 0 deletions src/test/echo-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default async function (req: Request): Promise<Response> {
let headers: { [key: string]: string } = {};
for (let [key, value] of req.headers.entries()) {
headers[key] = value;
}
return Response.json({
url: req.url,
headers: headers,
body: await req.text(),
method: req.method,
});
}
31 changes: 31 additions & 0 deletions src/test/val-town.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
let initialized = false;
let initializing = false;
let handler: (req: Request) => Promise<Response> | Response;

let pendingRequests: any[] = [];
export default async function (req: Request): Promise<Response> {
if (initializing) {
return new Promise((resolve, reject) => {
pendingRequests.push({ req, resolve, reject });
});
}
if (!initialized) {
initializing = true;
try {
let source = await req.text();
if (!source) {
return new Response("No source provided", { status: 400 });
}
handler = (await import(source)).default;
initialized = true;
initializing = false;
for (const { req, resolve } of pendingRequests) {
resolve(handler(req));
}
} catch (e: any) {
return new Response(e, { status: 500 });
}
return new Response("");
}
return handler(req);
}

0 comments on commit 95f52c2

Please sign in to comment.