1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as dlp from "@distilled.cloud/gcp/dlp_v2";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8import { GcpEnvironment } from "@/GCP/Environment";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12// Organization-scoped: set GOOGLE_ORGANIZATION_ID when the credentials
13// administer the organization (the testing service account does not).
14const organizationId = process.env.GOOGLE_ORGANIZATION_ID?.trim().replace(
15 /^organizations\//,
16 "",
17);
18const organization = `organizations/${organizationId}`;
19
20const logLevel = Effect.provideService(
21 MinimumLogLevel,
22 process.env.DEBUG ? "Debug" : "Info",
23);
24
25const location = "us-central1";
26
27const waitUntilGone = (name: string) =>
28 dlp.getOrganizationsLocationsJobTriggers({ name }).pipe(
29 Effect.as("found" as const),
30 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
31 Effect.repeat({
32 schedule: Schedule.spaced("1 second"),
33 until: (status) => status === "gone",
34 times: 10,
35 }),
36 );
37
38test.provider.skipIf(!organizationId)(
39 "getOrganizationsLocationsJobTriggers on a missing trigger fails with a typed tag",
40 (stack) =>
41 Effect.gen(function* () {
42 yield* stack.destroy();
43
44 const error = yield* Effect.flip(
45 dlp.getOrganizationsLocationsJobTriggers({
46 name: `${organization}/locations/${location}/jobTriggers/alchemy-missing`,
47 }),
48 );
49 expect(error._tag).toEqual("NotFound");
50
51 yield* stack.destroy();
52 }).pipe(logLevel),
53 { tags: ["provider:gcp", "provider:gcp:dlp", "live"], timeout: 90_000 },
54);
55
56test.provider.skipIf(!organizationId)(
57 "create, update, and delete an organization job trigger",
58 (stack) =>
59 Effect.gen(function* () {
60 const { project } = yield* GcpEnvironment.current;
61 yield* stack.destroy();
62
63 const inspectJob = {
64 inspectConfig: { infoTypes: [{ name: "EMAIL_ADDRESS" }] },
65 storageConfig: {
66 cloudStorageOptions: {
67 fileSet: { url: `gs://${project}-dlp-noop/` },
68 },
69 },
70 };
71
72 const parent = `${organization}/locations/${location}`;
73
74 const created = yield* stack.deploy(
75 Effect.gen(function* () {
76 return yield* GCP.DLP.OrganizationsLocationsJobTrigger("ScanBucket", {
77 organization,
78 location,
79 displayName: "scan-bucket",
80 description: "paused inspect",
81 status: "PAUSED",
82 triggers: [{ schedule: { recurrencePeriodDuration: "86400s" } }],
83 inspectJob,
84 });
85 }),
86 );
87
88 expect(created.location).toEqual(location);
89 expect(created.status).toEqual("PAUSED");
90 expect(created.name).toEqual(
91 `${parent}/jobTriggers/${created.triggerId}`,
92 );
93
94 const fetched = yield* dlp.getOrganizationsLocationsJobTriggers({
95 name: created.name,
96 });
97 expect(fetched.description).toContain("alchemy-id=");
98
99 const updated = yield* stack.deploy(
100 Effect.gen(function* () {
101 return yield* GCP.DLP.OrganizationsLocationsJobTrigger("ScanBucket", {
102 organization,
103 location,
104 triggerId: created.triggerId,
105 displayName: "scan-bucket-v2",
106 description: "paused inspect v2",
107 status: "PAUSED",
108 triggers: [{ schedule: { recurrencePeriodDuration: "172800s" } }],
109 inspectJob,
110 });
111 }),
112 );
113
114 expect(updated.name).toEqual(created.name);
115 expect(updated.displayName).toEqual("scan-bucket-v2");
116
117 yield* stack.destroy();
118
119 const gone = yield* waitUntilGone(created.name);
120 expect(gone).toEqual("gone");
121 }).pipe(logLevel),
122 { tags: ["provider:gcp", "provider:gcp:dlp", "live"], timeout: 90_000 },
123);