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