Files
bun.sh/test/js/web/websocket/websocket-permessage-deflate-edge-cases.test.ts
2025-06-27 00:04:42 -07:00

201 lines
6.2 KiB
TypeScript

import { serve } from "bun";
import { expect, test } from "bun:test";
// Test compressed continuation frames
test("WebSocket client handles compressed continuation frames correctly", async () => {
using server = serve({
port: 0,
fetch(req, server) {
if (server.upgrade(req)) {
return;
}
return new Response("Not found", { status: 404 });
},
websocket: {
perMessageDeflate: true,
open(ws) {
// Send a message that should be compressed
const largeMessage = "A".repeat(100000); // 100KB of A's
const result = ws.send(largeMessage, true);
if (result <= 0) {
throw new Error(`Failed to send large message, result: ${result}`);
}
},
message(ws, message) {
// Echo back
const result = ws.send(message, true);
if (result <= 0) {
throw new Error(`Failed to echo message, result: ${result}`);
}
},
},
});
const client = new WebSocket(`ws://localhost:${server.port}`);
const { promise: openPromise, resolve: resolveOpen, reject: rejectOpen } = Promise.withResolvers<void>();
client.onopen = () => resolveOpen();
client.onerror = error => rejectOpen(error);
client.onclose = event => {
if (!event.wasClean) {
rejectOpen(new Error(`WebSocket closed: code=${event.code}, reason=${event.reason}`));
}
};
await openPromise;
expect(client.extensions).toContain("permessage-deflate");
const { promise: messagePromise, resolve: resolveMessage } = Promise.withResolvers<string>();
client.onmessage = event => resolveMessage(event.data);
const receivedMessage = await messagePromise;
expect(receivedMessage).toBe("A".repeat(100000));
client.close();
server.stop();
});
// Test small message compression threshold
test("WebSocket client doesn't compress small messages", async () => {
let serverReceivedCompressed = false;
using server = serve({
port: 0,
fetch(req, server) {
if (server.upgrade(req)) {
return;
}
return new Response("Not found", { status: 404 });
},
websocket: {
perMessageDeflate: true,
open(ws) {
// Track if messages are compressed by checking frame headers
},
message(ws, message) {
// Small messages should not be compressed (< 860 bytes)
const result = ws.send("OK", true);
if (result <= 0) {
throw new Error(`Failed to send OK response, result: ${result}`);
}
},
},
});
const client = new WebSocket(`ws://localhost:${server.port}`);
const { promise: openPromise, resolve: resolveOpen, reject: rejectOpen } = Promise.withResolvers<void>();
client.onopen = () => resolveOpen();
client.onerror = error => rejectOpen(error);
await openPromise;
const { promise: messagePromise, resolve: resolveMessage } = Promise.withResolvers<string>();
client.onmessage = event => resolveMessage(event.data);
// Send a small message (should not be compressed)
client.send("Hello");
const receivedMessage = await messagePromise;
expect(receivedMessage).toBe("OK");
client.close();
server.stop();
});
// Test message size limits
test("WebSocket client rejects messages exceeding size limit", async () => {
// This test would require a custom server that sends extremely large compressed data
// For now, we'll test that normal large messages work
using server = serve({
port: 0,
fetch(req, server) {
if (server.upgrade(req)) {
return;
}
return new Response("Not found", { status: 404 });
},
websocket: {
perMessageDeflate: true,
open(ws) {
// Send a 1MB message (under the 100MB limit) with more realistic data
// Using varied content to avoid triggering compression bomb detection
const size = 1 * 1024 * 1024;
const pattern = "The quick brown fox jumps over the lazy dog. ";
const buffer = Buffer.alloc(size);
buffer.fill(pattern);
const result = ws.send(buffer, true);
if (result <= 0) {
throw new Error(`Failed to send large buffer, result: ${result}`);
}
},
message(ws, message) {},
},
});
const client = new WebSocket(`ws://localhost:${server.port}`);
const { promise: openPromise, resolve: resolveOpen, reject: rejectOpen } = Promise.withResolvers<void>();
client.onopen = () => resolveOpen();
client.onerror = error => rejectOpen(error);
const { promise: messagePromise, resolve: resolveMessage } = Promise.withResolvers<string>();
client.onmessage = event => resolveMessage(event.data);
await openPromise;
const receivedMessage = await messagePromise;
expect(receivedMessage.length).toBe(1 * 1024 * 1024);
client.close();
server.stop();
});
// Test compression error handling
test("WebSocket client handles compression errors gracefully", async () => {
using server = serve({
port: 0,
fetch(req, server) {
if (server.upgrade(req)) {
return;
}
return new Response("Not found", { status: 404 });
},
websocket: {
perMessageDeflate: true,
open(ws) {
// Send a message
const result = ws.send("Test message", true);
if (result <= 0) {
throw new Error(`Failed to send test message, result: ${result}`);
}
},
message(ws, message) {
// Echo back with compression
const result = ws.send(message, true);
if (result <= 0) {
throw new Error(`Failed to echo message in compression test, result: ${result}`);
}
},
},
});
const client = new WebSocket(`ws://localhost:${server.port}`);
const { promise: openPromise, resolve: resolveOpen, reject: rejectOpen } = Promise.withResolvers<void>();
client.onopen = () => resolveOpen();
client.onerror = error => rejectOpen(error);
const { promise: messagePromise, resolve: resolveMessage } = Promise.withResolvers<string>();
client.onmessage = event => resolveMessage(event.data);
await openPromise;
const receivedMessage = await messagePromise;
expect(receivedMessage).toBe("Test message");
// Send a message to test compression
client.send(Buffer.alloc(1000, "A").toString()); // Should be compressed
client.close();
server.stop();
});