1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as compute from "@distilled.cloud/gcp/compute_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8import {
9 CERT_A_PEM,
10 CERT_B_PEM,
11 KEY_A_PEM,
12 KEY_B_PEM,
13} from "./fixtures/https-proxy-cert.ts";
14import { GcpEnvironment } from "@/GCP/Environment";
15
16const { test } = Test.make({ providers: GCP.providers() });
17
18const logLevel = Effect.provideService(
19 MinimumLogLevel,
20 process.env.DEBUG ? "Debug" : "Info",
21);
22
23const waitUntilGone = (targetHttpsProxyName: string) =>
24 GcpEnvironment.current.pipe(
25 Effect.flatMap(({ project }) =>
26 compute
27 .getTargetHttpsProxies({
28 project,
29 targetHttpsProxy: targetHttpsProxyName,
30 })
31 .pipe(
32 Effect.as("found" as const),
33 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
34 Effect.repeat({
35 schedule: Schedule.spaced("1 second"),
36 until: (status) => status === "gone",
37 times: 10,
38 }),
39 ),
40 ),
41 );
42
43const resourceTail = (value: string | undefined): string => {
44 if (value === undefined || value.length === 0) return "";
45 const parts = value.split("/").filter((part) => part.length > 0);
46 return parts[parts.length - 1] ?? "";
47};
48
49test.provider(
50 "create, update, and delete a target https proxy",
51 (stack) =>
52 Effect.gen(function* () {
53 const { project } = yield* GcpEnvironment.current;
54 yield* stack.destroy();
55
56 const created = yield* stack.deploy(
57 Effect.gen(function* () {
58 const map = yield* GCP.Compute.UrlMap("Web", {
59 description: "https redirect",
60 defaultUrlRedirect: {
61 httpsRedirect: true,
62 hostRedirect: "example.com",
63 stripQuery: false,
64 },
65 });
66 const cert = yield* GCP.Compute.SslCertificate("TlsA", {
67 description: "frontend tls a",
68 certificate: CERT_A_PEM,
69 privateKey: KEY_A_PEM,
70 });
71 const proxy = yield* GCP.Compute.TargetHttpsProxy("Proxy", {
72 description: "https frontend",
73 urlMap: map.urlMapName,
74 sslCertificates: [cert.sslCertificateName],
75 });
76 return { map, cert, proxy };
77 }),
78 );
79
80 expect(created.proxy.targetHttpsProxyName).toEqual(expect.any(String));
81 expect(created.proxy.description).toEqual("https frontend");
82 expect(
83 created.proxy.quicOverride === "NONE" ||
84 created.proxy.quicOverride === undefined,
85 ).toEqual(true);
86 expect(resourceTail(created.proxy.urlMap)).toEqual(
87 created.map.urlMapName,
88 );
89 expect(created.proxy.sslCertificates.map(resourceTail)).toContain(
90 created.cert.sslCertificateName,
91 );
92
93 const fetched = yield* compute.getTargetHttpsProxies({
94 project,
95 targetHttpsProxy: created.proxy.targetHttpsProxyName,
96 });
97 expect(fetched.name).toEqual(created.proxy.targetHttpsProxyName);
98 expect(resourceTail(fetched.urlMap)).toEqual(
99 resourceTail(created.proxy.urlMap),
100 );
101 expect(fetched.description).toContain("[alchemy ");
102 expect(fetched.description).toContain("https frontend");
103 expect((fetched.sslCertificates ?? []).map(resourceTail)).toContain(
104 created.cert.sslCertificateName,
105 );
106
107 const updated = yield* stack.deploy(
108 Effect.gen(function* () {
109 yield* GCP.Compute.UrlMap("Web", {
110 urlMapName: created.map.urlMapName,
111 description: "https redirect",
112 defaultUrlRedirect: {
113 httpsRedirect: true,
114 hostRedirect: "example.com",
115 stripQuery: false,
116 },
117 });
118 const other = yield* GCP.Compute.UrlMap("Other", {
119 description: "alt redirect",
120 defaultUrlRedirect: {
121 httpsRedirect: true,
122 hostRedirect: "example.org",
123 stripQuery: true,
124 },
125 });
126 yield* GCP.Compute.SslCertificate("TlsA", {
127 sslCertificateName: created.cert.sslCertificateName,
128 description: "frontend tls a",
129 certificate: CERT_A_PEM,
130 privateKey: KEY_A_PEM,
131 });
132 const certB = yield* GCP.Compute.SslCertificate("TlsB", {
133 description: "frontend tls b",
134 certificate: CERT_B_PEM,
135 privateKey: KEY_B_PEM,
136 });
137 const proxy = yield* GCP.Compute.TargetHttpsProxy("Proxy", {
138 targetHttpsProxyName: created.proxy.targetHttpsProxyName,
139 description: "updated https",
140 urlMap: other.urlMapName,
141 sslCertificates: [certB.sslCertificateName],
142 quicOverride: "ENABLE",
143 });
144 return { other, certB, proxy };
145 }),
146 );
147
148 expect(updated.proxy.targetHttpsProxyName).toEqual(
149 created.proxy.targetHttpsProxyName,
150 );
151 expect(updated.proxy.description).toEqual("updated https");
152 expect(updated.proxy.quicOverride).toEqual("ENABLE");
153 expect(resourceTail(updated.proxy.urlMap)).toEqual(
154 updated.other.urlMapName,
155 );
156 expect(updated.proxy.sslCertificates.map(resourceTail)).toContain(
157 updated.certB.sslCertificateName,
158 );
159 expect(updated.proxy.sslCertificates.map(resourceTail)).not.toContain(
160 created.cert.sslCertificateName,
161 );
162
163 const refetched = yield* compute.getTargetHttpsProxies({
164 project,
165 targetHttpsProxy: updated.proxy.targetHttpsProxyName,
166 });
167 expect(refetched.description).toContain("updated https");
168 expect(refetched.quicOverride).toEqual("ENABLE");
169 expect(resourceTail(refetched.urlMap)).toEqual(
170 resourceTail(updated.proxy.urlMap),
171 );
172 expect(resourceTail(refetched.urlMap)).not.toEqual(
173 resourceTail(created.proxy.urlMap),
174 );
175 expect((refetched.sslCertificates ?? []).map(resourceTail)).toContain(
176 updated.certB.sslCertificateName,
177 );
178
179 yield* stack.destroy();
180
181 const gone = yield* waitUntilGone(created.proxy.targetHttpsProxyName);
182 expect(gone).toEqual("gone");
183 }).pipe(logLevel),
184 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
185);