1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as cloudidentity from "@distilled.cloud/gcp/cloudidentity_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import { customer, domain, logLevel, runGroupLifecycle } from "./common.ts";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const waitUntilGone = (name: string) =>
12 cloudidentity.getGroups({ name }).pipe(
13 Effect.as("found" as const),
14 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
15 Effect.repeat({
16 schedule: Schedule.spaced("1 second"),
17 until: (status) => status === "gone",
18 times: 10,
19 }),
20 );
21
22test.provider(
23 "getGroups on a missing group fails with a typed tag",
24 (stack) =>
25 Effect.gen(function* () {
26 yield* stack.destroy();
27
28 const error = yield* Effect.flip(
29 cloudidentity.getGroups({ name: "groups/alchemy-missing-group" }),
30 );
31 expect(error._tag).toEqual("ServiceDisabled");
32
33 yield* stack.destroy();
34 }).pipe(logLevel),
35 {
36 tags: ["provider:gcp", "provider:gcp:cloudidentity", "live"],
37 timeout: 90_000,
38 },
39);
40
41test.provider.skipIf(!!process.env.GCP_TEST_CLOUDIDENTITY)(
42 "createGroups without Cloud Identity access fails with a typed entitlement error",
43 (stack) =>
44 Effect.gen(function* () {
45 yield* stack.destroy();
46
47 const error = yield* Effect.flip(
48 cloudidentity.createGroups({
49 initialGroupConfig: "EMPTY",
50 body: {
51 parent: customer,
52 groupKey: { id: "alchemy-probe@example.com" },
53 labels: {
54 "cloudidentity.googleapis.com/groups.discussion_forum": "",
55 },
56 displayName: "Alchemy Cloud Identity Probe",
57 },
58 }),
59 );
60 expect(error._tag).toEqual("ServiceDisabled");
61
62 yield* stack.destroy();
63 }).pipe(logLevel),
64 {
65 tags: ["provider:gcp", "provider:gcp:cloudidentity", "live"],
66 timeout: 90_000,
67 },
68);
69
70test.provider.skipIf(!runGroupLifecycle)(
71 "create, update, and delete a group",
72 (stack) =>
73 Effect.gen(function* () {
74 yield* stack.destroy();
75
76 const created = yield* stack.deploy(
77 Effect.gen(function* () {
78 return yield* GCP.CloudIdentity.Group("Eng", {
79 parent: customer,
80 domain,
81 displayName: "Engineering",
82 description: "product engineering",
83 });
84 }),
85 );
86
87 expect(created.name.startsWith("groups/")).toEqual(true);
88 expect(created.displayName).toEqual("Engineering");
89 expect(created.description).toEqual("product engineering");
90 expect(created.groupKeyId).toContain("@");
91
92 const fetched = yield* cloudidentity.getGroups({ name: created.name });
93 expect(fetched.name).toEqual(created.name);
94 expect(fetched.description).toEqual("product engineering");
95
96 const updated = yield* stack.deploy(
97 Effect.gen(function* () {
98 return yield* GCP.CloudIdentity.Group("Eng", {
99 parent: customer,
100 groupKeyId: created.groupKeyId,
101 displayName: "Engineering 2026",
102 description: "product engineering and platform",
103 });
104 }),
105 );
106
107 expect(updated.name).toEqual(created.name);
108 expect(updated.displayName).toEqual("Engineering 2026");
109 expect(updated.description).toEqual("product engineering and platform");
110
111 yield* stack.destroy();
112
113 const gone = yield* waitUntilGone(created.name);
114 expect(gone).toEqual("gone");
115 }).pipe(logLevel),
116 {
117 tags: ["provider:gcp", "provider:gcp:cloudidentity", "live"],
118 timeout: 90_000,
119 },
120);