1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as secretmanager from "@distilled.cloud/gcp/secretmanager_v1";
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
11const logLevel = Effect.provideService(
12 MinimumLogLevel,
13 process.env.DEBUG ? "Debug" : "Info",
14);
15
16const waitUntilGone = (name: string) =>
17 secretmanager.getProjectsSecrets({ name }).pipe(
18 Effect.as("found" as const),
19 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
20 Effect.repeat({
21 schedule: Schedule.spaced("1 second"),
22 until: (status) => status === "gone",
23 times: 10,
24 }),
25 );
26
27test.provider(
28 "create, update, and delete a secret",
29 (stack) =>
30 Effect.gen(function* () {
31 yield* stack.destroy();
32
33 const created = yield* stack.deploy(
34 Effect.gen(function* () {
35 return yield* GCP.SecretManager.Secret("ApiKey", {
36 labels: { env: "test" },
37 annotations: { owner: "payments" },
38 });
39 }),
40 );
41
42 expect(created.name).toContain("/secrets/");
43 expect(created.secretId).toEqual(expect.any(String));
44 expect(created.labels).toMatchObject({ env: "test" });
45 expect(created.annotations).toMatchObject({ owner: "payments" });
46 expect(created.replication?.automatic).toBeDefined();
47
48 const fetched = yield* secretmanager.getProjectsSecrets({
49 name: created.name,
50 });
51 expect(fetched.name).toEqual(created.name);
52 expect(fetched.labels?.env).toEqual("test");
53 expect(fetched.labels?.["alchemy-id"]).toEqual(expect.any(String));
54 expect(fetched.annotations?.owner).toEqual("payments");
55 expect(fetched.replication?.automatic).toBeDefined();
56
57 const payload = yield* Effect.sync(() =>
58 Buffer.from("alchemy-secret-payload", "utf8").toString("base64"),
59 );
60 const version = yield* secretmanager.addVersionProjectsSecrets({
61 parent: created.name,
62 body: { payload: { data: payload } },
63 });
64 expect(version.name).toContain("/versions/");
65
66 const accessed = yield* secretmanager.accessProjectsSecretsVersions({
67 name: `${created.name}/versions/latest`,
68 });
69 expect(accessed.payload?.data).toEqual(payload);
70
71 const updated = yield* stack.deploy(
72 Effect.gen(function* () {
73 return yield* GCP.SecretManager.Secret("ApiKey", {
74 secretId: created.secretId,
75 labels: { env: "prod", role: "api" },
76 annotations: { owner: "platform" },
77 });
78 }),
79 );
80
81 expect(updated.name).toEqual(created.name);
82 expect(updated.labels).toMatchObject({ env: "prod", role: "api" });
83 expect(updated.annotations).toMatchObject({ owner: "platform" });
84
85 const fetchedUpdate = yield* secretmanager.getProjectsSecrets({
86 name: created.name,
87 });
88 expect(fetchedUpdate.labels?.env).toEqual("prod");
89 expect(fetchedUpdate.labels?.role).toEqual("api");
90 expect(fetchedUpdate.annotations?.owner).toEqual("platform");
91
92 yield* stack.destroy();
93
94 const gone = yield* waitUntilGone(created.name);
95 expect(gone).toEqual("gone");
96 }).pipe(logLevel),
97 {
98 tags: ["provider:gcp", "provider:gcp:secretmanager", "live"],
99 timeout: 90_000,
100 },
101);
102
103test.provider(
104 "replace a secret when replication changes",
105 (stack) =>
106 Effect.gen(function* () {
107 yield* stack.destroy();
108
109 const created = yield* stack.deploy(
110 Effect.gen(function* () {
111 return yield* GCP.SecretManager.Secret("RegionalKey", {
112 replication: { automatic: {} },
113 });
114 }),
115 );
116
117 expect(created.replication?.automatic).toBeDefined();
118 expect(created.replication?.userManaged).toBeUndefined();
119
120 const replaced = yield* stack.deploy(
121 Effect.gen(function* () {
122 return yield* GCP.SecretManager.Secret("RegionalKey", {
123 secretId: created.secretId,
124 replication: {
125 userManaged: {
126 replicas: [{ location: "us-central1" }],
127 },
128 },
129 });
130 }),
131 );
132
133 expect(replaced.name).toEqual(created.name);
134 expect(replaced.secretId).toEqual(created.secretId);
135 expect(replaced.replication?.automatic).toBeUndefined();
136 expect(
137 replaced.replication?.userManaged?.replicas?.[0]?.location,
138 ).toEqual("us-central1");
139
140 const fetched = yield* secretmanager.getProjectsSecrets({
141 name: replaced.name,
142 });
143 expect(fetched.replication?.automatic).toBeUndefined();
144 expect(fetched.replication?.userManaged?.replicas?.[0]?.location).toEqual(
145 "us-central1",
146 );
147
148 yield* stack.destroy();
149
150 const gone = yield* waitUntilGone(replaced.name);
151 expect(gone).toEqual("gone");
152 }).pipe(logLevel),
153 {
154 tags: ["provider:gcp", "provider:gcp:secretmanager", "live"],
155 timeout: 90_000,
156 },
157);