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 { 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
17const waitUntilGone = (forwardingRuleName: string) =>
18 GcpEnvironment.current.pipe(
19 Effect.flatMap(({ project }) =>
20 compute
21 .getGlobalForwardingRules({
22 project,
23 forwardingRule: forwardingRuleName,
24 })
25 .pipe(
26 Effect.as("found" as const),
27 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
28 Effect.repeat({
29 schedule: Schedule.spaced("1 second"),
30 until: (status) => status === "gone",
31 times: 10,
32 }),
33 ),
34 ),
35 );
36
37const resourceTail = (value: string | undefined): string => {
38 if (value === undefined || value.length === 0) return "";
39 const parts = value.split("/").filter((part) => part.length > 0);
40 return (parts[parts.length - 1] ?? "").toLowerCase();
41};
42
43const httpsRedirect = (host: string) => ({
44 httpsRedirect: true,
45 hostRedirect: host,
46 stripQuery: false,
47});
48
49test.provider(
50 "create, update, and delete a global forwarding rule",
51 (stack) =>
52 Effect.gen(function* () {
53 yield* stack.destroy();
54
55 const created = yield* stack.deploy(
56 Effect.gen(function* () {
57 const map = yield* GCP.Compute.UrlMap("Web", {
58 description: "https redirect",
59 defaultUrlRedirect: httpsRedirect("example.com"),
60 });
61 const proxy = yield* GCP.Compute.TargetHttpProxy("Proxy", {
62 description: "http frontend",
63 urlMap: map.urlMapName,
64 });
65 return yield* GCP.Compute.GlobalForwardingRule("Http", {
66 description: "alchemy test global forwarding rule",
67 target: proxy.selfLink.as<string>(),
68 portRange: "80",
69 loadBalancingScheme: "EXTERNAL",
70 labels: { env: "test" },
71 });
72 }),
73 );
74
75 expect(created.forwardingRuleName).toEqual(expect.any(String));
76 expect(created.ipAddress).toEqual(expect.any(String));
77 expect(created.ipProtocol).toEqual("TCP");
78 expect(created.loadBalancingScheme).toEqual("EXTERNAL");
79 expect(created.labels).toMatchObject({ env: "test" });
80 expect(created.description).toEqual(
81 "alchemy test global forwarding rule",
82 );
83 expect(resourceTail(created.target).length).toBeGreaterThan(0);
84
85 const fetched = yield* compute.getGlobalForwardingRules({
86 project: created.project,
87 forwardingRule: created.forwardingRuleName,
88 });
89 expect(fetched.name).toEqual(created.forwardingRuleName);
90 expect(fetched.IPAddress).toEqual(created.ipAddress);
91 expect(fetched.description).toContain("alchemy-id=");
92 expect(fetched.description).toContain(
93 "alchemy test global forwarding rule",
94 );
95 expect(fetched.labels?.env).toEqual("test");
96 expect(fetched.labels?.["alchemy-id"]).toEqual(expect.any(String));
97 expect(resourceTail(fetched.target)).toEqual(
98 resourceTail(created.target),
99 );
100
101 const updated = yield* stack.deploy(
102 Effect.gen(function* () {
103 const map = yield* GCP.Compute.UrlMap("Web", {
104 description: "https redirect",
105 defaultUrlRedirect: httpsRedirect("example.com"),
106 });
107 const proxy = yield* GCP.Compute.TargetHttpProxy("Proxy", {
108 description: "http frontend",
109 urlMap: map.urlMapName,
110 });
111 return yield* GCP.Compute.GlobalForwardingRule("Http", {
112 forwardingRuleName: created.forwardingRuleName,
113 description: "alchemy test global forwarding rule",
114 target: proxy.selfLink.as<string>(),
115 portRange: "80",
116 loadBalancingScheme: "EXTERNAL",
117 labels: { env: "prod", role: "edge" },
118 });
119 }),
120 );
121
122 expect(updated.forwardingRuleName).toEqual(created.forwardingRuleName);
123 expect(updated.ipAddress).toEqual(created.ipAddress);
124 expect(updated.labels).toMatchObject({ env: "prod", role: "edge" });
125 expect(resourceTail(updated.target)).toEqual(
126 resourceTail(created.target),
127 );
128
129 const refetched = yield* compute.getGlobalForwardingRules({
130 project: updated.project,
131 forwardingRule: updated.forwardingRuleName,
132 });
133 expect(refetched.labels?.env).toEqual("prod");
134 expect(refetched.labels?.role).toEqual("edge");
135 expect(resourceTail(refetched.target)).toEqual(
136 resourceTail(updated.target),
137 );
138
139 yield* stack.destroy();
140
141 const gone = yield* waitUntilGone(created.forwardingRuleName);
142 expect(gone).toEqual("gone");
143 }).pipe(logLevel),
144 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 120_000 },
145);