1import { assert, describe, it } from "@effect/vitest"
2import { Effect, PrimaryKey, Schema } from "effect"
3import { ClusterSchema, Entity } from "effect/cluster"
4import { Rpc } from "effect/rpc"
5import { type Backend, make } from "./harness.ts"
6
7class Ping extends Schema.Class<Ping>("Ping")({
8 id: Schema.String
9}) {
10 [PrimaryKey.symbol]() {
11 return this.id
12 }
13}
14
15const TestEntity = Entity.make("ClusterIntegrationSmoke", [
16 Rpc.make("Ping", {
17 payload: Ping,
18 success: Schema.String
19 })
20]).annotateRpcs(ClusterSchema.Persisted, true)
21
22const TestEntityLayer = TestEntity.toLayer({
23 Ping: ({ payload }) => Effect.succeed(`pong:${payload.id}`)
24})
25
26describe("cluster integration smoke", () => {
27 for (const backend of ["pg", "mysql"] satisfies ReadonlyArray<Backend>) {
28 it.live(`${backend}: persists messages and rebalances after an abrupt runner death`, () =>
29 Effect.gen(function*() {
30 const cluster = yield* make({ backend, entities: TestEntityLayer })
31 yield* cluster.start(3)
32 yield* cluster.waitForStableAssignments()
33
34 const client = yield* cluster.getClient(TestEntity)
35 assert.strictEqual(
36 yield* client("entity-1").Ping(new Ping({ id: "request-1" })),
37 "pong:request-1"
38 )
39
40 yield* cluster.waitUntil(
41 "The smoke entity had no active owner",
42 Effect.map(cluster.ownerOfEntity(TestEntity, "entity-1"), (owner) => owner !== undefined)
43 )
44 const owner = yield* cluster.ownerOfEntity(TestEntity, "entity-1")
45 yield* cluster.kill(owner!)
46 yield* cluster.waitForStableAssignments()
47
48 assert.strictEqual(
49 yield* client("entity-1").Ping(new Ping({ id: "request-2" })),
50 "pong:request-2"
51 )
52 assert.strictEqual(yield* cluster.repliedMessageCount, 2)
53 }))
54 }
55})