1import { GcpEnvironment } from "@/GCP/Environment";
2import * as GCP from "@/GCP";
3import { quotaTolerant } from "./parent.ts";
4import * as Test from "@/Test/Alchemy";
5import * as discoveryengine from "@distilled.cloud/gcp/discoveryengine_v1";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import { MinimumLogLevel } from "effect/References";
9import * as Schedule from "effect/Schedule";
10
11const { test } = Test.make({ providers: GCP.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18// Conversations need the Large Language Model add-on (BadRequest "This
19// feature is only available when Large Language Model add-on is enabled.").
20// Set GCP_TEST_DISCOVERYENGINE_LLM=1 on a project with the add-on.
21const runLifecycle =
22 !process.env.FAST && !!process.env.GCP_TEST_DISCOVERYENGINE_LLM;
23
24const waitUntilGone = (name: string) =>
25 discoveryengine
26 .getProjectsLocationsCollectionsEnginesConversations({ name })
27 .pipe(
28 Effect.as("found" as const),
29 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
30 Effect.repeat({
31 schedule: Schedule.spaced("2 seconds"),
32 until: (status) => status === "gone",
33 times: 10,
34 }),
35 );
36
37test.provider(
38 "getProjectsLocationsCollectionsEnginesConversations on a missing conversation fails with a typed tag",
39 (stack) =>
40 Effect.gen(function* () {
41 const { project } = yield* GcpEnvironment.current;
42
43 yield* stack.destroy();
44
45 const error = yield* Effect.flip(
46 discoveryengine.getProjectsLocationsCollectionsEnginesConversations({
47 name: `projects/${project}/locations/global/collections/default_collection/engines/alchemy-missing/conversations/alchemy-missing`,
48 }),
49 );
50 expect(error._tag).toEqual("NotFound");
51
52 yield* stack.destroy();
53 }).pipe(logLevel, quotaTolerant),
54 {
55 tags: ["provider:gcp", "provider:gcp:discoveryengine", "live"],
56 timeout: 90_000,
57 },
58);
59
60test.provider.skipIf(!runLifecycle)(
61 "create, update, and delete an engine conversation",
62 (stack) =>
63 Effect.gen(function* () {
64 yield* stack.destroy();
65
66 const created = yield* stack.deploy(
67 Effect.gen(function* () {
68 const store = yield* GCP.DiscoveryEngine.CollectionsDataStore(
69 "Docs",
70 {
71 location: "global",
72 displayName: "conversation-docs",
73 },
74 );
75 const engine = yield* GCP.DiscoveryEngine.CollectionsEngine(
76 "Search",
77 {
78 location: "global",
79 dataStoreIds: [store.dataStoreId],
80 displayName: "conversation engine",
81 },
82 );
83 const conversation =
84 yield* GCP.DiscoveryEngine.CollectionsEnginesConversation("Chat", {
85 engine: engine.name,
86 state: "IN_PROGRESS",
87 });
88 return { store, engine, conversation };
89 }),
90 );
91
92 expect(created.conversation.name).toContain("/conversations/");
93 expect(created.conversation.engine).toEqual(created.engine.name);
94 expect(created.conversation.state).toEqual("IN_PROGRESS");
95
96 const fetched =
97 yield* discoveryengine.getProjectsLocationsCollectionsEnginesConversations(
98 { name: created.conversation.name },
99 );
100 expect(fetched.name).toEqual(created.conversation.name);
101 expect(fetched.state).toEqual("IN_PROGRESS");
102
103 const updated = yield* stack.deploy(
104 Effect.gen(function* () {
105 const store = yield* GCP.DiscoveryEngine.CollectionsDataStore(
106 "Docs",
107 {
108 dataStoreId: created.store.dataStoreId,
109 location: "global",
110 displayName: "conversation-docs",
111 },
112 );
113 const engine = yield* GCP.DiscoveryEngine.CollectionsEngine(
114 "Search",
115 {
116 engineId: created.engine.engineId,
117 location: "global",
118 dataStoreIds: [store.dataStoreId],
119 displayName: "conversation engine",
120 },
121 );
122 const conversation =
123 yield* GCP.DiscoveryEngine.CollectionsEnginesConversation("Chat", {
124 engine: engine.name,
125 state: "COMPLETED",
126 });
127 return { store, engine, conversation };
128 }),
129 );
130
131 expect(updated.conversation.name).toEqual(created.conversation.name);
132 expect(updated.conversation.state).toEqual("COMPLETED");
133
134 yield* stack.destroy();
135
136 const gone = yield* waitUntilGone(created.conversation.name);
137 expect(gone).toEqual("gone");
138 }).pipe(logLevel, quotaTolerant),
139 {
140 tags: ["provider:gcp", "provider:gcp:discoveryengine", "live"],
141 timeout: 120_000,
142 },
143);