Skip to content
Source
Sign inSign up
Open History to see the last commit

LiveActivities.test.ts

138 lines4.6 KB

1import type {
2 RelayAgentActivityAggregateState,
3 RelayLiveActivityRegistrationRequest,
4} from "@t3tools/contracts/relay";
5import { describe, expect, it } from "@effect/vitest";
6import * as Effect from "effect/Effect";
7import * as Layer from "effect/Layer";
8​
9import * as RelayDb from "../db.ts";
10import { relayLiveActivities } from "../persistence/schema.ts";
11import * as LiveActivities from "./LiveActivities.ts";
12​
13const aggregate: RelayAgentActivityAggregateState = {
14 title: "T3 Code",
15 subtitle: "Agent work in progress",
16 activeCount: 1,
17 updatedAt: "2026-05-25T00:00:00.000Z",
18 activities: [
19 {
20 environmentId:
21 "env" as RelayAgentActivityAggregateState["activities"][number]["environmentId"],
22 threadId: "thread" as RelayAgentActivityAggregateState["activities"][number]["threadId"],
23 projectTitle: "Project",
24 threadTitle: "Thread",
25 modelTitle: "gpt-5.4",
26 phase: "running",
27 status: "Working",
28 updatedAt: "2026-05-25T00:00:00.000Z",
29 deepLink: "/threads/env/thread",
30 },
31 ],
32};
33​
34describe("LiveActivities", () => {
35 it.effect("keeps the arming time when the current card re-registers its token", () => {
36 const armedAt = "2026-05-25T00:00:00.000Z";
37 const insertedValues: Array<Record<string, unknown>> = [];
38 const conflictSets: Array<Record<string, unknown> | undefined> = [];
39 const fakeDb = {
40 select: () => ({
41 from: () => ({
42 where: () =>
43 Effect.succeed([{ activityPushToken: "current-token", remoteStartedAt: armedAt }]),
44 }),
45 }),
46 update: () => ({ set: () => ({ where: () => Effect.void }) }),
47 insert: () => ({
48 values: (values: Record<string, unknown>) => {
49 insertedValues.push(values);
50 return {
51 onConflictDoUpdate: (config: { readonly set?: Record<string, unknown> }) => {
52 conflictSets.push(config.set);
53 return Effect.void;
54 },
55 };
56 },
57 }),
58 } as unknown as RelayDb.RelayDb["Service"];
59 const register = (activityPushToken: string) =>
60 Effect.flatMap(LiveActivities.LiveActivities, (liveActivities) =>
61 liveActivities.register({
62 userId: "user-1",
63 registration: {
64 deviceId: "device-1" as RelayLiveActivityRegistrationRequest["deviceId"],
65 activityPushToken:
66 activityPushToken as RelayLiveActivityRegistrationRequest["activityPushToken"],
67 },
68 }),
69 );
70​
71 return Effect.gen(function* () {
72 yield* register("current-token");
73 yield* register("new-card-token");
74​
75 expect(insertedValues[0]).toMatchObject({ remoteStartedAt: armedAt });
76 expect(conflictSets[0]).toMatchObject({ remoteStartedAt: armedAt, lastAggregateJson: null });
77 expect(insertedValues[1]?.remoteStartedAt).not.toBe(armedAt);
78 expect(conflictSets[1]?.remoteStartedAt).not.toBe(armedAt);
79 }).pipe(
80 Effect.provide(
81 LiveActivities.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))),
82 ),
83 );
84 });
85​
86 it.effect("preserves ended state when a delayed update delivery is marked", () => {
87 const insertedValues: Array<Record<string, unknown>> = [];
88 const conflictConfigs: Array<{
89 readonly set?: Record<string, unknown>;
90 }> = [];
91​
92 const fakeDb = {
93 insert: (table: unknown) => {
94 expect(table).toBe(relayLiveActivities);
95 return {
96 values: (values: Record<string, unknown>) => {
97 insertedValues.push(values);
98 return {
99 onConflictDoUpdate: (config: { readonly set?: Record<string, unknown> }) => {
100 conflictConfigs.push(config);
101 return Effect.void;
102 },
103 };
104 },
105 };
106 },
107 } as unknown as RelayDb.RelayDb["Service"];
108​
109 return Effect.gen(function* () {
110 const liveActivities = yield* LiveActivities.LiveActivities;
111 yield* liveActivities.markDelivery({
112 userId: "user-2",
113 deviceId: "device-1",
114 kind: "live_activity_update",
115 aggregate,
116 deliveredAt: "2026-05-25T00:00:10.000Z",
117 });
118​
119 expect(insertedValues).toEqual([
120 expect.objectContaining({
121 userId: "user-2",
122 deviceId: "device-1",
123 endedAt: null,
124 }),
125 ]);
126 expect(conflictConfigs[0]?.set).toEqual(
127 expect.objectContaining({
128 endedAt: relayLiveActivities.endedAt,
129 lastLiveActivityDeliveryAt: "2026-05-25T00:00:10.000Z",
130 }),
131 );
132 }).pipe(
133 Effect.provide(
134 LiveActivities.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))),
135 ),
136 );
137 });
138});