mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
* Allow zero length WebSocket client & server messages * Add test * Clean this up a little * Clean up these tests a little * Hopefully fix the test failure in release build * Don't copy into the receive buffer * Less flaky --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
343 lines
8.4 KiB
JavaScript
343 lines
8.4 KiB
JavaScript
import { describe, it, expect } from "bun:test";
|
|
import { unsafe, spawn, readableStreamToText } from "bun";
|
|
import { bunExe, bunEnv, gc } from "harness";
|
|
|
|
const TEST_WEBSOCKET_HOST = process.env.TEST_WEBSOCKET_HOST || "wss://ws.postman-echo.com/raw";
|
|
|
|
describe("WebSocket", () => {
|
|
it("should connect", async () => {
|
|
const server = Bun.serve({
|
|
port: 0,
|
|
fetch(req, server) {
|
|
if (server.upgrade(req)) {
|
|
server.stop();
|
|
return;
|
|
}
|
|
|
|
return new Response();
|
|
},
|
|
websocket: {
|
|
open(ws) {},
|
|
message(ws) {
|
|
ws.close();
|
|
},
|
|
},
|
|
});
|
|
const ws = new WebSocket(`ws://${server.hostname}:${server.port}`, {});
|
|
await new Promise(resolve => {
|
|
ws.onopen = resolve;
|
|
});
|
|
var closed = new Promise(resolve => {
|
|
ws.onclose = resolve;
|
|
});
|
|
ws.close();
|
|
await closed;
|
|
server.stop(true);
|
|
});
|
|
|
|
it("should connect over https", async () => {
|
|
const ws = new WebSocket(TEST_WEBSOCKET_HOST.replaceAll("wss:", "https:"));
|
|
await new Promise((resolve, reject) => {
|
|
ws.onopen = resolve;
|
|
ws.onerror = reject;
|
|
});
|
|
var closed = new Promise((resolve, reject) => {
|
|
ws.onclose = resolve;
|
|
});
|
|
ws.close();
|
|
await closed;
|
|
});
|
|
|
|
it("supports headers", done => {
|
|
const server = Bun.serve({
|
|
port: 0,
|
|
fetch(req, server) {
|
|
expect(req.headers.get("X-Hello")).toBe("World");
|
|
expect(req.headers.get("content-type")).toBe("lolwut");
|
|
server.stop();
|
|
done();
|
|
return new Response();
|
|
},
|
|
websocket: {
|
|
open(ws) {
|
|
ws.close();
|
|
},
|
|
},
|
|
});
|
|
const ws = new WebSocket(`ws://${server.hostname}:${server.port}`, {
|
|
headers: {
|
|
"X-Hello": "World",
|
|
"content-type": "lolwut",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("should connect over http", done => {
|
|
const server = Bun.serve({
|
|
port: 0,
|
|
fetch(req, server) {
|
|
done();
|
|
server.stop();
|
|
return new Response();
|
|
},
|
|
websocket: {
|
|
open(ws) {},
|
|
message(ws) {
|
|
ws.close();
|
|
},
|
|
},
|
|
});
|
|
new WebSocket(`http://${server.hostname}:${server.port}`, {});
|
|
});
|
|
describe("nodebuffer", () => {
|
|
it("should support 'nodebuffer' binaryType", done => {
|
|
const server = Bun.serve({
|
|
port: 0,
|
|
fetch(req, server) {
|
|
if (server.upgrade(req)) {
|
|
return;
|
|
}
|
|
|
|
return new Response();
|
|
},
|
|
websocket: {
|
|
open(ws) {
|
|
ws.sendBinary(new Uint8Array([1, 2, 3]));
|
|
},
|
|
},
|
|
});
|
|
const ws = new WebSocket(`http://${server.hostname}:${server.port}`, {});
|
|
ws.binaryType = "nodebuffer";
|
|
expect(ws.binaryType).toBe("nodebuffer");
|
|
Bun.gc(true);
|
|
ws.onmessage = ({ data }) => {
|
|
ws.close();
|
|
expect(Buffer.isBuffer(data)).toBe(true);
|
|
expect(data).toEqual(new Uint8Array([1, 2, 3]));
|
|
server.stop(true);
|
|
Bun.gc(true);
|
|
done();
|
|
};
|
|
});
|
|
|
|
it("should support 'nodebuffer' binaryType when the handler is not immediately provided", done => {
|
|
var client;
|
|
const server = Bun.serve({
|
|
port: 0,
|
|
fetch(req, server) {
|
|
if (server.upgrade(req)) {
|
|
return;
|
|
}
|
|
|
|
return new Response();
|
|
},
|
|
websocket: {
|
|
open(ws) {
|
|
ws.sendBinary(new Uint8Array([1, 2, 3]));
|
|
setTimeout(() => {
|
|
client.onmessage = ({ data }) => {
|
|
client.close();
|
|
expect(Buffer.isBuffer(data)).toBe(true);
|
|
expect(data).toEqual(new Uint8Array([1, 2, 3]));
|
|
server.stop(true);
|
|
done();
|
|
};
|
|
}, 0);
|
|
},
|
|
},
|
|
});
|
|
client = new WebSocket(`http://${server.hostname}:${server.port}`, {});
|
|
client.binaryType = "nodebuffer";
|
|
expect(client.binaryType).toBe("nodebuffer");
|
|
});
|
|
});
|
|
|
|
it("should send and receive messages", async () => {
|
|
const ws = new WebSocket(TEST_WEBSOCKET_HOST);
|
|
await new Promise((resolve, reject) => {
|
|
ws.onopen = resolve;
|
|
ws.onerror = reject;
|
|
ws.onclose = () => {
|
|
reject("WebSocket closed");
|
|
};
|
|
});
|
|
const count = 10;
|
|
|
|
// 10 messages in burst
|
|
var promise = new Promise((resolve, reject) => {
|
|
var remain = count;
|
|
ws.onmessage = event => {
|
|
gc(true);
|
|
expect(event.data).toBe("Hello World!");
|
|
remain--;
|
|
|
|
if (remain <= 0) {
|
|
ws.onmessage = () => {};
|
|
resolve();
|
|
}
|
|
};
|
|
ws.onerror = reject;
|
|
});
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
ws.send("Hello World!");
|
|
gc(true);
|
|
}
|
|
|
|
await promise;
|
|
var echo = 0;
|
|
|
|
// 10 messages one at a time
|
|
function waitForEcho() {
|
|
return new Promise((resolve, reject) => {
|
|
gc(true);
|
|
const msg = `Hello World! ${echo++}`;
|
|
ws.onmessage = event => {
|
|
expect(event.data).toBe(msg);
|
|
resolve();
|
|
};
|
|
ws.onerror = reject;
|
|
ws.onclose = reject;
|
|
ws.send(msg);
|
|
gc(true);
|
|
});
|
|
}
|
|
gc(true);
|
|
for (let i = 0; i < count; i++) await waitForEcho();
|
|
ws.onclose = () => {};
|
|
ws.onerror = () => {};
|
|
ws.close();
|
|
gc(true);
|
|
});
|
|
});
|
|
|
|
describe("websocket in subprocess", () => {
|
|
it("should exit", async () => {
|
|
let messageReceived = false;
|
|
const server = Bun.serve({
|
|
port: 0,
|
|
fetch(req, server) {
|
|
if (server.upgrade(req)) {
|
|
return;
|
|
}
|
|
|
|
return new Response("http response");
|
|
},
|
|
websocket: {
|
|
open(ws) {
|
|
ws.send("hello websocket");
|
|
},
|
|
message(ws) {
|
|
messageReceived = true;
|
|
ws.close();
|
|
},
|
|
close(ws) {},
|
|
},
|
|
});
|
|
const subprocess = Bun.spawn({
|
|
cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", `http://${server.hostname}:${server.port}`],
|
|
stderr: "pipe",
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
env: bunEnv,
|
|
});
|
|
|
|
expect(await subprocess.exited).toBe(0);
|
|
expect(messageReceived).toBe(true);
|
|
server.stop(true);
|
|
});
|
|
|
|
it("should exit after killed", async () => {
|
|
const subprocess = Bun.spawn({
|
|
cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", TEST_WEBSOCKET_HOST],
|
|
stderr: "pipe",
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
env: bunEnv,
|
|
});
|
|
|
|
subprocess.kill();
|
|
|
|
expect(await subprocess.exited).toBe("SIGHUP");
|
|
});
|
|
|
|
it("should exit with invalid url", async () => {
|
|
const subprocess = Bun.spawn({
|
|
cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", "invalid url"],
|
|
stderr: "pipe",
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
env: bunEnv,
|
|
});
|
|
|
|
expect(await subprocess.exited).toBe(1);
|
|
});
|
|
|
|
it("should exit after timeout", async () => {
|
|
let messageReceived = false;
|
|
let start = 0;
|
|
const server = Bun.serve({
|
|
port: 0,
|
|
fetch(req, server) {
|
|
if (server.upgrade(req)) {
|
|
return;
|
|
}
|
|
|
|
return new Response("http response");
|
|
},
|
|
websocket: {
|
|
open(ws) {
|
|
start = performance.now();
|
|
ws.send("timeout");
|
|
},
|
|
message(ws, message) {
|
|
messageReceived = true;
|
|
expect(performance.now() - start >= 300).toBe(true);
|
|
ws.close();
|
|
},
|
|
close(ws) {},
|
|
},
|
|
});
|
|
const subprocess = Bun.spawn({
|
|
cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", `http://${server.hostname}:${server.port}`],
|
|
stderr: "pipe",
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
env: bunEnv,
|
|
});
|
|
|
|
expect(await subprocess.exited).toBe(0);
|
|
expect(messageReceived).toBe(true);
|
|
server.stop(true);
|
|
});
|
|
|
|
it("should exit after server stop and 0 messages", async () => {
|
|
const server = Bun.serve({
|
|
port: 0,
|
|
fetch(req, server) {
|
|
if (server.upgrade(req)) {
|
|
return;
|
|
}
|
|
|
|
return new Response("http response");
|
|
},
|
|
websocket: {
|
|
open(ws) {},
|
|
message(ws, message) {},
|
|
close(ws) {},
|
|
},
|
|
});
|
|
|
|
const subprocess = Bun.spawn({
|
|
cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", `http://${server.hostname}:${server.port}`],
|
|
stderr: "pipe",
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
env: bunEnv,
|
|
});
|
|
|
|
server.stop(true);
|
|
expect(await subprocess.exited).toBe(0);
|
|
});
|
|
});
|