1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as acm from "@distilled.cloud/gcp/accesscontextmanager_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import { logLevel, projectContext, runLifecycle, runProbe } from "./common.ts";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const waitUntilGone = (name: string) =>
12 acm.getAccessPolicies({ name }).pipe(
13 Effect.as("found" as const),
14 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
15 Effect.repeat({
16 schedule: Schedule.spaced("2 seconds"),
17 until: (status) => status === "gone",
18 times: 10,
19 }),
20 );
21
22test.provider(
23 "getAccessPolicies on an inaccessible policy fails with Forbidden",
24 (stack) =>
25 Effect.gen(function* () {
26 yield* stack.destroy();
27
28 const error = yield* Effect.flip(
29 acm.getAccessPolicies({ name: "accessPolicies/123456789012" }),
30 );
31 expect(error._tag).toEqual("Forbidden");
32
33 yield* stack.destroy();
34 }).pipe(logLevel),
35 {
36 tags: ["provider:gcp", "provider:gcp:accesscontextmanager", "live"],
37 timeout: 90_000,
38 },
39);
40
41test.provider.skipIf(!runProbe)(
42 "createAccessPolicies without org permissions fails with Forbidden",
43 (stack) =>
44 Effect.gen(function* () {
45 yield* stack.destroy();
46
47 const ctx = yield* projectContext();
48 const error = yield* Effect.flip(
49 acm.createAccessPolicies({
50 body: {
51 title: "alchemy-acm-probe",
52 parent: ctx.organization ?? "organizations/123456789012",
53 scopes:
54 ctx.projectNumber.length > 0
55 ? [`projects/${ctx.projectNumber}`]
56 : undefined,
57 },
58 }),
59 );
60 expect(error._tag).toBe("Forbidden");
61
62 yield* stack.destroy();
63 }).pipe(logLevel),
64 {
65 tags: ["provider:gcp", "provider:gcp:accesscontextmanager", "live"],
66 timeout: 90_000,
67 },
68);
69
70test.provider.skipIf(!runLifecycle)(
71 "create, update, and delete an access policy",
72 (stack) =>
73 Effect.gen(function* () {
74 yield* stack.destroy();
75
76 const ctx = yield* projectContext();
77 const scopes =
78 ctx.projectNumber.length > 0
79 ? [`projects/${ctx.projectNumber}`]
80 : undefined;
81
82 const created = yield* stack.deploy(
83 Effect.gen(function* () {
84 return yield* GCP.AccessContextManager.AccessPolicy("Corp", {
85 title: "corp access policy",
86 scopes,
87 });
88 }),
89 );
90
91 expect(created.name).toMatch(/^accessPolicies\//);
92 expect(created.policyId).toEqual(expect.any(String));
93 expect(created.parent).toMatch(/^organizations\//);
94 expect(created.title).toEqual("corp access policy");
95
96 const fetched = yield* acm.getAccessPolicies({ name: created.name });
97 expect(fetched.name).toEqual(created.name);
98 expect(fetched.title).toContain("alchemy-id=");
99 expect(fetched.title).toContain("corp access policy");
100
101 const updated = yield* stack.deploy(
102 Effect.gen(function* () {
103 return yield* GCP.AccessContextManager.AccessPolicy("Corp", {
104 title: "corp access policy prod",
105 scopes,
106 });
107 }),
108 );
109
110 expect(updated.name).toEqual(created.name);
111 expect(updated.title).toEqual("corp access policy prod");
112
113 const fetchedUpdate = yield* acm.getAccessPolicies({
114 name: updated.name,
115 });
116 expect(fetchedUpdate.title).toContain("corp access policy prod");
117
118 yield* stack.destroy();
119
120 const gone = yield* waitUntilGone(created.name);
121 expect(gone).toEqual("gone");
122 }).pipe(logLevel),
123 {
124 tags: ["provider:gcp", "provider:gcp:accesscontextmanager", "live"],
125 timeout: 120_000,
126 },
127);