1import * as DenoSocket from "@effect/platform-deno/DenoSocket"
2import * as DenoSocketServer from "@effect/platform-deno/DenoSocketServer"
3import { assert, describe, it } from "@effect/vitest"
4import { Effect, Exit, Redacted, Scope } from "effect"
5import * as NetAddress from "effect/net/NetAddress"
6import type * as Socket from "effect/socket/Socket"
7
8const ca = Deno.readTextFileSync(new URL("./fixtures/tls/ca.pem", import.meta.url))
9const cert = Deno.readTextFileSync(new URL("./fixtures/tls/cert.pem", import.meta.url))
10const key = Deno.readTextFileSync(new URL("./fixtures/tls/key.pem", import.meta.url))
11
12const makeTempDir = Effect.acquireRelease(
13 Effect.promise(() => Deno.makeTempDir()),
14 (path) => Effect.promise(() => Deno.remove(path, { recursive: true }))
15)
16
17const echo = (socket: Socket.Socket, upgradeOptions?: Socket.TlsUpgradeOptions) =>
18 Effect.gen(function*() {
19 const writer = yield* socket.writer
20 const { pull, upgrade } = yield* socket.reader
21 if (upgradeOptions !== undefined) {
22 yield* upgrade(upgradeOptions)
23 }
24 while (true) {
25 yield* writer.writeAll(yield* pull)
26 }
27 }).pipe(
28 Effect.scoped,
29 Effect.catchTag("SocketError", () => Effect.void)
30 )
31
32const sendHelloDeno = (socket: Socket.Socket, upgradeOptions?: Socket.TlsUpgradeOptions) =>
33 Effect.gen(function*() {
34 const writer = yield* socket.writer
35 const { pull, upgrade } = yield* socket.reader
36 if (upgradeOptions !== undefined) {
37 yield* upgrade(upgradeOptions)
38 }
39 yield* writer.writeAll(["Hello", "Deno"])
40 const decoder = new TextDecoder()
41 let output = ""
42 while (output.length < 9) {
43 for (const chunk of yield* pull) {
44 output += typeof chunk === "string" ? chunk : decoder.decode(chunk)
45 }
46 }
47 return output
48 }).pipe(Effect.scoped)
49
50describe("DenoSocketServer", () => {
51 it.effect("echoes over TCP and reports its address", () =>
52 Effect.gen(function*() {
53 const server = yield* DenoSocketServer.make({ host: "127.0.0.1", port: 0 })
54 const address = server.address
55 assert.strictEqual(address._tag, "InetAddressV4")
56 if (address._tag !== "InetAddressV4") return
57 assert.strictEqual(NetAddress.formatIp(address.address), "127.0.0.1")
58 assert.notStrictEqual(address.port, 0)
59 yield* server.run(echo).pipe(Effect.forkScoped)
60
61 const socket = yield* DenoSocket.makeTcp({
62 hostname: NetAddress.formatIp(address.address),
63 port: address.port
64 })
65 const output = yield* sendHelloDeno(socket)
66
67 assert.strictEqual(output, "HelloDeno")
68 }))
69
70 it.effect("echoes over Unix sockets and reports its address", () =>
71 Effect.gen(function*() {
72 const directory = yield* makeTempDir
73 const path = `${directory}/echo.sock`
74 const server = yield* DenoSocketServer.make({ path })
75 assert.strictEqual(server.address._tag, "UnixPathAddress")
76 assert.strictEqual(server.address._tag === "UnixPathAddress" ? server.address.path : undefined, path)
77 yield* server.run(echo).pipe(Effect.forkScoped)
78
79 const socket = yield* DenoSocket.makeTcp({ transport: "unix", path })
80 const output = yield* sendHelloDeno(socket)
81
82 assert.strictEqual(output, "HelloDeno")
83 }))
84
85 it.effect("closes with a pending pre-run connection", () =>
86 Effect.gen(function*() {
87 const scope = yield* Scope.make()
88 const server = yield* DenoSocketServer.make({ host: "127.0.0.1", port: 0 }).pipe(Scope.provide(scope))
89 const address = server.address
90 assert.strictEqual(address._tag, "InetAddressV4")
91 if (address._tag !== "InetAddressV4") return
92 const conn = yield* Effect.acquireRelease(
93 Effect.promise(() => Deno.connect({ hostname: NetAddress.formatIp(address.address), port: address.port })),
94 (conn) => Effect.sync(() => conn.close())
95 )
96 void conn
97
98 yield* Scope.close(scope, Exit.void).pipe(Effect.timeout("1 second"))
99 }))
100
101 it.effect("upgrades a plain server connection to TLS", () =>
102 Effect.gen(function*() {
103 const server = yield* DenoSocketServer.make({ host: "127.0.0.1", port: 0 })
104 const address = server.address
105 assert.strictEqual(address._tag, "InetAddressV4")
106 if (address._tag !== "InetAddressV4") return
107 yield* server.run((socket) => echo(socket, { cert, key: Redacted.make(key) })).pipe(Effect.forkScoped)
108
109 const socket = yield* DenoSocket.makeTcp({
110 hostname: NetAddress.formatIp(address.address),
111 port: address.port
112 })
113 const output = yield* sendHelloDeno(socket, {
114 ca: [ca]
115 })
116
117 assert.strictEqual(output, "HelloDeno")
118 }))
119})