@stigmer/react 3.1.8 → 3.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/environment/EnvironmentPicker.d.ts +17 -1
- package/environment/EnvironmentPicker.d.ts.map +1 -1
- package/environment/EnvironmentPicker.js +3 -2
- package/environment/EnvironmentPicker.js.map +1 -1
- package/index.d.ts +2 -2
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/sharing/ShareAgentDialog.d.ts +10 -8
- package/sharing/ShareAgentDialog.d.ts.map +1 -1
- package/sharing/ShareAgentDialog.js +139 -54
- package/sharing/ShareAgentDialog.js.map +1 -1
- package/sharing/SharedAgentChat.d.ts +1 -1
- package/sharing/SharedAgentChat.d.ts.map +1 -1
- package/sharing/index.d.ts +4 -2
- package/sharing/index.d.ts.map +1 -1
- package/sharing/index.js +2 -1
- package/sharing/index.js.map +1 -1
- package/sharing/useAgentShare.d.ts +43 -0
- package/sharing/useAgentShare.d.ts.map +1 -0
- package/sharing/useAgentShare.js +54 -0
- package/sharing/useAgentShare.js.map +1 -0
- package/sharing/useRotateShareLink.d.ts +22 -19
- package/sharing/useRotateShareLink.d.ts.map +1 -1
- package/sharing/useRotateShareLink.js +22 -19
- package/sharing/useRotateShareLink.js.map +1 -1
- package/sharing/useSaveAgentShare.d.ts +105 -0
- package/sharing/useSaveAgentShare.d.ts.map +1 -0
- package/sharing/useSaveAgentShare.js +97 -0
- package/sharing/useSaveAgentShare.js.map +1 -0
- package/sharing/useShareAgent.d.ts +5 -4
- package/sharing/useShareAgent.d.ts.map +1 -1
- package/sharing/useShareAgent.js +5 -4
- package/sharing/useShareAgent.js.map +1 -1
- package/sharing/useShareToolReadiness.d.ts +29 -25
- package/sharing/useShareToolReadiness.d.ts.map +1 -1
- package/sharing/useShareToolReadiness.js +47 -51
- package/sharing/useShareToolReadiness.js.map +1 -1
- package/sharing/useSharedAgentProfile.d.ts +5 -4
- package/sharing/useSharedAgentProfile.d.ts.map +1 -1
- package/sharing/useSharedAgentProfile.js +6 -5
- package/sharing/useSharedAgentProfile.js.map +1 -1
- package/src/environment/EnvironmentPicker.tsx +23 -2
- package/src/index.ts +8 -4
- package/src/sharing/ShareAgentDialog.tsx +353 -119
- package/src/sharing/SharedAgentChat.tsx +1 -1
- package/src/sharing/__tests__/ShareAgentDialog.test.tsx +550 -369
- package/src/sharing/__tests__/useAgentShare.test.tsx +149 -0
- package/src/sharing/__tests__/useRotateShareLink.test.tsx +8 -8
- package/src/sharing/__tests__/useSaveAgentShare.test.tsx +241 -0
- package/src/sharing/__tests__/useShareAgent.test.tsx +6 -2
- package/src/sharing/__tests__/useShareToolReadiness.test.tsx +84 -47
- package/src/sharing/__tests__/useSharedAgentProfile.test.tsx +1 -1
- package/src/sharing/index.ts +9 -7
- package/src/sharing/useAgentShare.ts +91 -0
- package/src/sharing/useRotateShareLink.ts +30 -25
- package/src/sharing/useSaveAgentShare.ts +186 -0
- package/src/sharing/useShareAgent.tsx +5 -4
- package/src/sharing/useShareToolReadiness.ts +62 -60
- package/src/sharing/useSharedAgentProfile.ts +7 -6
- package/src/switch/Switch.tsx +6 -1
- package/src/switch/__tests__/Switch.test.tsx +19 -0
- package/switch/Switch.d.ts.map +1 -1
- package/switch/Switch.js +6 -1
- package/switch/Switch.js.map +1 -1
- package/sharing/useUpdateAgentSharing.d.ts +0 -86
- package/sharing/useUpdateAgentSharing.d.ts.map +0 -1
- package/sharing/useUpdateAgentSharing.js +0 -81
- package/sharing/useUpdateAgentSharing.js.map +0 -1
- package/src/sharing/__tests__/useUpdateAgentSharing.test.tsx +0 -200
- package/src/sharing/useUpdateAgentSharing.ts +0 -151
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
|
|
2
2
|
import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react";
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
4
|
+
import { AgentShareAudience } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/spec_pb";
|
|
5
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
6
|
+
import type { AgentShareInput } from "@stigmer/sdk";
|
|
6
7
|
import { StigmerContext } from "../../context";
|
|
7
8
|
import { FetchCacheContext } from "../../internal/FetchCacheProvider";
|
|
8
9
|
import { DeploymentModeContext } from "../../deployment-mode";
|
|
@@ -26,18 +27,41 @@ beforeAll(() => {
|
|
|
26
27
|
afterEach(cleanup);
|
|
27
28
|
|
|
28
29
|
interface MockOverrides {
|
|
29
|
-
|
|
30
|
+
getByAgent?: (input: unknown) => Promise<unknown>;
|
|
31
|
+
apply?: (input: AgentShareInput) => Promise<unknown>;
|
|
30
32
|
rotateShareLink?: (input: unknown) => Promise<unknown>;
|
|
31
33
|
getOrCreateBillingAccount?: (orgId: string) => Promise<unknown>;
|
|
34
|
+
environments?: unknown[];
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
function createMockStigmer(overrides: MockOverrides = {}) {
|
|
35
38
|
return {
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
agentShare: {
|
|
40
|
+
getByAgent:
|
|
41
|
+
overrides.getByAgent ??
|
|
42
|
+
vi.fn().mockResolvedValue({ totalCount: 0, items: [] }),
|
|
43
|
+
apply: overrides.apply ?? vi.fn().mockResolvedValue({}),
|
|
38
44
|
rotateShareLink:
|
|
39
45
|
overrides.rotateShareLink ?? vi.fn().mockResolvedValue({}),
|
|
40
46
|
},
|
|
47
|
+
environment: {
|
|
48
|
+
list: vi.fn().mockResolvedValue({
|
|
49
|
+
items: overrides.environments ?? [],
|
|
50
|
+
totalCount: overrides.environments?.length ?? 0,
|
|
51
|
+
}),
|
|
52
|
+
// The readiness hook checks bound refs' visibility; the picker's
|
|
53
|
+
// list above is the source of the fixtures, so resolve from it.
|
|
54
|
+
getByReference: vi
|
|
55
|
+
.fn()
|
|
56
|
+
.mockImplementation(({ slug }: { org: string; slug: string }) => {
|
|
57
|
+
const env = (overrides.environments ?? []).find(
|
|
58
|
+
(e) => (e as { metadata?: { slug?: string } }).metadata?.slug === slug,
|
|
59
|
+
);
|
|
60
|
+
return env
|
|
61
|
+
? Promise.resolve(env)
|
|
62
|
+
: Promise.reject(new Error("not found"));
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
41
65
|
billing: {
|
|
42
66
|
getOrCreateBillingAccount:
|
|
43
67
|
overrides.getOrCreateBillingAccount ??
|
|
@@ -66,41 +90,96 @@ function Providers({
|
|
|
66
90
|
);
|
|
67
91
|
}
|
|
68
92
|
|
|
69
|
-
function makeAgent(
|
|
70
|
-
|
|
93
|
+
function makeAgent(overrides?: { mcpUsages?: boolean }) {
|
|
94
|
+
return {
|
|
95
|
+
metadata: {
|
|
96
|
+
id: "agt_1",
|
|
97
|
+
org: "acme",
|
|
98
|
+
slug: "support-agent",
|
|
99
|
+
name: "Support Agent",
|
|
100
|
+
},
|
|
101
|
+
spec: {
|
|
102
|
+
mcpServerUsages: overrides?.mcpUsages
|
|
103
|
+
? [{ mcpServerRef: { org: "acme", slug: "github" } }]
|
|
104
|
+
: [],
|
|
105
|
+
},
|
|
106
|
+
} as never;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function makeShare(
|
|
110
|
+
spec?: {
|
|
71
111
|
enabled?: boolean;
|
|
72
|
-
audience?:
|
|
112
|
+
audience?: AgentShareAudience;
|
|
73
113
|
allowedOrigins?: string[];
|
|
74
114
|
messages?: {
|
|
75
115
|
rateLimited?: string;
|
|
76
116
|
unavailable?: string;
|
|
77
117
|
conversationEnded?: string;
|
|
78
118
|
};
|
|
119
|
+
environmentRefs?: { org: string; slug: string }[];
|
|
79
120
|
},
|
|
80
121
|
shareLinkToken?: string,
|
|
81
122
|
) {
|
|
82
123
|
return {
|
|
83
124
|
metadata: {
|
|
84
|
-
id: "
|
|
125
|
+
id: "ash_1",
|
|
85
126
|
org: "acme",
|
|
86
127
|
slug: "support-agent",
|
|
87
128
|
name: "Support Agent",
|
|
88
129
|
},
|
|
89
|
-
spec: {
|
|
90
|
-
...(shareLinkToken !== undefined
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
130
|
+
spec: { agentRef: { org: "acme", slug: "support-agent" }, ...spec },
|
|
131
|
+
...(shareLinkToken !== undefined ? { status: { shareLinkToken } } : {}),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function withShare(share: unknown) {
|
|
136
|
+
return vi.fn().mockResolvedValue({ totalCount: 1, items: [share] });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function orgSharedEnv(slug: string, name?: string) {
|
|
140
|
+
return {
|
|
141
|
+
metadata: {
|
|
142
|
+
slug,
|
|
143
|
+
name: name ?? slug,
|
|
144
|
+
visibility: ApiResourceVisibility.visibility_org,
|
|
145
|
+
},
|
|
146
|
+
spec: { description: "" },
|
|
147
|
+
};
|
|
94
148
|
}
|
|
95
149
|
|
|
96
150
|
const buildShareUrl = (org: string, slug: string) =>
|
|
97
151
|
`https://app.example.com/chat/${org}/${slug}`;
|
|
98
152
|
|
|
153
|
+
/** Render the open dialog and wait for the share load to settle. */
|
|
154
|
+
async function renderOpenDialog(
|
|
155
|
+
client: unknown,
|
|
156
|
+
props?: Partial<Parameters<typeof ShareAgentDialog>[0]> & { mode?: "cloud" | "local" },
|
|
157
|
+
) {
|
|
158
|
+
const { mode, ...dialogProps } = props ?? {};
|
|
159
|
+
render(
|
|
160
|
+
<Providers client={client} mode={mode}>
|
|
161
|
+
<ShareAgentDialog
|
|
162
|
+
open
|
|
163
|
+
onOpenChange={() => {}}
|
|
164
|
+
agent={makeAgent()}
|
|
165
|
+
buildShareUrl={buildShareUrl}
|
|
166
|
+
{...dialogProps}
|
|
167
|
+
/>
|
|
168
|
+
</Providers>,
|
|
169
|
+
);
|
|
170
|
+
await waitFor(() =>
|
|
171
|
+
expect(screen.queryByLabelText("Loading sharing settings")).toBeNull(),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
99
175
|
describe("ShareAgentDialog", () => {
|
|
100
|
-
it("mounts no body while closed (billing
|
|
176
|
+
it("mounts no body while closed (share and billing fetches stay lazy)", () => {
|
|
177
|
+
const getByAgent = vi.fn();
|
|
101
178
|
const getOrCreateBillingAccount = vi.fn();
|
|
102
179
|
render(
|
|
103
|
-
<Providers
|
|
180
|
+
<Providers
|
|
181
|
+
client={createMockStigmer({ getByAgent, getOrCreateBillingAccount })}
|
|
182
|
+
>
|
|
104
183
|
<ShareAgentDialog
|
|
105
184
|
open={false}
|
|
106
185
|
onOpenChange={() => {}}
|
|
@@ -111,54 +190,180 @@ describe("ShareAgentDialog", () => {
|
|
|
111
190
|
);
|
|
112
191
|
|
|
113
192
|
expect(screen.queryByText("Share")).toBeNull();
|
|
193
|
+
expect(getByAgent).not.toHaveBeenCalled();
|
|
114
194
|
expect(getOrCreateBillingAccount).not.toHaveBeenCalled();
|
|
115
195
|
});
|
|
116
196
|
|
|
117
|
-
it("
|
|
197
|
+
it("shows a loading state while the share resolves, then the form", async () => {
|
|
198
|
+
let resolveLoad: (v: unknown) => void = () => {};
|
|
199
|
+
const getByAgent = vi.fn().mockReturnValue(
|
|
200
|
+
new Promise((resolve) => {
|
|
201
|
+
resolveLoad = resolve;
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
118
204
|
render(
|
|
119
|
-
<Providers client={createMockStigmer()}>
|
|
205
|
+
<Providers client={createMockStigmer({ getByAgent })}>
|
|
120
206
|
<ShareAgentDialog
|
|
121
207
|
open
|
|
122
208
|
onOpenChange={() => {}}
|
|
123
|
-
agent={makeAgent(
|
|
209
|
+
agent={makeAgent()}
|
|
124
210
|
buildShareUrl={buildShareUrl}
|
|
125
211
|
/>
|
|
126
212
|
</Providers>,
|
|
127
213
|
);
|
|
128
214
|
|
|
129
|
-
expect(screen.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
215
|
+
expect(screen.getByLabelText("Loading sharing settings")).toBeTruthy();
|
|
216
|
+
resolveLoad({ totalCount: 1, items: [makeShare({ enabled: true })] });
|
|
217
|
+
await waitFor(() =>
|
|
218
|
+
expect(
|
|
219
|
+
screen.getByText("https://app.example.com/chat/acme/support-agent"),
|
|
220
|
+
).toBeTruthy(),
|
|
221
|
+
);
|
|
135
222
|
});
|
|
136
223
|
|
|
137
|
-
it("
|
|
224
|
+
it("offers retry when the share load fails", async () => {
|
|
225
|
+
const share = makeShare({ enabled: true });
|
|
226
|
+
const getByAgent = vi
|
|
227
|
+
.fn()
|
|
228
|
+
.mockRejectedValueOnce(new Error("backend unavailable"))
|
|
229
|
+
.mockResolvedValueOnce({ totalCount: 1, items: [share] });
|
|
138
230
|
render(
|
|
139
|
-
<Providers client={createMockStigmer()}>
|
|
231
|
+
<Providers client={createMockStigmer({ getByAgent })}>
|
|
140
232
|
<ShareAgentDialog
|
|
141
233
|
open
|
|
142
234
|
onOpenChange={() => {}}
|
|
143
|
-
agent={makeAgent(
|
|
235
|
+
agent={makeAgent()}
|
|
236
|
+
buildShareUrl={buildShareUrl}
|
|
144
237
|
/>
|
|
145
238
|
</Providers>,
|
|
146
239
|
);
|
|
147
240
|
|
|
241
|
+
// A failed load must never render a form that would create a share
|
|
242
|
+
// over an existing one the dialog couldn't see.
|
|
243
|
+
expect(
|
|
244
|
+
await screen.findByText(/couldn't load this agent's sharing settings/i),
|
|
245
|
+
).toBeTruthy();
|
|
246
|
+
|
|
247
|
+
fireEvent.click(screen.getByRole("button", { name: "Try again", hidden: true }));
|
|
248
|
+
await waitFor(() =>
|
|
249
|
+
expect(
|
|
250
|
+
screen.getByText("https://app.example.com/chat/acme/support-agent"),
|
|
251
|
+
).toBeTruthy(),
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("renders header, toggle, and the share link from buildShareUrl", async () => {
|
|
256
|
+
const client = createMockStigmer({
|
|
257
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
258
|
+
});
|
|
259
|
+
await renderOpenDialog(client);
|
|
260
|
+
|
|
261
|
+
expect(screen.getByText("Share")).toBeTruthy();
|
|
262
|
+
expect(screen.getByText("Support Agent")).toBeTruthy();
|
|
263
|
+
expect(screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked")).toBe("true");
|
|
264
|
+
expect(
|
|
265
|
+
screen.getByText("https://app.example.com/chat/acme/support-agent"),
|
|
266
|
+
).toBeTruthy();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("builds the link from the SHARE's slug when it differs from the agent's", async () => {
|
|
270
|
+
// A manifest-renamed share: the hosted URL lives at the share's slug.
|
|
271
|
+
const renamed = {
|
|
272
|
+
...makeShare({ enabled: true }),
|
|
273
|
+
metadata: { id: "ash_1", org: "acme", slug: "help-desk", name: "Help Desk" },
|
|
274
|
+
};
|
|
275
|
+
const client = createMockStigmer({ getByAgent: withShare(renamed) });
|
|
276
|
+
await renderOpenDialog(client);
|
|
277
|
+
|
|
278
|
+
expect(
|
|
279
|
+
screen.getByText("https://app.example.com/chat/acme/help-desk"),
|
|
280
|
+
).toBeTruthy();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("falls back to the relative /chat path when buildShareUrl is omitted", async () => {
|
|
284
|
+
const client = createMockStigmer({
|
|
285
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
286
|
+
});
|
|
287
|
+
await renderOpenDialog(client, { buildShareUrl: undefined });
|
|
288
|
+
|
|
148
289
|
expect(screen.getByText("/chat/acme/support-agent")).toBeTruthy();
|
|
149
290
|
});
|
|
150
291
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
292
|
+
describe("never-shared agent (no share yet)", () => {
|
|
293
|
+
it("renders the off state with disabled copy affordances", async () => {
|
|
294
|
+
const client = createMockStigmer();
|
|
295
|
+
await renderOpenDialog(client);
|
|
296
|
+
|
|
297
|
+
expect(
|
|
298
|
+
screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked"),
|
|
299
|
+
).toBe("false");
|
|
300
|
+
const copy = screen.getByRole("button", {
|
|
301
|
+
name: "Copy",
|
|
302
|
+
hidden: true,
|
|
303
|
+
}) as HTMLButtonElement;
|
|
304
|
+
expect(copy.disabled).toBe(true);
|
|
305
|
+
expect(
|
|
306
|
+
screen.getByText(/sharing is off, so this link doesn't work yet/i),
|
|
307
|
+
).toBeTruthy();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("first enable creates the canonical share via apply", async () => {
|
|
311
|
+
const apply = vi.fn().mockResolvedValue(makeShare({ enabled: true }));
|
|
312
|
+
const client = createMockStigmer({ apply });
|
|
313
|
+
await renderOpenDialog(client);
|
|
314
|
+
|
|
315
|
+
fireEvent.click(screen.getByRole("switch", { hidden: true }));
|
|
316
|
+
|
|
317
|
+
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
318
|
+
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
319
|
+
// Create identity: the agent's own org/slug/name (the server's D2
|
|
320
|
+
// default made explicit) plus the agent reference.
|
|
321
|
+
expect(input.org).toBe("acme");
|
|
322
|
+
expect(input.slug).toBe("support-agent");
|
|
323
|
+
expect(input.agentRef).toEqual({ org: "acme", slug: "support-agent" });
|
|
324
|
+
expect(input.enabled).toBe(true);
|
|
325
|
+
|
|
326
|
+
await waitFor(() =>
|
|
327
|
+
expect(
|
|
328
|
+
screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked"),
|
|
329
|
+
).toBe("true"),
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe("Sharing-off state", () => {
|
|
335
|
+
it("shows the reason on the Embed tab too", async () => {
|
|
336
|
+
const client = createMockStigmer({
|
|
337
|
+
getByAgent: withShare(makeShare({ enabled: false })),
|
|
338
|
+
});
|
|
339
|
+
await renderOpenDialog(client);
|
|
340
|
+
|
|
341
|
+
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
342
|
+
expect(
|
|
343
|
+
screen.getByText(/sharing is off, so this embed doesn't work yet/i),
|
|
344
|
+
).toBeTruthy();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("drops the hint and enables copy once sharing is on", async () => {
|
|
348
|
+
const client = createMockStigmer({
|
|
349
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
350
|
+
});
|
|
351
|
+
await renderOpenDialog(client);
|
|
352
|
+
|
|
353
|
+
const copy = screen.getByRole("button", {
|
|
354
|
+
name: "Copy",
|
|
355
|
+
hidden: true,
|
|
356
|
+
}) as HTMLButtonElement;
|
|
357
|
+
expect(copy.disabled).toBe(false);
|
|
358
|
+
expect(screen.queryByText(/sharing is off/i)).toBeNull();
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("shows the indexability warning", async () => {
|
|
363
|
+
const client = createMockStigmer({
|
|
364
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
365
|
+
});
|
|
366
|
+
await renderOpenDialog(client);
|
|
162
367
|
|
|
163
368
|
expect(
|
|
164
369
|
screen.getByText(/forwarded and indexed by search engines/),
|
|
@@ -166,22 +371,19 @@ describe("ShareAgentDialog", () => {
|
|
|
166
371
|
});
|
|
167
372
|
|
|
168
373
|
describe("Audience", () => {
|
|
169
|
-
it("defaults to Public link and switching to Org members
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
/>
|
|
183
|
-
</Providers>,
|
|
184
|
-
);
|
|
374
|
+
it("defaults to Public link and switching to Org members applies the full spec", async () => {
|
|
375
|
+
const apply = vi.fn().mockResolvedValue({});
|
|
376
|
+
const client = createMockStigmer({
|
|
377
|
+
apply,
|
|
378
|
+
getByAgent: withShare(
|
|
379
|
+
makeShare({
|
|
380
|
+
enabled: true,
|
|
381
|
+
allowedOrigins: ["https://example.com"],
|
|
382
|
+
messages: { rateLimited: "Easy there." },
|
|
383
|
+
}),
|
|
384
|
+
),
|
|
385
|
+
});
|
|
386
|
+
await renderOpenDialog(client);
|
|
185
387
|
|
|
186
388
|
const publicOption = screen.getByRole("radio", {
|
|
187
389
|
name: "Public link",
|
|
@@ -193,30 +395,49 @@ describe("ShareAgentDialog", () => {
|
|
|
193
395
|
screen.getByRole("radio", { name: "Org members", hidden: true }),
|
|
194
396
|
);
|
|
195
397
|
|
|
196
|
-
await waitFor(() => expect(
|
|
197
|
-
const input =
|
|
198
|
-
expect(input.
|
|
199
|
-
// Whole-
|
|
200
|
-
expect(input.
|
|
201
|
-
expect(input.
|
|
202
|
-
expect(input.
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
398
|
+
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
399
|
+
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
400
|
+
expect(input.audience).toBe(AgentShareAudience.org);
|
|
401
|
+
// Whole-spec replace: the audience switch must not clobber the rest.
|
|
402
|
+
expect(input.enabled).toBe(true);
|
|
403
|
+
expect(input.allowedOrigins).toEqual(["https://example.com"]);
|
|
404
|
+
expect(input.messages?.rateLimited).toBe("Easy there.");
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("switching to Org members drops credential bindings (public-audience only)", async () => {
|
|
408
|
+
const apply = vi.fn().mockResolvedValue({});
|
|
409
|
+
const client = createMockStigmer({
|
|
410
|
+
apply,
|
|
411
|
+
getByAgent: withShare(
|
|
412
|
+
makeShare({
|
|
413
|
+
enabled: true,
|
|
414
|
+
environmentRefs: [{ org: "acme", slug: "github-creds" }],
|
|
415
|
+
}),
|
|
416
|
+
),
|
|
417
|
+
environments: [orgSharedEnv("github-creds")],
|
|
418
|
+
});
|
|
419
|
+
await renderOpenDialog(client);
|
|
420
|
+
|
|
421
|
+
fireEvent.click(
|
|
422
|
+
screen.getByRole("radio", { name: "Org members", hidden: true }),
|
|
218
423
|
);
|
|
219
424
|
|
|
425
|
+
// The proto CEL rule rejects environment_refs on org-audience
|
|
426
|
+
// shares — carrying them would fail the whole apply.
|
|
427
|
+
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
428
|
+
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
429
|
+
expect(input.audience).toBe(AgentShareAudience.org);
|
|
430
|
+
expect(input.environmentRefs).toEqual([]);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("renders org-audience copy: member link, revocation note, no indexability warning", async () => {
|
|
434
|
+
const client = createMockStigmer({
|
|
435
|
+
getByAgent: withShare(
|
|
436
|
+
makeShare({ enabled: true, audience: AgentShareAudience.org }),
|
|
437
|
+
),
|
|
438
|
+
});
|
|
439
|
+
await renderOpenDialog(client);
|
|
440
|
+
|
|
220
441
|
expect(screen.getByText("Organization members can chat")).toBeTruthy();
|
|
221
442
|
expect(screen.getByText("Member chat link")).toBeTruthy();
|
|
222
443
|
expect(
|
|
@@ -227,20 +448,13 @@ describe("ShareAgentDialog", () => {
|
|
|
227
448
|
).toBeNull();
|
|
228
449
|
});
|
|
229
450
|
|
|
230
|
-
it("replaces the Embed tab with a public-only explanation for the org audience", () => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
enabled: true,
|
|
238
|
-
audience: AgentSharingAudience.org,
|
|
239
|
-
})}
|
|
240
|
-
buildShareUrl={buildShareUrl}
|
|
241
|
-
/>
|
|
242
|
-
</Providers>,
|
|
243
|
-
);
|
|
451
|
+
it("replaces the Embed tab with a public-only explanation for the org audience", async () => {
|
|
452
|
+
const client = createMockStigmer({
|
|
453
|
+
getByAgent: withShare(
|
|
454
|
+
makeShare({ enabled: true, audience: AgentShareAudience.org }),
|
|
455
|
+
),
|
|
456
|
+
});
|
|
457
|
+
await renderOpenDialog(client);
|
|
244
458
|
|
|
245
459
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
246
460
|
expect(
|
|
@@ -250,56 +464,46 @@ describe("ShareAgentDialog", () => {
|
|
|
250
464
|
});
|
|
251
465
|
});
|
|
252
466
|
|
|
253
|
-
it("enabling
|
|
254
|
-
const
|
|
467
|
+
it("enabling applies the complete spec and notifies the host", async () => {
|
|
468
|
+
const apply = vi.fn().mockResolvedValue({});
|
|
255
469
|
const onSharingChanged = vi.fn();
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
onSharingChanged={onSharingChanged}
|
|
268
|
-
/>
|
|
269
|
-
</Providers>,
|
|
270
|
-
);
|
|
470
|
+
const client = createMockStigmer({
|
|
471
|
+
apply,
|
|
472
|
+
getByAgent: withShare(
|
|
473
|
+
makeShare({
|
|
474
|
+
enabled: false,
|
|
475
|
+
allowedOrigins: ["https://example.com"],
|
|
476
|
+
messages: { rateLimited: "Easy there." },
|
|
477
|
+
}),
|
|
478
|
+
),
|
|
479
|
+
});
|
|
480
|
+
await renderOpenDialog(client, { onSharingChanged });
|
|
271
481
|
|
|
272
482
|
fireEvent.click(screen.getByRole("switch", { hidden: true }));
|
|
273
483
|
|
|
274
|
-
await waitFor(() => expect(
|
|
275
|
-
const input =
|
|
276
|
-
expect(input.
|
|
277
|
-
|
|
278
|
-
// The RPC replaces spec.sharing wholesale — the toggle must carry the
|
|
484
|
+
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
485
|
+
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
486
|
+
expect(input.enabled).toBe(true);
|
|
487
|
+
// Apply replaces the spec wholesale — the toggle must carry the
|
|
279
488
|
// existing origins and messages or it would silently erase them.
|
|
280
|
-
expect(input.
|
|
281
|
-
expect(input.
|
|
489
|
+
expect(input.allowedOrigins).toEqual(["https://example.com"]);
|
|
490
|
+
expect(input.messages?.rateLimited).toBe("Easy there.");
|
|
282
491
|
await waitFor(() => expect(onSharingChanged).toHaveBeenCalled());
|
|
283
492
|
});
|
|
284
493
|
|
|
285
|
-
it("adopts the server's returned
|
|
286
|
-
// Server echoes the
|
|
287
|
-
const
|
|
288
|
-
|
|
494
|
+
it("adopts the server's returned share state after a commit", async () => {
|
|
495
|
+
// Server echoes the apply but with a normalized origin list.
|
|
496
|
+
const apply = vi.fn().mockResolvedValue(
|
|
497
|
+
makeShare({
|
|
289
498
|
enabled: true,
|
|
290
499
|
allowedOrigins: ["https://normalized.example.com"],
|
|
291
500
|
}),
|
|
292
501
|
);
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
agent={makeAgent({ enabled: false })}
|
|
299
|
-
buildShareUrl={buildShareUrl}
|
|
300
|
-
/>
|
|
301
|
-
</Providers>,
|
|
302
|
-
);
|
|
502
|
+
const client = createMockStigmer({
|
|
503
|
+
apply,
|
|
504
|
+
getByAgent: withShare(makeShare({ enabled: false })),
|
|
505
|
+
});
|
|
506
|
+
await renderOpenDialog(client);
|
|
303
507
|
|
|
304
508
|
fireEvent.click(screen.getByRole("switch", { hidden: true }));
|
|
305
509
|
await waitFor(() =>
|
|
@@ -312,18 +516,83 @@ describe("ShareAgentDialog", () => {
|
|
|
312
516
|
expect(screen.getByText("https://normalized.example.com")).toBeTruthy();
|
|
313
517
|
});
|
|
314
518
|
|
|
519
|
+
describe("Tool credentials", () => {
|
|
520
|
+
it("warns needs-credentials for a tool-using share without bindings", async () => {
|
|
521
|
+
const client = createMockStigmer({
|
|
522
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
523
|
+
});
|
|
524
|
+
await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
|
|
525
|
+
|
|
526
|
+
expect(
|
|
527
|
+
screen.getByText(/no credentials are bound to this share/i),
|
|
528
|
+
).toBeTruthy();
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("binds an org-shared environment by applying the appended refs", async () => {
|
|
532
|
+
const apply = vi.fn().mockResolvedValue({});
|
|
533
|
+
const client = createMockStigmer({
|
|
534
|
+
apply,
|
|
535
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
536
|
+
environments: [orgSharedEnv("github-creds", "GitHub Creds")],
|
|
537
|
+
});
|
|
538
|
+
await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
|
|
539
|
+
|
|
540
|
+
// The section is expanded by default for tool-using agents.
|
|
541
|
+
const picker = await screen.findByLabelText("Add environment", {
|
|
542
|
+
// The select is visually present inside the collapsible.
|
|
543
|
+
selector: "select",
|
|
544
|
+
});
|
|
545
|
+
fireEvent.change(picker, { target: { value: "github-creds" } });
|
|
546
|
+
|
|
547
|
+
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
548
|
+
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
549
|
+
expect(input.environmentRefs).toEqual([
|
|
550
|
+
{ org: "acme", slug: "github-creds" },
|
|
551
|
+
]);
|
|
552
|
+
// The binding rides the full spec — nothing else changes.
|
|
553
|
+
expect(input.enabled).toBe(true);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("offers only org-shared environments in the picker", async () => {
|
|
557
|
+
const privateEnv = {
|
|
558
|
+
metadata: {
|
|
559
|
+
slug: "personal-creds",
|
|
560
|
+
name: "Personal Creds",
|
|
561
|
+
visibility: ApiResourceVisibility.visibility_private,
|
|
562
|
+
},
|
|
563
|
+
spec: { description: "" },
|
|
564
|
+
};
|
|
565
|
+
const client = createMockStigmer({
|
|
566
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
567
|
+
environments: [orgSharedEnv("github-creds", "GitHub Creds"), privateEnv],
|
|
568
|
+
});
|
|
569
|
+
await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
|
|
570
|
+
|
|
571
|
+
await screen.findByLabelText("Add environment", { selector: "select" });
|
|
572
|
+
// Private environments are guest-unusable (the runtime merge skips
|
|
573
|
+
// them) — offering them would bind credentials that never apply.
|
|
574
|
+
expect(screen.getByText("GitHub Creds")).toBeTruthy();
|
|
575
|
+
expect(screen.queryByText("Personal Creds")).toBeNull();
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it("hides the section entirely for org-audience shares", async () => {
|
|
579
|
+
const client = createMockStigmer({
|
|
580
|
+
getByAgent: withShare(
|
|
581
|
+
makeShare({ enabled: true, audience: AgentShareAudience.org }),
|
|
582
|
+
),
|
|
583
|
+
});
|
|
584
|
+
await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
|
|
585
|
+
|
|
586
|
+
expect(screen.queryByText("Tool credentials")).toBeNull();
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
|
|
315
590
|
describe("Embed tab", () => {
|
|
316
|
-
it("shows the one-line script snippet: loader from the app origin + <stigmer-agent>", () => {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
onOpenChange={() => {}}
|
|
322
|
-
agent={makeAgent({ enabled: true })}
|
|
323
|
-
buildShareUrl={buildShareUrl}
|
|
324
|
-
/>
|
|
325
|
-
</Providers>,
|
|
326
|
-
);
|
|
591
|
+
it("shows the one-line script snippet: loader from the app origin + <stigmer-agent>", async () => {
|
|
592
|
+
const client = createMockStigmer({
|
|
593
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
594
|
+
});
|
|
595
|
+
await renderOpenDialog(client);
|
|
327
596
|
|
|
328
597
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
329
598
|
// The loader must be served from the SAME origin as the share URL —
|
|
@@ -340,17 +609,11 @@ describe("ShareAgentDialog", () => {
|
|
|
340
609
|
).toBeTruthy();
|
|
341
610
|
});
|
|
342
611
|
|
|
343
|
-
it("keeps the iframe snippet available as the collapsed no-JavaScript alternative", () => {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
onOpenChange={() => {}}
|
|
349
|
-
agent={makeAgent({ enabled: true })}
|
|
350
|
-
buildShareUrl={buildShareUrl}
|
|
351
|
-
/>
|
|
352
|
-
</Providers>,
|
|
353
|
-
);
|
|
612
|
+
it("keeps the iframe snippet available as the collapsed no-JavaScript alternative", async () => {
|
|
613
|
+
const client = createMockStigmer({
|
|
614
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
615
|
+
});
|
|
616
|
+
await renderOpenDialog(client);
|
|
354
617
|
|
|
355
618
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
356
619
|
// Collapsed by default — the script snippet is the primary path.
|
|
@@ -370,17 +633,12 @@ describe("ShareAgentDialog", () => {
|
|
|
370
633
|
});
|
|
371
634
|
|
|
372
635
|
it("rejects an invalid origin without calling the RPC", async () => {
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
agent={makeAgent({ enabled: true })}
|
|
380
|
-
buildShareUrl={buildShareUrl}
|
|
381
|
-
/>
|
|
382
|
-
</Providers>,
|
|
383
|
-
);
|
|
636
|
+
const apply = vi.fn();
|
|
637
|
+
const client = createMockStigmer({
|
|
638
|
+
apply,
|
|
639
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
640
|
+
});
|
|
641
|
+
await renderOpenDialog(client);
|
|
384
642
|
|
|
385
643
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
386
644
|
const input = screen.getByLabelText("Add allowed origin");
|
|
@@ -390,24 +648,21 @@ describe("ShareAgentDialog", () => {
|
|
|
390
648
|
fireEvent.click(screen.getByText("Add"));
|
|
391
649
|
|
|
392
650
|
expect(await screen.findByRole("alert", { hidden: true })).toBeTruthy();
|
|
393
|
-
expect(
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it("adds a valid origin by
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
/>
|
|
409
|
-
</Providers>,
|
|
410
|
-
);
|
|
651
|
+
expect(apply).not.toHaveBeenCalled();
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it("adds a valid origin by applying the appended list", async () => {
|
|
655
|
+
const apply = vi.fn().mockResolvedValue({});
|
|
656
|
+
const client = createMockStigmer({
|
|
657
|
+
apply,
|
|
658
|
+
getByAgent: withShare(
|
|
659
|
+
makeShare({
|
|
660
|
+
enabled: true,
|
|
661
|
+
allowedOrigins: ["https://existing.example.com"],
|
|
662
|
+
}),
|
|
663
|
+
),
|
|
664
|
+
});
|
|
665
|
+
await renderOpenDialog(client);
|
|
411
666
|
|
|
412
667
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
413
668
|
fireEvent.change(screen.getByLabelText("Add allowed origin"), {
|
|
@@ -415,31 +670,28 @@ describe("ShareAgentDialog", () => {
|
|
|
415
670
|
});
|
|
416
671
|
fireEvent.click(screen.getByText("Add"));
|
|
417
672
|
|
|
418
|
-
await waitFor(() => expect(
|
|
419
|
-
const input =
|
|
420
|
-
expect(input.
|
|
673
|
+
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
674
|
+
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
675
|
+
expect(input.allowedOrigins).toEqual([
|
|
421
676
|
"https://existing.example.com",
|
|
422
677
|
"https://new.example.com",
|
|
423
678
|
]);
|
|
424
679
|
// Enabled state rides along untouched.
|
|
425
|
-
expect(input.
|
|
680
|
+
expect(input.enabled).toBe(true);
|
|
426
681
|
});
|
|
427
682
|
|
|
428
683
|
it("rejects a duplicate origin", async () => {
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
/>
|
|
441
|
-
</Providers>,
|
|
442
|
-
);
|
|
684
|
+
const apply = vi.fn();
|
|
685
|
+
const client = createMockStigmer({
|
|
686
|
+
apply,
|
|
687
|
+
getByAgent: withShare(
|
|
688
|
+
makeShare({
|
|
689
|
+
enabled: true,
|
|
690
|
+
allowedOrigins: ["https://example.com"],
|
|
691
|
+
}),
|
|
692
|
+
),
|
|
693
|
+
});
|
|
694
|
+
await renderOpenDialog(client);
|
|
443
695
|
|
|
444
696
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
445
697
|
fireEvent.change(screen.getByLabelText("Add allowed origin"), {
|
|
@@ -448,50 +700,44 @@ describe("ShareAgentDialog", () => {
|
|
|
448
700
|
fireEvent.click(screen.getByText("Add"));
|
|
449
701
|
|
|
450
702
|
expect(await screen.findByRole("alert", { hidden: true })).toBeTruthy();
|
|
451
|
-
expect(
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
it("removes an origin by
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
/>
|
|
467
|
-
</Providers>,
|
|
468
|
-
);
|
|
703
|
+
expect(apply).not.toHaveBeenCalled();
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it("removes an origin by applying the filtered list", async () => {
|
|
707
|
+
const apply = vi.fn().mockResolvedValue({});
|
|
708
|
+
const client = createMockStigmer({
|
|
709
|
+
apply,
|
|
710
|
+
getByAgent: withShare(
|
|
711
|
+
makeShare({
|
|
712
|
+
enabled: true,
|
|
713
|
+
allowedOrigins: ["https://a.example.com", "https://b.example.com"],
|
|
714
|
+
}),
|
|
715
|
+
),
|
|
716
|
+
});
|
|
717
|
+
await renderOpenDialog(client);
|
|
469
718
|
|
|
470
719
|
fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
|
|
471
720
|
fireEvent.click(screen.getByLabelText("Remove https://a.example.com"));
|
|
472
721
|
|
|
473
|
-
await waitFor(() => expect(
|
|
474
|
-
const input =
|
|
475
|
-
expect(input.
|
|
722
|
+
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
723
|
+
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
724
|
+
expect(input.allowedOrigins).toEqual(["https://b.example.com"]);
|
|
476
725
|
});
|
|
477
726
|
});
|
|
478
727
|
|
|
479
728
|
describe("visitor messages", () => {
|
|
480
|
-
it("saves edited messages as part of the complete
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
/>
|
|
493
|
-
</Providers>,
|
|
494
|
-
);
|
|
729
|
+
it("saves edited messages as part of the complete spec", async () => {
|
|
730
|
+
const apply = vi.fn().mockResolvedValue({});
|
|
731
|
+
const client = createMockStigmer({
|
|
732
|
+
apply,
|
|
733
|
+
getByAgent: withShare(
|
|
734
|
+
makeShare({
|
|
735
|
+
enabled: true,
|
|
736
|
+
allowedOrigins: ["https://example.com"],
|
|
737
|
+
}),
|
|
738
|
+
),
|
|
739
|
+
});
|
|
740
|
+
await renderOpenDialog(client);
|
|
495
741
|
|
|
496
742
|
fireEvent.click(screen.getByText("Customize visitor messages"));
|
|
497
743
|
fireEvent.change(screen.getByLabelText(/Rate limited/), {
|
|
@@ -499,24 +745,18 @@ describe("ShareAgentDialog", () => {
|
|
|
499
745
|
});
|
|
500
746
|
fireEvent.click(screen.getByText("Save messages"));
|
|
501
747
|
|
|
502
|
-
await waitFor(() => expect(
|
|
503
|
-
const input =
|
|
504
|
-
expect(input.
|
|
505
|
-
expect(input.
|
|
506
|
-
expect(input.
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
it("caps each message at 300 characters", () => {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
onOpenChange={() => {}}
|
|
515
|
-
agent={makeAgent({ enabled: true })}
|
|
516
|
-
buildShareUrl={buildShareUrl}
|
|
517
|
-
/>
|
|
518
|
-
</Providers>,
|
|
519
|
-
);
|
|
748
|
+
await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
|
|
749
|
+
const input = apply.mock.calls[0][0] as AgentShareInput;
|
|
750
|
+
expect(input.messages?.rateLimited).toBe("Please slow down.");
|
|
751
|
+
expect(input.enabled).toBe(true);
|
|
752
|
+
expect(input.allowedOrigins).toEqual(["https://example.com"]);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
it("caps each message at 300 characters", async () => {
|
|
756
|
+
const client = createMockStigmer({
|
|
757
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
758
|
+
});
|
|
759
|
+
await renderOpenDialog(client);
|
|
520
760
|
|
|
521
761
|
fireEvent.click(screen.getByText("Customize visitor messages"));
|
|
522
762
|
const field = screen.getByLabelText(/Rate limited/) as HTMLTextAreaElement;
|
|
@@ -530,19 +770,11 @@ describe("ShareAgentDialog", () => {
|
|
|
530
770
|
const getOrCreateBillingAccount = vi.fn().mockResolvedValue({
|
|
531
771
|
balance: { availableMicros: BigInt(12_500_000) },
|
|
532
772
|
});
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
<ShareAgentDialog
|
|
539
|
-
open
|
|
540
|
-
onOpenChange={() => {}}
|
|
541
|
-
agent={makeAgent({ enabled: true })}
|
|
542
|
-
buildShareUrl={buildShareUrl}
|
|
543
|
-
/>
|
|
544
|
-
</Providers>,
|
|
545
|
-
);
|
|
773
|
+
const client = createMockStigmer({
|
|
774
|
+
getOrCreateBillingAccount,
|
|
775
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
776
|
+
});
|
|
777
|
+
await renderOpenDialog(client, { mode: "cloud" });
|
|
546
778
|
|
|
547
779
|
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
548
780
|
await waitFor(() =>
|
|
@@ -551,21 +783,13 @@ describe("ShareAgentDialog", () => {
|
|
|
551
783
|
expect(getOrCreateBillingAccount).toHaveBeenCalledWith("acme");
|
|
552
784
|
});
|
|
553
785
|
|
|
554
|
-
it("degrades to the who-pays line alone in local mode (no billing fetch)", () => {
|
|
786
|
+
it("degrades to the who-pays line alone in local mode (no billing fetch)", async () => {
|
|
555
787
|
const getOrCreateBillingAccount = vi.fn();
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
<ShareAgentDialog
|
|
562
|
-
open
|
|
563
|
-
onOpenChange={() => {}}
|
|
564
|
-
agent={makeAgent({ enabled: true })}
|
|
565
|
-
buildShareUrl={buildShareUrl}
|
|
566
|
-
/>
|
|
567
|
-
</Providers>,
|
|
568
|
-
);
|
|
788
|
+
const client = createMockStigmer({
|
|
789
|
+
getOrCreateBillingAccount,
|
|
790
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
791
|
+
});
|
|
792
|
+
await renderOpenDialog(client, { mode: "local" });
|
|
569
793
|
|
|
570
794
|
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
571
795
|
expect(screen.queryByText(/available/)).toBeNull();
|
|
@@ -576,19 +800,11 @@ describe("ShareAgentDialog", () => {
|
|
|
576
800
|
const getOrCreateBillingAccount = vi
|
|
577
801
|
.fn()
|
|
578
802
|
.mockRejectedValue(new Error("billing unavailable"));
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
<ShareAgentDialog
|
|
585
|
-
open
|
|
586
|
-
onOpenChange={() => {}}
|
|
587
|
-
agent={makeAgent({ enabled: true })}
|
|
588
|
-
buildShareUrl={buildShareUrl}
|
|
589
|
-
/>
|
|
590
|
-
</Providers>,
|
|
591
|
-
);
|
|
803
|
+
const client = createMockStigmer({
|
|
804
|
+
getOrCreateBillingAccount,
|
|
805
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
806
|
+
});
|
|
807
|
+
await renderOpenDialog(client, { mode: "cloud" });
|
|
592
808
|
|
|
593
809
|
await waitFor(() => expect(getOrCreateBillingAccount).toHaveBeenCalled());
|
|
594
810
|
expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
|
|
@@ -598,17 +814,11 @@ describe("ShareAgentDialog", () => {
|
|
|
598
814
|
});
|
|
599
815
|
|
|
600
816
|
describe("Developer tab", () => {
|
|
601
|
-
it("shows the platform client snippet and docs link", () => {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
onOpenChange={() => {}}
|
|
607
|
-
agent={makeAgent({ enabled: true })}
|
|
608
|
-
buildShareUrl={buildShareUrl}
|
|
609
|
-
/>
|
|
610
|
-
</Providers>,
|
|
611
|
-
);
|
|
817
|
+
it("shows the platform client snippet and docs link", async () => {
|
|
818
|
+
const client = createMockStigmer({
|
|
819
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
820
|
+
});
|
|
821
|
+
await renderOpenDialog(client);
|
|
612
822
|
|
|
613
823
|
fireEvent.click(screen.getByRole("tab", { name: /Developer/, hidden: true }));
|
|
614
824
|
expect(screen.getByText(/createPlatformClientAuth/)).toBeTruthy();
|
|
@@ -617,37 +827,24 @@ describe("ShareAgentDialog", () => {
|
|
|
617
827
|
});
|
|
618
828
|
});
|
|
619
829
|
|
|
620
|
-
it("renders in-flow without showModal when modal is false", () => {
|
|
830
|
+
it("renders in-flow without showModal when modal is false", async () => {
|
|
621
831
|
const showModal = vi.spyOn(HTMLDialogElement.prototype, "showModal");
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
onOpenChange={() => {}}
|
|
627
|
-
agent={makeAgent({ enabled: true })}
|
|
628
|
-
buildShareUrl={buildShareUrl}
|
|
629
|
-
modal={false}
|
|
630
|
-
/>
|
|
631
|
-
</Providers>,
|
|
632
|
-
);
|
|
832
|
+
const client = createMockStigmer({
|
|
833
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
834
|
+
});
|
|
835
|
+
await renderOpenDialog(client, { modal: false });
|
|
633
836
|
|
|
634
837
|
expect(screen.getByText("Share")).toBeTruthy();
|
|
635
838
|
expect(showModal).not.toHaveBeenCalled();
|
|
636
839
|
showModal.mockRestore();
|
|
637
840
|
});
|
|
638
841
|
|
|
639
|
-
it("requests close via Done and the close affordance", () => {
|
|
842
|
+
it("requests close via Done and the close affordance", async () => {
|
|
640
843
|
const onOpenChange = vi.fn();
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
onOpenChange={onOpenChange}
|
|
646
|
-
agent={makeAgent({ enabled: true })}
|
|
647
|
-
buildShareUrl={buildShareUrl}
|
|
648
|
-
/>
|
|
649
|
-
</Providers>,
|
|
650
|
-
);
|
|
844
|
+
const client = createMockStigmer({
|
|
845
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
846
|
+
});
|
|
847
|
+
await renderOpenDialog(client, { onOpenChange });
|
|
651
848
|
|
|
652
849
|
screen.getByText("Done").click();
|
|
653
850
|
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
@@ -658,17 +855,11 @@ describe("ShareAgentDialog", () => {
|
|
|
658
855
|
});
|
|
659
856
|
|
|
660
857
|
describe("Reset link (rotatable share token)", () => {
|
|
661
|
-
it("appends the status token to the shown link and embed snippet", () => {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
onOpenChange={() => {}}
|
|
667
|
-
agent={makeAgent({ enabled: true }, "tok123")}
|
|
668
|
-
buildShareUrl={buildShareUrl}
|
|
669
|
-
/>
|
|
670
|
-
</Providers>,
|
|
671
|
-
);
|
|
858
|
+
it("appends the status token to the shown link and embed snippet", async () => {
|
|
859
|
+
const client = createMockStigmer({
|
|
860
|
+
getByAgent: withShare(makeShare({ enabled: true }, "tok123")),
|
|
861
|
+
});
|
|
862
|
+
await renderOpenDialog(client);
|
|
672
863
|
|
|
673
864
|
expect(
|
|
674
865
|
screen.getByText("https://app.example.com/chat/acme/support-agent?k=tok123"),
|
|
@@ -679,22 +870,15 @@ describe("ShareAgentDialog", () => {
|
|
|
679
870
|
});
|
|
680
871
|
|
|
681
872
|
it("rotates on Reset link, adopts the fresh token, and notifies the host", async () => {
|
|
682
|
-
const rotateShareLink = vi
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
});
|
|
873
|
+
const rotateShareLink = vi
|
|
874
|
+
.fn()
|
|
875
|
+
.mockResolvedValue(makeShare({ enabled: true }, "fresh-token"));
|
|
686
876
|
const onSharingChanged = vi.fn();
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
agent={makeAgent({ enabled: true })}
|
|
693
|
-
buildShareUrl={buildShareUrl}
|
|
694
|
-
onSharingChanged={onSharingChanged}
|
|
695
|
-
/>
|
|
696
|
-
</Providers>,
|
|
697
|
-
);
|
|
877
|
+
const client = createMockStigmer({
|
|
878
|
+
rotateShareLink,
|
|
879
|
+
getByAgent: withShare(makeShare({ enabled: true })),
|
|
880
|
+
});
|
|
881
|
+
await renderOpenDialog(client, { onSharingChanged });
|
|
698
882
|
|
|
699
883
|
// A plain link shows no token before the reset.
|
|
700
884
|
expect(
|
|
@@ -711,26 +895,23 @@ describe("ShareAgentDialog", () => {
|
|
|
711
895
|
).toBeTruthy(),
|
|
712
896
|
);
|
|
713
897
|
expect(rotateShareLink).toHaveBeenCalledTimes(1);
|
|
898
|
+
// The rotation targets the loaded share by its own id.
|
|
899
|
+
expect(
|
|
900
|
+
(rotateShareLink.mock.calls[0][0] as { resourceId: string }).resourceId,
|
|
901
|
+
).toBe("ash_1");
|
|
714
902
|
expect(onSharingChanged).toHaveBeenCalled();
|
|
715
903
|
});
|
|
716
904
|
|
|
717
|
-
it("hides the Reset control and the token for org-members-only shares", () => {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
},
|
|
728
|
-
"tok123",
|
|
729
|
-
)}
|
|
730
|
-
buildShareUrl={buildShareUrl}
|
|
731
|
-
/>
|
|
732
|
-
</Providers>,
|
|
733
|
-
);
|
|
905
|
+
it("hides the Reset control and the token for org-members-only shares", async () => {
|
|
906
|
+
const client = createMockStigmer({
|
|
907
|
+
getByAgent: withShare(
|
|
908
|
+
makeShare(
|
|
909
|
+
{ enabled: true, audience: AgentShareAudience.org },
|
|
910
|
+
"tok123",
|
|
911
|
+
),
|
|
912
|
+
),
|
|
913
|
+
});
|
|
914
|
+
await renderOpenDialog(client);
|
|
734
915
|
|
|
735
916
|
// Org access is gated by membership, not the link token: the member
|
|
736
917
|
// link stays clean and the Reset lever is not offered.
|