1import { assert, describe, it } from "@effect/vitest"
2import { Cause, Context, Effect, Fiber, Option, Stream } from "effect"
3// @ts-ignore
4// oxlint-disable-next-line @typescript-eslint/no-unused-vars
5import { NodeInspectSymbol } from "effect/Inspectable"
6import * as Layer from "effect/Layer"
7import * as RpcClient from "effect/rpc/RpcClient"
8import type { RpcClientError } from "effect/rpc/RpcClientError"
9import type * as RpcGroup from "effect/rpc/RpcGroup"
10import * as RpcServer from "effect/rpc/RpcServer"
11import * as RpcTest from "effect/rpc/RpcTest"
12import { AuthClient, AuthLayer, TimingLayer, User, UserRpcs, UsersLayer } from "./rpc-schemas.ts"
13
14export class UsersClient extends Context.Service<
15 UsersClient,
16 RpcClient.RpcClient<RpcGroup.Rpcs<typeof UserRpcs>, RpcClientError>
17>()("UsersClient") {
18 static readonly layer = Layer.effect(UsersClient)(RpcClient.make(UserRpcs)).pipe(
19 Layer.provide(AuthClient)
20 )
21 static layerTest = Layer.effect(UsersClient)(RpcTest.makeClient(UserRpcs)).pipe(
22 Layer.provide([UsersLayer, AuthLayer, TimingLayer, AuthClient])
23 )
24}
25
26export const e2eSuite = <E>(
27 name: string,
28 layer: Layer.Layer<UsersClient | RpcServer.Protocol, E>,
29 concurrent = true
30) => {
31 describe(name, { concurrent, timeout: 30_000 }, () => {
32 it.effect("should get user", () =>
33 Effect.gen(function*() {
34 const client = yield* UsersClient
35 const user = yield* client.GetUser({ id: "1" })
36 assert.instanceOf(user, User)
37 assert.deepStrictEqual(user, new User({ id: "1", name: "Logged in user" }))
38 }).pipe(Effect.provide(layer)))
39
40 it.effect("nested method", () =>
41 Effect.gen(function*() {
42 const client = yield* UsersClient
43 yield* client["nested.test"]()
44 }).pipe(Effect.provide(layer)))
45
46 it.effect("should not flatten Option", () =>
47 Effect.gen(function*() {
48 const client = yield* UsersClient
49 const user = yield* client.GetUserOption({ id: "1" })
50 assert.deepStrictEqual(user, Option.some(new User({ id: "1", name: "John" })))
51 }).pipe(Effect.provide(layer)))
52
53 it.effect("headers", () =>
54 Effect.gen(function*() {
55 const client = yield* UsersClient
56 const user = yield* client.GetUser({ id: "1" })
57 assert.instanceOf(user, User)
58 assert.deepStrictEqual(user, new User({ id: "123", name: "Logged in user" }))
59 }).pipe(
60 RpcClient.withHeaders({ userId: "123" }),
61 Effect.provide(layer)
62 ))
63
64 it.live("Stream", () =>
65 Effect.gen(function*() {
66 const client = yield* UsersClient
67 const users: Array<User> = []
68 const fiber = yield* client.StreamUsers({ id: "1" }).pipe(
69 Stream.take(5),
70 Stream.runForEach((user) =>
71 Effect.sync(() => {
72 users.push(user)
73 })
74 ),
75 Effect.forkChild
76 )
77
78 yield* Fiber.join(fiber)
79 assert.lengthOf(users, 5)
80
81 // test interrupts
82 const interrupts = yield* client.GetInterrupts()
83 assert.equal(interrupts, 1)
84
85 const { supportsAck } = yield* RpcServer.Protocol
86
87 // test backpressure
88 if (supportsAck) {
89 const emits = yield* client.GetEmits()
90 assert.equal(emits, 5)
91 }
92 }).pipe(Effect.provide(layer)), { timeout: 20000 })
93
94 it.effect("defect", () =>
95 Effect.gen(function*() {
96 const client = yield* UsersClient
97 const cause = yield* client.ProduceDefect().pipe(
98 Effect.sandbox,
99 Effect.flip
100 )
101 assert.deepStrictEqual(cause, Cause.die("boom"))
102 }).pipe(
103 RpcClient.withHeaders({ userId: "123" }),
104 Effect.provide(layer)
105 ))
106
107 it.live("never", () =>
108 Effect.gen(function*() {
109 const client = yield* UsersClient
110 const fiber = yield* client.Never().pipe(
111 Effect.forkChild
112 )
113 yield* Effect.sleep(500)
114 assert.isUndefined(fiber.pollUnsafe())
115
116 yield* Fiber.interrupt(fiber)
117 yield* Effect.sleep(100)
118
119 const { supportsAck } = yield* RpcServer.Protocol
120 if (supportsAck) {
121 const interrupts = yield* client.GetInterrupts()
122 assert.equal(interrupts, 1)
123 }
124 }).pipe(
125 RpcClient.withHeaders({ userId: "123" }),
126 Effect.provide(layer)
127 ))
128
129 it.effect("timing middleware", () =>
130 Effect.gen(function*() {
131 const client = yield* UsersClient
132 const result = yield* client.TimedMethod({ shouldFail: false })
133 assert.equal(result, 1)
134 yield* client.TimedMethod({ shouldFail: true }).pipe(Effect.exit)
135 const { count, defect, success } = yield* client.GetTimingMiddlewareMetrics()
136 assert.notEqual(count, 0)
137 assert.notEqual(defect, 0)
138 assert.notEqual(success, 0)
139 }).pipe(Effect.provide(layer)))
140 })
141}