Skip to content

Commit 2b0c24d

Browse files
committed
test(sdk): cover createStartSessionAction generic + clientData fold
Three runtime cases and two type assertions: - `clientData` flows into `triggerConfig.basePayload.metadata` so `onPreload` / `onChatStart` see the same shape per-turn `metadata` carries via the transport. - Omitting `clientData` leaves `basePayload.metadata` unset (no empty-object pollution). - Session-level `metadata` stays distinct from per-turn `clientData` on the session row. - The generic narrows `clientData` against the agent's `clientDataSchema`. - Without a generic, `clientData` defaults to `unknown`. All 224 tests in the SDK suite still pass.
1 parent 3050c89 commit 2b0c24d

1 file changed

Lines changed: 138 additions & 0 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { afterEach, describe, expect, expectTypeOf, it } from "vitest";
2+
import { z } from "zod";
3+
import type { CreateSessionRequestBody, CreatedSessionResponseBody } from "@trigger.dev/core/v3";
4+
5+
import { chat } from "./ai.js";
6+
import {
7+
__setSessionStartImplForTests,
8+
__setSessionOpenImplForTests,
9+
SessionHandle,
10+
} from "./sessions.js";
11+
import { apiClientManager } from "@trigger.dev/core/v3";
12+
13+
// `auth.createPublicToken` is called by the action when no start token is
14+
// supplied. Provide a minimal API client config so the mint path doesn't
15+
// throw before we get to assert the captured request body.
16+
apiClientManager.setGlobalAPIClientConfiguration({
17+
baseURL: "https://example.invalid",
18+
accessToken: "tr_test_secret",
19+
});
20+
21+
// Capture the request body the action would send to `sessions.start()`.
22+
let lastStartBody: CreateSessionRequestBody | undefined;
23+
24+
function installStartFixture() {
25+
__setSessionStartImplForTests(async (body): Promise<CreatedSessionResponseBody> => {
26+
lastStartBody = body;
27+
return {
28+
id: "session_fixture",
29+
externalId: body.externalId ?? null,
30+
type: body.type,
31+
taskIdentifier: body.taskIdentifier,
32+
triggerConfig: body.triggerConfig,
33+
currentRunId: "run_fixture",
34+
tags: body.triggerConfig.tags ?? [],
35+
metadata: body.metadata ?? null,
36+
closedAt: null,
37+
closedReason: null,
38+
expiresAt: null,
39+
createdAt: new Date(),
40+
updatedAt: new Date(),
41+
runId: "run_fixture",
42+
publicAccessToken: "tr_pat_fixture",
43+
isCached: false,
44+
};
45+
});
46+
__setSessionOpenImplForTests(() => new SessionHandle("session_fixture"));
47+
}
48+
49+
afterEach(() => {
50+
__setSessionStartImplForTests(undefined);
51+
__setSessionOpenImplForTests(undefined);
52+
lastStartBody = undefined;
53+
});
54+
55+
// Build a fake chat agent task shape that the generic can narrow against.
56+
// We only need the static type — the runtime never invokes this task because
57+
// `__setSessionStartImplForTests` intercepts the network call.
58+
const fakeChat = chat
59+
.withClientData({
60+
schema: z.object({
61+
userId: z.string(),
62+
plan: z.enum(["free", "pro"]),
63+
}),
64+
})
65+
.agent({
66+
id: "fake-chat",
67+
run: async () => undefined as any,
68+
});
69+
70+
describe("chat.createStartSessionAction — runtime", () => {
71+
it("folds typed clientData into basePayload.metadata so onChatStart sees it on the first turn", async () => {
72+
installStartFixture();
73+
74+
const start = chat.createStartSessionAction<typeof fakeChat>("fake-chat");
75+
76+
const result = await start({
77+
chatId: "chat-1",
78+
clientData: { userId: "u-1", plan: "pro" },
79+
});
80+
81+
expect(result.publicAccessToken).toBe("tr_pat_fixture");
82+
expect(lastStartBody?.triggerConfig.basePayload).toMatchObject({
83+
messages: [],
84+
trigger: "preload",
85+
metadata: { userId: "u-1", plan: "pro" },
86+
chatId: "chat-1",
87+
});
88+
});
89+
90+
it("leaves basePayload.metadata unset when clientData is not provided", async () => {
91+
installStartFixture();
92+
93+
const start = chat.createStartSessionAction("fake-chat");
94+
await start({ chatId: "chat-2" });
95+
96+
expect(lastStartBody?.triggerConfig.basePayload).not.toHaveProperty("metadata");
97+
});
98+
99+
it("keeps session-level metadata distinct from per-turn clientData", async () => {
100+
installStartFixture();
101+
102+
const start = chat.createStartSessionAction<typeof fakeChat>("fake-chat");
103+
await start({
104+
chatId: "chat-3",
105+
clientData: { userId: "u-3", plan: "free" },
106+
metadata: { source: "marketing-site" },
107+
});
108+
109+
// Per-turn shape (visible to onPreload / onChatStart):
110+
expect(lastStartBody?.triggerConfig.basePayload).toMatchObject({
111+
metadata: { userId: "u-3", plan: "free" },
112+
});
113+
// Session-row metadata (opaque, never typed via clientDataSchema):
114+
expect(lastStartBody?.metadata).toEqual({ source: "marketing-site" });
115+
});
116+
});
117+
118+
describe("chat.createStartSessionAction — types", () => {
119+
it("narrows clientData against the chat agent's clientDataSchema", () => {
120+
const start = chat.createStartSessionAction<typeof fakeChat>("fake-chat");
121+
122+
// The clientData field is typed off the agent's schema.
123+
expectTypeOf(start).parameter(0).toHaveProperty("clientData");
124+
expectTypeOf(start).parameter(0).extract<{ clientData?: any }>().toEqualTypeOf<{
125+
chatId: string;
126+
clientData?: { userId: string; plan: "free" | "pro" };
127+
triggerConfig?: any;
128+
metadata?: Record<string, unknown>;
129+
}>();
130+
});
131+
132+
it("defaults clientData to unknown when called without a generic", () => {
133+
const start = chat.createStartSessionAction("fake-chat");
134+
expectTypeOf(start).parameter(0).toHaveProperty("clientData");
135+
// Untyped variant — clientData is `unknown`.
136+
expectTypeOf<Parameters<typeof start>[0]["clientData"]>().toEqualTypeOf<unknown>();
137+
});
138+
});

0 commit comments

Comments
 (0)