1import * as GCP from "@/GCP";
2import { GcpEnvironment } from "@/GCP/Environment";
3import * as Test from "@/Test/Alchemy";
4import * as cci from "@distilled.cloud/gcp/contactcenterinsights_v1";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9import { uploadChatTranscript } from "./transcript.ts";
10
11const { test } = Test.make({ providers: GCP.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18const transcriptBucketOf = (project: string) =>
19 `alchemy-cci-transcripts-${project}`;
20const conversationId = "alchemy-cci-assess-conv";
21const conversationNameOf = (project: string) =>
22 `projects/${project}/locations/us-central1/conversations/${conversationId}`;
23
24const waitUntilGone = (name: string) =>
25 cci.getProjectsLocationsConversationsAssessments({ name }).pipe(
26 Effect.as("found" as const),
27 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
28 Effect.repeat({
29 schedule: Schedule.spaced("1 second"),
30 until: (status) => status === "gone",
31 times: 10,
32 }),
33 );
34
35const ensureConversation = (project: string) =>
36 Effect.gen(function* () {
37 const transcriptBucket = transcriptBucketOf(project);
38 const conversationName = conversationNameOf(project);
39 yield* uploadChatTranscript(transcriptBucket);
40 const existing = yield* cci
41 .getProjectsLocationsConversations({
42 name: conversationName,
43 view: "BASIC",
44 })
45 .pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
46 if (existing !== undefined) return existing;
47 return yield* cci.createProjectsLocationsConversations({
48 parent: `projects/${project}/locations/us-central1`,
49 conversationId,
50 body: {
51 medium: "CHAT",
52 languageCode: "en-US",
53 labels: { "alchemy-test": "cci" },
54 dataSource: {
55 gcsSource: {
56 transcriptUri: `gs://${transcriptBucket}/transcript.json`,
57 },
58 },
59 },
60 });
61 });
62
63const deleteConversation = (project: string) =>
64 cci
65 .deleteProjectsLocationsConversations({
66 name: conversationNameOf(project),
67 force: true,
68 })
69 .pipe(Effect.catchTag("NotFound", () => Effect.void));
70
71test.provider(
72 "getProjectsLocationsConversationsAssessments on a missing assessment fails with a typed tag",
73 (stack) =>
74 Effect.gen(function* () {
75 const { project } = yield* GcpEnvironment.current;
76 const conversationName = conversationNameOf(project);
77 yield* stack.destroy();
78
79 const error = yield* Effect.flip(
80 cci.getProjectsLocationsConversationsAssessments({
81 name: `${conversationName}/assessments/missing`,
82 }),
83 );
84 expect(error._tag).toEqual("NotFound");
85
86 yield* stack.destroy();
87 }).pipe(logLevel),
88 {
89 tags: ["provider:gcp", "provider:gcp:contactcenterinsights", "live"],
90 timeout: 90_000,
91 },
92);
93
94test.provider.skipIf(!!process.env.FAST)(
95 "create, update, and delete a conversation assessment",
96 (stack) =>
97 Effect.gen(function* () {
98 const { project } = yield* GcpEnvironment.current;
99 const conversationName = conversationNameOf(project);
100 yield* stack.destroy();
101 const conversation = yield* ensureConversation(project);
102
103 const created = yield* stack.deploy(
104 Effect.gen(function* () {
105 return yield* GCP.ContactCenterInsights.ConversationsAssessment(
106 "QA",
107 {
108 parent: conversation.name ?? conversationName,
109 agentInfo: {
110 agentId: "agent-1",
111 displayName: "Ada",
112 agentType: "HUMAN_AGENT",
113 },
114 },
115 );
116 }),
117 );
118
119 expect(created.name).toContain("/assessments/");
120 expect(created.agentInfo?.displayName).toEqual("Ada");
121
122 const fetched = yield* cci.getProjectsLocationsConversationsAssessments({
123 name: created.name,
124 });
125 expect(fetched.name).toEqual(created.name);
126 expect(fetched.agentInfo?.displayName).toContain("alchemy-id=");
127
128 const updated = yield* stack.deploy(
129 Effect.gen(function* () {
130 return yield* GCP.ContactCenterInsights.ConversationsAssessment(
131 "QA",
132 {
133 parent: conversation.name ?? conversationName,
134 agentInfo: {
135 agentId: "agent-1",
136 displayName: "Bob",
137 agentType: "HUMAN_AGENT",
138 },
139 },
140 );
141 }),
142 );
143 expect(updated.agentInfo?.displayName).toEqual("Bob");
144
145 yield* stack.destroy();
146 const gone = yield* waitUntilGone(created.name);
147 expect(gone).toEqual("gone");
148 yield* deleteConversation(project);
149 }).pipe(logLevel),
150 {
151 tags: ["provider:gcp", "provider:gcp:contactcenterinsights", "live"],
152 timeout: 120_000,
153 },
154);