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";
14
15const { test } = Test.make({ providers: GCP.providers() });
16
17const logLevel = Effect.provideService(
18 MinimumLogLevel,
19 process.env.DEBUG ? "Debug" : "Info",
20);
21
22const region = "us-central1";
23
24const waitUntilGone = (project: string, targetHttpsProxyName: string) =>
25 compute
26 .getRegionTargetHttpsProxies({
27 project,
28 region,
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
41const resourceTail = (value: string | undefined): string => {
42 if (value === undefined || value.length === 0) return "";
43 const parts = value.split("/").filter((part) => part.length > 0);
44 return parts[parts.length - 1] ?? "";
45};
46
47test.provider(
48 "create, update, and delete a regional target https proxy",
49 (stack) =>
50 Effect.gen(function* () {
51 yield* stack.destroy();
52
53 const created = yield* stack.deploy(
54 Effect.gen(function* () {
55 const map = yield* GCP.Compute.RegionUrlMap("Web", {
56 region,
57 description: "https redirect",
58 defaultUrlRedirect: {
59 httpsRedirect: true,
60 hostRedirect: "example.com",
61 stripQuery: false,
62 },
63 });
64 const cert = yield* GCP.Compute.RegionSslCertificate("TlsA", {
65 region,
66 description: "frontend tls a",
67 certificate: CERT_A_PEM,
68 privateKey: KEY_A_PEM,
69 });
70 const proxy = yield* GCP.Compute.RegionTargetHttpsProxy("Proxy", {
71 region,
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.region).toEqual(region);
82 expect(created.proxy.description).toEqual("https frontend");
83 expect(resourceTail(created.proxy.urlMap)).toEqual(
84 created.map.urlMapName,
85 );
86 expect(created.proxy.sslCertificates.map(resourceTail)).toContain(
87 created.cert.sslCertificateName,
88 );
89
90 const fetched = yield* compute.getRegionTargetHttpsProxies({
91 project: created.proxy.project,
92 region,
93 targetHttpsProxy: created.proxy.targetHttpsProxyName,
94 });
95 expect(fetched.name).toEqual(created.proxy.targetHttpsProxyName);
96 expect(resourceTail(fetched.urlMap)).toEqual(
97 resourceTail(created.proxy.urlMap),
98 );
99 expect(fetched.description).toContain("[alchemy ");
100 expect(fetched.description).toContain("https frontend");
101 expect((fetched.sslCertificates ?? []).map(resourceTail)).toContain(
102 created.cert.sslCertificateName,
103 );
104
105 const updated = yield* stack.deploy(
106 Effect.gen(function* () {
107 yield* GCP.Compute.RegionUrlMap("Web", {
108 urlMapName: created.map.urlMapName,
109 region,
110 description: "https redirect",
111 defaultUrlRedirect: {
112 httpsRedirect: true,
113 hostRedirect: "example.com",
114 stripQuery: false,
115 },
116 });
117 const other = yield* GCP.Compute.RegionUrlMap("Other", {
118 region,
119 description: "alt redirect",
120 defaultUrlRedirect: {
121 httpsRedirect: true,
122 hostRedirect: "example.org",
123 stripQuery: true,
124 },
125 });
126 yield* GCP.Compute.RegionSslCertificate("TlsA", {
127 sslCertificateName: created.cert.sslCertificateName,
128 region,
129 description: "frontend tls a",
130 certificate: CERT_A_PEM,
131 privateKey: KEY_A_PEM,
132 });
133 const certB = yield* GCP.Compute.RegionSslCertificate("TlsB", {
134 region,
135 description: "frontend tls b",
136 certificate: CERT_B_PEM,
137 privateKey: KEY_B_PEM,
138 });
139 const proxy = yield* GCP.Compute.RegionTargetHttpsProxy("Proxy", {
140 targetHttpsProxyName: created.proxy.targetHttpsProxyName,
141 region,
142 description: "updated https",
143 urlMap: other.urlMapName,
144 sslCertificates: [certB.sslCertificateName],
145 });
146 return { other, certB, proxy };
147 }),
148 );
149
150 expect(updated.proxy.targetHttpsProxyName).toEqual(
151 created.proxy.targetHttpsProxyName,
152 );
153 expect(updated.proxy.description).toEqual("updated https");
154 expect(resourceTail(updated.proxy.urlMap)).toEqual(
155 updated.other.urlMapName,
156 );
157 expect(updated.proxy.sslCertificates.map(resourceTail)).toContain(
158 updated.certB.sslCertificateName,
159 );
160 expect(updated.proxy.sslCertificates.map(resourceTail)).not.toContain(
161 created.cert.sslCertificateName,
162 );
163
164 const refetched = yield* compute.getRegionTargetHttpsProxies({
165 project: updated.proxy.project,
166 region,
167 targetHttpsProxy: updated.proxy.targetHttpsProxyName,
168 });
169 expect(refetched.description).toContain("updated https");
170 expect(resourceTail(refetched.urlMap)).toEqual(
171 resourceTail(updated.proxy.urlMap),
172 );
173 expect(resourceTail(refetched.urlMap)).not.toEqual(
174 resourceTail(created.proxy.urlMap),
175 );
176 expect((refetched.sslCertificates ?? []).map(resourceTail)).toContain(
177 updated.certB.sslCertificateName,
178 );
179
180 yield* stack.destroy();
181
182 const gone = yield* waitUntilGone(
183 created.proxy.project,
184 created.proxy.targetHttpsProxyName,
185 );
186 expect(gone).toEqual("gone");
187 }).pipe(logLevel),
188 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
189);