1import { GcpEnvironment } from "@/GCP/Environment";
2import * as GCP from "@/GCP";
3import * as Test from "@/Test/Alchemy";
4import * as discoveryengine from "@distilled.cloud/gcp/discoveryengine_v1";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9import { ensureDataStore, quotaTolerant } from "./parent.ts";
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;
23const parentId = "alchds3conv";
24
25const waitUntilGone = (name: string) =>
26 discoveryengine.getProjectsLocationsDataStoresConversations({ name }).pipe(
27 Effect.as("found" as const),
28 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
29 Effect.repeat({
30 schedule: Schedule.spaced("2 seconds"),
31 until: (status) => status === "gone",
32 times: 10,
33 }),
34 );
35
36test.provider(
37 "getProjectsLocationsDataStoresConversations on a missing conversation fails with a typed tag",
38 (stack) =>
39 Effect.gen(function* () {
40 const { project } = yield* GcpEnvironment.current;
41
42 yield* stack.destroy();
43
44 const error = yield* Effect.flip(
45 discoveryengine.getProjectsLocationsDataStoresConversations({
46 name: `projects/${project}/locations/global/dataStores/alchemy-missing/conversations/alchemy-missing`,
47 }),
48 );
49 expect(error._tag).toEqual("NotFound");
50
51 yield* stack.destroy();
52 }).pipe(logLevel, quotaTolerant),
53 {
54 tags: ["provider:gcp", "provider:gcp:discoveryengine", "live"],
55 timeout: 90_000,
56 },
57);
58
59test.provider.skipIf(!!process.env.FAST || runLifecycle)(
60 "createProjectsLocationsDataStoresConversations without the LLM add-on is rejected with a typed tag",
61 (stack) =>
62 Effect.gen(function* () {
63 const { project } = yield* GcpEnvironment.current;
64
65 yield* stack.destroy();
66
67 const parent = yield* ensureDataStore(project, parentId, {
68 contentConfig: "NO_CONTENT",
69 });
70 const error = yield* Effect.flip(
71 discoveryengine.createProjectsLocationsDataStoresConversations({
72 parent: parent.name ?? "",
73 body: { userPseudoId: "alchemy-probe" },
74 }),
75 );
76 expect(error._tag).toEqual("LlmAddOnRequired");
77
78 yield* stack.destroy();
79 }).pipe(logLevel, quotaTolerant),
80 {
81 tags: ["provider:gcp", "provider:gcp:discoveryengine", "live"],
82 timeout: 120_000,
83 },
84);
85
86test.provider.skipIf(!runLifecycle)(
87 "create, update, and delete a data store conversation",
88 (stack) =>
89 Effect.gen(function* () {
90 const { project } = yield* GcpEnvironment.current;
91
92 yield* stack.destroy();
93
94 const parent = yield* ensureDataStore(project, parentId, {
95 contentConfig: "NO_CONTENT",
96 });
97
98 const created = yield* stack.deploy(
99 Effect.gen(function* () {
100 return yield* GCP.DiscoveryEngine.DataStoresConversation("Support", {
101 dataStore: parent.name ?? "",
102 });
103 }),
104 );
105
106 expect(created.name).toContain("/conversations/");
107 expect(created.dataStore).toEqual(parent.name);
108
109 const fetched =
110 yield* discoveryengine.getProjectsLocationsDataStoresConversations({
111 name: created.name,
112 });
113 expect(fetched.name).toEqual(created.name);
114
115 const updated = yield* stack.deploy(
116 Effect.gen(function* () {
117 return yield* GCP.DiscoveryEngine.DataStoresConversation("Support", {
118 dataStore: parent.name ?? "",
119 state: "COMPLETED",
120 });
121 }),
122 );
123
124 expect(updated.name).toEqual(created.name);
125 expect(updated.state).toEqual("COMPLETED");
126
127 yield* stack.destroy();
128
129 const gone = yield* waitUntilGone(created.name);
130 expect(gone).toEqual("gone");
131 }).pipe(logLevel, quotaTolerant),
132 {
133 tags: ["provider:gcp", "provider:gcp:discoveryengine", "live"],
134 timeout: 120_000,
135 },
136);