1import { GcpEnvironment } from "@/GCP/Environment";
2import * as GCP from "@/GCP";
3import * as Test from "@/Test/Alchemy";
4import * as privateca from "@distilled.cloud/gcp/privateca_v1";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const waitUntilGone = (name: string) =>
18 privateca.getProjectsLocationsCertificateTemplates({ name }).pipe(
19 Effect.as("found" as const),
20 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
21 Effect.repeat({
22 schedule: Schedule.spaced("1 second"),
23 until: (status) => status === "gone",
24 times: 10,
25 }),
26 );
27
28test.provider(
29 "getProjectsLocationsCertificateTemplates on a missing template fails with NotFound",
30 (stack) =>
31 Effect.gen(function* () {
32 const { project } = yield* GcpEnvironment.current;
33
34 yield* stack.destroy();
35
36 const error = yield* Effect.flip(
37 privateca.getProjectsLocationsCertificateTemplates({
38 name: `projects/${project}/locations/us-central1/certificateTemplates/alchemy-template-missing`,
39 }),
40 );
41 expect(error._tag).toBe("NotFound");
42
43 const page = yield* privateca.listProjectsLocationsCertificateTemplates({
44 parent: `projects/${project}/locations/-`,
45 pageSize: 10,
46 });
47 expect(
48 (page.certificateTemplates ?? []).map((template) => template.name),
49 ).not.toContain(
50 `projects/${project}/locations/us-central1/certificateTemplates/alchemy-template-missing`,
51 );
52
53 yield* stack.destroy();
54 }).pipe(logLevel),
55 { tags: ["provider:gcp", "provider:gcp:privateca", "live"], timeout: 90_000 },
56);
57
58test.provider(
59 "create, update, replace, and delete a certificate template",
60 (stack) =>
61 Effect.gen(function* () {
62 yield* stack.destroy();
63
64 const created = yield* stack.deploy(
65 Effect.gen(function* () {
66 return yield* GCP.PrivateCA.CertificateTemplate("LeafTls", {
67 location: "us-central1",
68 description: "leaf tls a",
69 maximumLifetime: "86400s",
70 labels: { env: "test" },
71 identityConstraints: {
72 allowSubjectPassthrough: true,
73 allowSubjectAltNamesPassthrough: true,
74 },
75 passthroughExtensions: {
76 knownExtensions: ["EXTENDED_KEY_USAGE"],
77 },
78 predefinedValues: {
79 caOptions: { isCa: false },
80 keyUsage: {
81 baseKeyUsage: {
82 digitalSignature: true,
83 keyEncipherment: true,
84 },
85 extendedKeyUsage: { serverAuth: true },
86 },
87 },
88 });
89 }),
90 );
91
92 expect(created.name).toContain("/certificateTemplates/");
93 expect(created.certificateTemplateId).toEqual(expect.any(String));
94 expect(created.location).toEqual("us-central1");
95 expect(created.description).toEqual("leaf tls a");
96 expect(created.maximumLifetime).toEqual("86400s");
97 expect(created.labels).toMatchObject({ env: "test" });
98 expect(created.identityConstraints?.allowSubjectPassthrough).toEqual(
99 true,
100 );
101 expect(
102 created.identityConstraints?.allowSubjectAltNamesPassthrough,
103 ).toEqual(true);
104 expect(created.passthroughExtensions?.knownExtensions).toContain(
105 "EXTENDED_KEY_USAGE",
106 );
107 expect(created.predefinedValues?.caOptions?.isCa).toEqual(false);
108 expect(created.createTime).toEqual(expect.any(String));
109
110 const fetched = yield* privateca.getProjectsLocationsCertificateTemplates(
111 {
112 name: created.name,
113 },
114 );
115 expect(fetched.name).toEqual(created.name);
116 expect(fetched.labels?.env).toEqual("test");
117 expect(fetched.description).toEqual("leaf tls a");
118 expect(fetched.maximumLifetime).toEqual("86400s");
119 expect(
120 Object.keys(fetched.labels ?? {}).some((key) =>
121 key.startsWith("alchemy-"),
122 ),
123 ).toEqual(true);
124
125 const updated = yield* stack.deploy(
126 Effect.gen(function* () {
127 return yield* GCP.PrivateCA.CertificateTemplate("LeafTls", {
128 certificateTemplateId: created.certificateTemplateId,
129 location: "us-central1",
130 description: "leaf tls b",
131 maximumLifetime: "172800s",
132 labels: { env: "prod", role: "tls" },
133 identityConstraints: {
134 allowSubjectPassthrough: true,
135 allowSubjectAltNamesPassthrough: false,
136 },
137 passthroughExtensions: {
138 knownExtensions: ["BASE_KEY_USAGE", "EXTENDED_KEY_USAGE"],
139 },
140 predefinedValues: {
141 caOptions: { isCa: false },
142 keyUsage: {
143 baseKeyUsage: {
144 digitalSignature: true,
145 keyEncipherment: true,
146 },
147 extendedKeyUsage: { serverAuth: true, clientAuth: true },
148 },
149 },
150 });
151 }),
152 );
153
154 expect(updated.name).toEqual(created.name);
155 expect(updated.description).toEqual("leaf tls b");
156 expect(updated.maximumLifetime).toEqual("172800s");
157 expect(updated.labels).toMatchObject({ env: "prod", role: "tls" });
158 expect(
159 updated.identityConstraints?.allowSubjectAltNamesPassthrough,
160 ).toEqual(false);
161 expect(updated.passthroughExtensions?.knownExtensions).toEqual(
162 expect.arrayContaining(["BASE_KEY_USAGE", "EXTENDED_KEY_USAGE"]),
163 );
164 expect(
165 updated.predefinedValues?.keyUsage?.extendedKeyUsage?.clientAuth,
166 ).toEqual(true);
167
168 const refetched =
169 yield* privateca.getProjectsLocationsCertificateTemplates({
170 name: created.name,
171 });
172 expect(refetched.description).toEqual("leaf tls b");
173 expect(refetched.labels?.env).toEqual("prod");
174 expect(refetched.labels?.role).toEqual("tls");
175 expect(refetched.maximumLifetime).toEqual("172800s");
176
177 const replaced = yield* stack.deploy(
178 Effect.gen(function* () {
179 return yield* GCP.PrivateCA.CertificateTemplate("LeafTls", {
180 certificateTemplateId: created.certificateTemplateId,
181 location: "us-east1",
182 description: "leaf tls east",
183 maximumLifetime: "86400s",
184 labels: { env: "prod" },
185 identityConstraints: {
186 allowSubjectPassthrough: true,
187 allowSubjectAltNamesPassthrough: true,
188 },
189 });
190 }),
191 );
192
193 expect(replaced.location).toEqual("us-east1");
194 expect(replaced.certificateTemplateId).toEqual(
195 created.certificateTemplateId,
196 );
197 expect(replaced.name).not.toEqual(created.name);
198 expect(replaced.name).toContain("/locations/us-east1/");
199
200 const fetchedReplacement =
201 yield* privateca.getProjectsLocationsCertificateTemplates({
202 name: replaced.name,
203 });
204 expect(fetchedReplacement.name).toEqual(replaced.name);
205 expect(fetchedReplacement.labels?.env).toEqual("prod");
206
207 const previousGone = yield* waitUntilGone(created.name);
208 expect(previousGone).toEqual("gone");
209
210 yield* stack.destroy();
211
212 const gone = yield* waitUntilGone(replaced.name);
213 expect(gone).toEqual("gone");
214 }).pipe(logLevel),
215 {
216 tags: ["provider:gcp", "provider:gcp:privateca", "live"],
217 timeout: 120_000,
218 },
219);