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 {
8 lastSegment,
9 logLevel,
10 projectContext,
11 runLifecycle,
12} from "./common.ts";
13
14const { test } = Test.make({ providers: GCP.providers() });
15
16const waitUntilGone = (name: string) =>
17 acm.getAccessPoliciesAuthorizedOrgsDescs({ name }).pipe(
18 Effect.as("found" as const),
19 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
20 Effect.repeat({
21 schedule: Schedule.spaced("2 seconds"),
22 until: (status) => status === "gone",
23 times: 10,
24 }),
25 );
26
27test.provider(
28 "getAccessPoliciesAuthorizedOrgsDescs on an inaccessible descriptor fails with Forbidden",
29 (stack) =>
30 Effect.gen(function* () {
31 yield* stack.destroy();
32
33 const error = yield* Effect.flip(
34 acm.getAccessPoliciesAuthorizedOrgsDescs({
35 name: "accessPolicies/123456789012/authorizedOrgsDescs/alchemy_missing_desc",
36 }),
37 );
38 expect(error._tag).toEqual("Forbidden");
39
40 yield* stack.destroy();
41 }).pipe(logLevel),
42 {
43 tags: ["provider:gcp", "provider:gcp:accesscontextmanager", "live"],
44 timeout: 90_000,
45 },
46);
47
48test.provider.skipIf(!runLifecycle)(
49 "create, update, and delete an authorized orgs descriptor",
50 (stack) =>
51 Effect.gen(function* () {
52 yield* stack.destroy();
53
54 const ctx = yield* projectContext();
55 const scopes =
56 ctx.projectNumber.length > 0
57 ? [`projects/${ctx.projectNumber}`]
58 : undefined;
59 const organization =
60 ctx.organization ??
61 (ctx.parent.startsWith("organizations/")
62 ? ctx.parent
63 : "organizations/123456789012");
64
65 const created = yield* stack.deploy(
66 Effect.gen(function* () {
67 const policy = yield* GCP.AccessContextManager.AccessPolicy(
68 "OrgsPolicy",
69 {
70 title: "authorized orgs policy",
71 scopes,
72 },
73 );
74 const desc =
75 yield* GCP.AccessContextManager.AccessPoliciesAuthorizedOrgsDesc(
76 "Partner",
77 {
78 policy: policy.name,
79 authorizationDirection: "AUTHORIZATION_DIRECTION_FROM",
80 assetType: "ASSET_TYPE_DEVICE",
81 authorizationType: "AUTHORIZATION_TYPE_TRUST",
82 orgs: [organization],
83 },
84 );
85 return { policy, desc };
86 }),
87 );
88
89 expect(created.desc.name).toContain("/authorizedOrgsDescs/");
90 expect(created.desc.policy).toEqual(created.policy.name);
91 expect(created.desc.authorizationDirection).toEqual(
92 "AUTHORIZATION_DIRECTION_FROM",
93 );
94 expect(created.desc.assetType).toEqual("ASSET_TYPE_DEVICE");
95 expect(created.desc.orgs.map(lastSegment)).toContain(
96 lastSegment(organization),
97 );
98
99 const fetched = yield* acm.getAccessPoliciesAuthorizedOrgsDescs({
100 name: created.desc.name,
101 });
102 expect(fetched.name).toEqual(created.desc.name);
103
104 const updated = yield* stack.deploy(
105 Effect.gen(function* () {
106 const policy = yield* GCP.AccessContextManager.AccessPolicy(
107 "OrgsPolicy",
108 {
109 title: "authorized orgs policy",
110 scopes,
111 },
112 );
113 const desc =
114 yield* GCP.AccessContextManager.AccessPoliciesAuthorizedOrgsDesc(
115 "Partner",
116 {
117 policy: policy.name,
118 authorizedOrgsDescId: created.desc.authorizedOrgsDescId,
119 authorizationDirection: "AUTHORIZATION_DIRECTION_FROM",
120 assetType: "ASSET_TYPE_DEVICE",
121 authorizationType: "AUTHORIZATION_TYPE_TRUST",
122 orgs: [organization],
123 },
124 );
125 return { policy, desc };
126 }),
127 );
128
129 expect(updated.desc.name).toEqual(created.desc.name);
130
131 yield* stack.destroy();
132
133 const gone = yield* waitUntilGone(created.desc.name);
134 expect(gone).toEqual("gone");
135 }).pipe(logLevel),
136 {
137 tags: ["provider:gcp", "provider:gcp:accesscontextmanager", "live"],
138 timeout: 120_000,
139 },
140);