1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as managedidentities from "@distilled.cloud/gcp/managedidentities_v1";
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
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17// Backups require a provisioned Managed AD domain (20-60 minutes).
18// Create against a missing parent returns `NotFound` (`parent resource
19// not found for .../domains/missing.alch.test/backups/...`). Set
20// GCP_TEST_MANAGEDIDENTITIES=1 to run the full lifecycle.
21const runLifecycle =
22 !process.env.FAST && process.env.GCP_TEST_MANAGEDIDENTITIES === "1";
23
24const waitUntilGone = (name: string) =>
25 managedidentities.getProjectsLocationsGlobalDomainsBackups({ name }).pipe(
26 Effect.as("found" as const),
27 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
28 Effect.repeat({
29 schedule: Schedule.spaced("2 seconds"),
30 until: (status) => status === "gone",
31 times: 10,
32 }),
33 );
34
35test.provider(
36 "getProjectsLocationsGlobalDomainsBackups on a missing backup fails with a typed tag",
37 (stack) =>
38 Effect.gen(function* () {
39 const { project } = yield* GcpEnvironment.current;
40 const missingDomain = `projects/${project}/locations/global/domains/missing.alch.test`;
41 yield* stack.destroy();
42
43 const error = yield* Effect.flip(
44 managedidentities.getProjectsLocationsGlobalDomainsBackups({
45 name: `${missingDomain}/backups/alchemy-backup-missing`,
46 }),
47 );
48 expect(error._tag).toEqual("NotFound");
49
50 yield* stack.destroy();
51 }).pipe(logLevel),
52 {
53 tags: ["provider:gcp", "provider:gcp:managedidentities", "live"],
54 timeout: 90_000,
55 },
56);
57
58test.provider.skipIf(runLifecycle)(
59 "create is rejected with a typed tag when the parent domain is missing",
60 (stack) =>
61 Effect.gen(function* () {
62 const { project } = yield* GcpEnvironment.current;
63 const missingDomain = `projects/${project}/locations/global/domains/missing.alch.test`;
64 yield* stack.destroy();
65
66 const error = yield* Effect.flip(
67 stack.deploy(
68 Effect.gen(function* () {
69 return yield* GCP.ManagedIdentities.DomainsBackup("Nightly", {
70 domain: missingDomain,
71 labels: { env: "test" },
72 });
73 }),
74 ),
75 );
76 expect(error._tag).toEqual("NotFound");
77
78 yield* stack.destroy();
79 }).pipe(logLevel),
80 {
81 tags: ["provider:gcp", "provider:gcp:managedidentities", "live"],
82 timeout: 90_000,
83 },
84);
85
86test.provider.skipIf(!runLifecycle)(
87 "create, update, and delete a domain backup",
88 (stack) =>
89 Effect.gen(function* () {
90 yield* stack.destroy();
91
92 const created = yield* stack.deploy(
93 Effect.gen(function* () {
94 const domain = yield* GCP.ManagedIdentities.Domain("Ad", {
95 reservedIpRange: "172.16.1.0/24",
96 locations: ["us-central1"],
97 authorizedNetworks: ["default"],
98 labels: { env: "test" },
99 });
100 const backup = yield* GCP.ManagedIdentities.DomainsBackup("Nightly", {
101 domain: domain.name,
102 labels: { env: "test" },
103 });
104 return { domain, backup };
105 }),
106 );
107
108 expect(created.backup.name).toContain("/backups/");
109 expect(created.backup.backupId).toEqual(expect.any(String));
110 expect(created.backup.domain).toEqual(created.domain.name);
111 expect(created.backup.labels).toMatchObject({ env: "test" });
112
113 const fetched =
114 yield* managedidentities.getProjectsLocationsGlobalDomainsBackups({
115 name: created.backup.name,
116 });
117 expect(fetched.name).toEqual(created.backup.name);
118 expect(fetched.labels?.env).toEqual("test");
119
120 const updated = yield* stack.deploy(
121 Effect.gen(function* () {
122 const domain = yield* GCP.ManagedIdentities.Domain("Ad", {
123 domainName: created.domain.domainName,
124 reservedIpRange: "172.16.1.0/24",
125 locations: ["us-central1"],
126 authorizedNetworks: ["default"],
127 labels: { env: "test" },
128 });
129 const backup = yield* GCP.ManagedIdentities.DomainsBackup("Nightly", {
130 domain: domain.name,
131 backupId: created.backup.backupId,
132 labels: { env: "prod", role: "backup" },
133 });
134 return { domain, backup };
135 }),
136 );
137
138 expect(updated.backup.name).toEqual(created.backup.name);
139 expect(updated.backup.labels).toMatchObject({
140 env: "prod",
141 role: "backup",
142 });
143
144 const refetched =
145 yield* managedidentities.getProjectsLocationsGlobalDomainsBackups({
146 name: created.backup.name,
147 });
148 expect(refetched.labels?.env).toEqual("prod");
149 expect(refetched.labels?.role).toEqual("backup");
150
151 yield* stack.destroy();
152
153 const gone = yield* waitUntilGone(created.backup.name);
154 expect(gone).toEqual("gone");
155 }).pipe(logLevel),
156 {
157 tags: ["provider:gcp", "provider:gcp:managedidentities", "live"],
158 timeout: 240_000,
159 },
160);