summaryrefslogtreecommitdiff
path: root/cli/tests/unit/websocketstream_test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tests/unit/websocketstream_test.ts')
-rw-r--r--cli/tests/unit/websocketstream_test.ts353
1 files changed, 353 insertions, 0 deletions
diff --git a/cli/tests/unit/websocketstream_test.ts b/cli/tests/unit/websocketstream_test.ts
new file mode 100644
index 000000000..0a16f254e
--- /dev/null
+++ b/cli/tests/unit/websocketstream_test.ts
@@ -0,0 +1,353 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import {
+ assertEquals,
+ assertNotEquals,
+ assertRejects,
+ assertThrows,
+ unreachable,
+} from "@test_util/std/assert/mod.ts";
+
+Deno.test("fragment", () => {
+ assertThrows(() => new WebSocketStream("ws://localhost:4242/#"));
+ assertThrows(() => new WebSocketStream("ws://localhost:4242/#foo"));
+});
+
+Deno.test("duplicate protocols", () => {
+ assertThrows(() =>
+ new WebSocketStream("ws://localhost:4242", {
+ protocols: ["foo", "foo"],
+ })
+ );
+});
+
+Deno.test(
+ "connect & close custom valid code",
+ { sanitizeOps: false },
+ async () => {
+ const ws = new WebSocketStream("ws://localhost:4242");
+ await ws.opened;
+ ws.close({ code: 1000 });
+ await ws.closed;
+ },
+);
+
+Deno.test(
+ "connect & close custom invalid reason",
+ { sanitizeOps: false },
+ async () => {
+ const ws = new WebSocketStream("ws://localhost:4242");
+ await ws.opened;
+ assertThrows(() => ws.close({ code: 1000, reason: "".padEnd(124, "o") }));
+ ws.close();
+ await ws.closed;
+ },
+);
+
+Deno.test("echo string", async () => {
+ const ws = new WebSocketStream("ws://localhost:4242");
+ const { readable, writable } = await ws.opened;
+ await writable.getWriter().write("foo");
+ const res = await readable.getReader().read();
+ assertEquals(res.value, "foo");
+ ws.close();
+ await ws.closed;
+});
+
+// TODO(mmastrac): This fails -- perhaps it isn't respecting the TLS settings?
+Deno.test("echo string tls", { ignore: true }, async () => {
+ const ws = new WebSocketStream("wss://localhost:4243");
+ const { readable, writable } = await ws.opened;
+ await writable.getWriter().write("foo");
+ const res = await readable.getReader().read();
+ assertEquals(res.value, "foo");
+ ws.close();
+ await ws.closed;
+});
+
+Deno.test("websocket error", { sanitizeOps: false }, async () => {
+ const ws = new WebSocketStream("wss://localhost:4242");
+ await Promise.all([
+ // TODO(mmastrac): this exception should be tested
+ assertRejects(
+ () => ws.opened,
+ // Deno.errors.UnexpectedEof,
+ // "tls handshake eof",
+ ),
+ // TODO(mmastrac): this exception should be tested
+ assertRejects(
+ () => ws.closed,
+ // Deno.errors.UnexpectedEof,
+ // "tls handshake eof",
+ ),
+ ]);
+});
+
+Deno.test("echo uint8array", { sanitizeOps: false }, async () => {
+ const ws = new WebSocketStream("ws://localhost:4242");
+ const { readable, writable } = await ws.opened;
+ const uint = new Uint8Array([102, 111, 111]);
+ await writable.getWriter().write(uint);
+ const res = await readable.getReader().read();
+ assertEquals(res.value, uint);
+ ws.close();
+ await ws.closed;
+});
+
+Deno.test("aborting immediately throws an AbortError", async () => {
+ const controller = new AbortController();
+ const wss = new WebSocketStream("ws://localhost:4242", {
+ signal: controller.signal,
+ });
+ controller.abort();
+ // TODO(mmastrac): this exception should be tested
+ await assertRejects(
+ () => wss.opened,
+ // (error: Error) => {
+ // assert(error instanceof DOMException);
+ // assertEquals(error.name, "AbortError");
+ // },
+ );
+ // TODO(mmastrac): this exception should be tested
+ await assertRejects(
+ () => wss.closed,
+ // (error: Error) => {
+ // assert(error instanceof DOMException);
+ // assertEquals(error.name, "AbortError");
+ // },
+ );
+});
+
+Deno.test("aborting immediately with a reason throws that reason", async () => {
+ const controller = new AbortController();
+ const wss = new WebSocketStream("ws://localhost:4242", {
+ signal: controller.signal,
+ });
+ const abortReason = new Error();
+ controller.abort(abortReason);
+ // TODO(mmastrac): this exception should be tested
+ await assertRejects(
+ () => wss.opened,
+ // (error: Error) => assertEquals(error, abortReason),
+ );
+ // TODO(mmastrac): this exception should be tested
+ await assertRejects(
+ () => wss.closed,
+ // (error: Error) => assertEquals(error, abortReason),
+ );
+});
+
+Deno.test("aborting immediately with a primitive as reason throws that primitive", async () => {
+ const controller = new AbortController();
+ const wss = new WebSocketStream("ws://localhost:4242", {
+ signal: controller.signal,
+ });
+ controller.abort("Some string");
+ await wss.opened.then(
+ () => unreachable(),
+ (e) => assertEquals(e, "Some string"),
+ );
+ await wss.closed.then(
+ () => unreachable(),
+ (e) => assertEquals(e, "Some string"),
+ );
+});
+
+Deno.test("headers", { sanitizeOps: false }, async () => {
+ const listener = Deno.listen({ port: 4512 });
+ const promise = (async () => {
+ const conn = await listener.accept();
+ const httpConn = Deno.serveHttp(conn);
+ const { request, respondWith } = (await httpConn.nextRequest())!;
+ assertEquals(request.headers.get("x-some-header"), "foo");
+ const { response, socket } = Deno.upgradeWebSocket(request);
+ socket.onopen = () => socket.close();
+ const p = new Promise<void>((resolve) => {
+ socket.onopen = () => socket.close();
+ socket.onclose = () => resolve();
+ });
+ await respondWith(response);
+ await p;
+ })();
+
+ const ws = new WebSocketStream("ws://localhost:4512", {
+ headers: [["x-some-header", "foo"]],
+ });
+ await ws.opened;
+ await promise;
+ await ws.closed;
+ listener.close();
+});
+
+Deno.test("forbidden headers", async () => {
+ const forbiddenHeaders = [
+ "sec-websocket-accept",
+ "sec-websocket-extensions",
+ "sec-websocket-key",
+ "sec-websocket-protocol",
+ "sec-websocket-version",
+ "upgrade",
+ "connection",
+ ];
+
+ const listener = Deno.listen({ port: 4512 });
+ const promise = (async () => {
+ const conn = await listener.accept();
+ const httpConn = Deno.serveHttp(conn);
+ const { request, respondWith } = (await httpConn.nextRequest())!;
+ for (const [key] of request.headers) {
+ assertNotEquals(key, "foo");
+ }
+ const { response, socket } = Deno.upgradeWebSocket(request);
+ const p = new Promise<void>((resolve) => {
+ socket.onopen = () => socket.close();
+ socket.onclose = () => resolve();
+ });
+ await respondWith(response);
+ await p;
+ })();
+
+ const ws = new WebSocketStream("ws://localhost:4512", {
+ headers: forbiddenHeaders.map((header) => [header, "foo"]),
+ });
+ await ws.opened;
+ await promise;
+ await ws.closed;
+ listener.close();
+});
+
+Deno.test("sync close with empty stream", { sanitizeOps: false }, async () => {
+ const listener = Deno.listen({ port: 4512 });
+ const promise = (async () => {
+ const conn = await listener.accept();
+ const httpConn = Deno.serveHttp(conn);
+ const { request, respondWith } = (await httpConn.nextRequest())!;
+ const { response, socket } = Deno.upgradeWebSocket(request);
+ const p = new Promise<void>((resolve) => {
+ socket.onopen = () => {
+ socket.send("first message");
+ socket.send("second message");
+ };
+ socket.onclose = () => resolve();
+ });
+ await respondWith(response);
+ await p;
+ })();
+
+ const ws = new WebSocketStream("ws://localhost:4512");
+ const { readable } = await ws.opened;
+ const reader = readable.getReader();
+ const firstMessage = await reader.read();
+ assertEquals(firstMessage.value, "first message");
+ const secondMessage = await reader.read();
+ assertEquals(secondMessage.value, "second message");
+ ws.close({ code: 1000 });
+ await ws.closed;
+ await promise;
+ listener.close();
+});
+
+Deno.test(
+ "sync close with unread messages in stream",
+ { sanitizeOps: false },
+ async () => {
+ const listener = Deno.listen({ port: 4512 });
+ const promise = (async () => {
+ const conn = await listener.accept();
+ const httpConn = Deno.serveHttp(conn);
+ const { request, respondWith } = (await httpConn.nextRequest())!;
+ const { response, socket } = Deno.upgradeWebSocket(request);
+ const p = new Promise<void>((resolve) => {
+ socket.onopen = () => {
+ socket.send("first message");
+ socket.send("second message");
+ socket.send("third message");
+ socket.send("fourth message");
+ };
+ socket.onclose = () => resolve();
+ });
+ await respondWith(response);
+ await p;
+ })();
+
+ const ws = new WebSocketStream("ws://localhost:4512");
+ const { readable } = await ws.opened;
+ const reader = readable.getReader();
+ const firstMessage = await reader.read();
+ assertEquals(firstMessage.value, "first message");
+ const secondMessage = await reader.read();
+ assertEquals(secondMessage.value, "second message");
+ ws.close({ code: 1000 });
+ await ws.closed;
+ await promise;
+ listener.close();
+ },
+);
+
+Deno.test("async close with empty stream", async () => {
+ const listener = Deno.listen({ port: 4512 });
+ const promise = (async () => {
+ const conn = await listener.accept();
+ const httpConn = Deno.serveHttp(conn);
+ const { request, respondWith } = (await httpConn.nextRequest())!;
+ const { response, socket } = Deno.upgradeWebSocket(request);
+ const p = new Promise<void>((resolve) => {
+ socket.onopen = () => {
+ socket.send("first message");
+ socket.send("second message");
+ };
+ socket.onclose = () => resolve();
+ });
+ await respondWith(response);
+ await p;
+ })();
+
+ const ws = new WebSocketStream("ws://localhost:4512");
+ const { readable } = await ws.opened;
+ const reader = readable.getReader();
+ const firstMessage = await reader.read();
+ assertEquals(firstMessage.value, "first message");
+ const secondMessage = await reader.read();
+ assertEquals(secondMessage.value, "second message");
+ setTimeout(() => {
+ ws.close({ code: 1000 });
+ }, 0);
+ await ws.closed;
+ await promise;
+ listener.close();
+});
+
+Deno.test("async close with unread messages in stream", async () => {
+ const listener = Deno.listen({ port: 4512 });
+ const promise = (async () => {
+ const conn = await listener.accept();
+ const httpConn = Deno.serveHttp(conn);
+ const { request, respondWith } = (await httpConn.nextRequest())!;
+ const { response, socket } = Deno.upgradeWebSocket(request);
+ const p = new Promise<void>((resolve) => {
+ socket.onopen = () => {
+ socket.send("first message");
+ socket.send("second message");
+ socket.send("third message");
+ socket.send("fourth message");
+ };
+ socket.onclose = () => resolve();
+ });
+ await respondWith(response);
+ await p;
+ })();
+
+ const ws = new WebSocketStream("ws://localhost:4512");
+ const { readable } = await ws.opened;
+ const reader = readable.getReader();
+ const firstMessage = await reader.read();
+ assertEquals(firstMessage.value, "first message");
+ const secondMessage = await reader.read();
+ assertEquals(secondMessage.value, "second message");
+ setTimeout(() => {
+ ws.close({ code: 1000 });
+ }, 0);
+ await ws.closed;
+ await promise;
+ listener.close();
+});