1import * as GCP from "@/GCP";
2import { quotaTolerant } from "./parent.ts";
3import * as Test from "@/Test/Alchemy";
4import * as dialogflow from "@distilled.cloud/gcp/dialogflow_v3";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9import { GcpEnvironment } from "@/GCP/Environment";
10
11const { test } = Test.make({ providers: GCP.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18const runLifecycle = !process.env.FAST;
19// Non-global Dialogflow CX locations need the {region}-dialogflow host, which
20// distilled does not route to yet; the global location works everywhere.
21const location = "global";
22
23const waitUntilGone = (name: string) =>
24 dialogflow.getProjectsLocationsSecuritySettings({ name }).pipe(
25 Effect.as("found" as const),
26 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
27 Effect.repeat({
28 schedule: Schedule.spaced("2 seconds"),
29 until: (status) => status === "gone",
30 times: 10,
31 }),
32 );
33
34test.provider(
35 "getProjectsLocationsSecuritySettings on a missing setting fails with a typed tag",
36 (stack) =>
37 Effect.gen(function* () {
38 const { project } = yield* GcpEnvironment.current;
39 yield* stack.destroy();
40
41 const error = yield* Effect.flip(
42 dialogflow.getProjectsLocationsSecuritySettings({
43 name: `projects/${project}/locations/${location}/securitySettings/missing`,
44 }),
45 );
46 expect(error._tag).toEqual("NotFound");
47
48 yield* stack.destroy();
49 }).pipe(logLevel, quotaTolerant),
50 {
51 tags: ["provider:gcp", "provider:gcp:dialogflow", "live"],
52 timeout: 90_000,
53 },
54);
55
56test.provider.skipIf(!runLifecycle)(
57 "create, update, and delete security settings",
58 (stack) =>
59 Effect.gen(function* () {
60 yield* stack.destroy();
61
62 const created = yield* stack.deploy(
63 Effect.gen(function* () {
64 return yield* GCP.Dialogflow.SecuritySetting("Retention", {
65 location,
66 displayName: "session-only",
67 retentionStrategy: "REMOVE_AFTER_CONVERSATION",
68 });
69 }),
70 );
71
72 expect(created.name).toContain("/securitySettings/");
73 expect(created.location).toEqual(location);
74 expect(created.displayName).toEqual("session-only");
75 expect(created.retentionStrategy).toEqual("REMOVE_AFTER_CONVERSATION");
76
77 const fetched = yield* dialogflow.getProjectsLocationsSecuritySettings({
78 name: created.name,
79 });
80 expect(fetched.name).toEqual(created.name);
81 expect(fetched.displayName).toMatch(/^\[(alchemy|alc) /);
82
83 const updated = yield* stack.deploy(
84 Effect.gen(function* () {
85 return yield* GCP.Dialogflow.SecuritySetting("Retention", {
86 location,
87 securitySettingsId: created.securitySettingsId,
88 displayName: "thirty-days",
89 retentionWindowDays: 30,
90 });
91 }),
92 );
93
94 expect(updated.name).toEqual(created.name);
95 expect(updated.displayName).toEqual("thirty-days");
96 expect(updated.retentionWindowDays).toEqual(30);
97
98 yield* stack.destroy();
99 const gone = yield* waitUntilGone(created.name);
100 expect(gone).toEqual("gone");
101 }).pipe(logLevel, quotaTolerant),
102 {
103 tags: ["provider:gcp", "provider:gcp:dialogflow", "live"],
104 timeout: 120_000,
105 },
106);