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";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11// Organization-scoped: set GOOGLE_ORGANIZATION_ID when the credentials
12// administer the organization (the testing service account does not).
13const organizationId = process.env.GOOGLE_ORGANIZATION_ID?.trim().replace(
14 /^organizations\//,
15 "",
16);
17const organization = `organizations/${organizationId}`;
18
19const logLevel = Effect.provideService(
20 MinimumLogLevel,
21 process.env.DEBUG ? "Debug" : "Info",
22);
23
24const location = "us-central1";
25
26const redactConfig = {
27 infoTypeTransformations: {
28 transformations: [
29 { primitiveTransformation: { replaceWithInfoTypeConfig: {} } },
30 ],
31 },
32};
33
34const waitUntilGone = (name: string) =>
35 dlp.getOrganizationsLocationsDeidentifyTemplates({ name }).pipe(
36 Effect.as("found" as const),
37 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
38 Effect.repeat({
39 schedule: Schedule.spaced("1 second"),
40 until: (status) => status === "gone",
41 times: 10,
42 }),
43 );
44
45test.provider.skipIf(!organizationId)(
46 "getOrganizationsLocationsDeidentifyTemplates on a missing template fails with a typed tag",
47 (stack) =>
48 Effect.gen(function* () {
49 yield* stack.destroy();
50
51 const error = yield* Effect.flip(
52 dlp.getOrganizationsLocationsDeidentifyTemplates({
53 name: `${organization}/locations/${location}/deidentifyTemplates/alchemy-missing`,
54 }),
55 );
56 expect(error._tag).toEqual("NotFound");
57
58 yield* stack.destroy();
59 }).pipe(logLevel),
60 { tags: ["provider:gcp", "provider:gcp:dlp", "live"], timeout: 90_000 },
61);
62
63test.provider.skipIf(!organizationId)(
64 "create, update, and delete a location-scoped organization deidentify template",
65 (stack) =>
66 Effect.gen(function* () {
67 yield* stack.destroy();
68
69 const parent = `${organization}/locations/${location}`;
70
71 const created = yield* stack.deploy(
72 Effect.gen(function* () {
73 return yield* GCP.DLP.OrganizationsLocationsDeidentifyTemplate(
74 "RedactPhone",
75 {
76 organization,
77 location,
78 displayName: "redact-phone",
79 description: "redact phones",
80 deidentifyConfig: redactConfig,
81 },
82 );
83 }),
84 );
85
86 expect(created.location).toEqual(location);
87 expect(created.name).toEqual(
88 `${parent}/deidentifyTemplates/${created.templateId}`,
89 );
90
91 const fetched = yield* dlp.getOrganizationsLocationsDeidentifyTemplates({
92 name: created.name,
93 });
94 expect(fetched.description).toContain("alchemy-id=");
95
96 const updated = yield* stack.deploy(
97 Effect.gen(function* () {
98 return yield* GCP.DLP.OrganizationsLocationsDeidentifyTemplate(
99 "RedactPhone",
100 {
101 organization,
102 location,
103 templateId: created.templateId,
104 displayName: "redact-phone-v2",
105 description: "redact phones v2",
106 deidentifyConfig: redactConfig,
107 },
108 );
109 }),
110 );
111
112 expect(updated.name).toEqual(created.name);
113 expect(updated.displayName).toEqual("redact-phone-v2");
114
115 yield* stack.destroy();
116
117 const gone = yield* waitUntilGone(created.name);
118 expect(gone).toEqual("gone");
119 }).pipe(logLevel),
120 { tags: ["provider:gcp", "provider:gcp:dlp", "live"], timeout: 90_000 },
121);