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 = (targetTcpProxyName: string) =>
18 GcpEnvironment.current.pipe(
19 Effect.flatMap(({ project }) =>
20 compute
21 .getTargetTcpProxies({ project, targetTcpProxy: targetTcpProxyName })
22 .pipe(
23 Effect.as("found" as const),
24 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
25 Effect.repeat({
26 schedule: Schedule.spaced("1 second"),
27 until: (status) => status === "gone",
28 times: 10,
29 }),
30 ),
31 ),
32 );
33
34const resourceTail = (value: string | undefined): string => {
35 if (value === undefined || value.length === 0) return "";
36 const parts = value.split("/").filter((part) => part.length > 0);
37 return parts[parts.length - 1] ?? "";
38};
39
40test.provider(
41 "create, update, and delete a target tcp proxy",
42 (stack) =>
43 Effect.gen(function* () {
44 const { project } = yield* GcpEnvironment.current;
45 yield* stack.destroy();
46
47 const created = yield* stack.deploy(
48 Effect.gen(function* () {
49 const check = yield* GCP.Compute.HealthCheck("Probe", {
50 description: "tcp probe",
51 type: "TCP",
52 tcpHealthCheck: { port: 80 },
53 });
54 const backend = yield* GCP.Compute.BackendService("TcpA", {
55 protocol: "TCP",
56 loadBalancingScheme: "EXTERNAL",
57 description: "tcp backend a",
58 healthChecks: [check.selfLink.as<string>()],
59 });
60 const proxy = yield* GCP.Compute.TargetTcpProxy("Proxy", {
61 description: "tcp frontend",
62 service: backend.name,
63 proxyHeader: "NONE",
64 });
65 return { check, backend, proxy };
66 }),
67 );
68
69 expect(created.proxy.targetTcpProxyName).toEqual(expect.any(String));
70 expect(created.proxy.description).toEqual("tcp frontend");
71 expect(
72 created.proxy.proxyHeader === "NONE" ||
73 created.proxy.proxyHeader === undefined,
74 ).toEqual(true);
75 expect(resourceTail(created.proxy.service)).toEqual(created.backend.name);
76
77 const fetched = yield* compute.getTargetTcpProxies({
78 project,
79 targetTcpProxy: created.proxy.targetTcpProxyName,
80 });
81 expect(fetched.name).toEqual(created.proxy.targetTcpProxyName);
82 expect(resourceTail(fetched.service)).toEqual(
83 resourceTail(created.proxy.service),
84 );
85 expect(fetched.proxyHeader ?? "NONE").toEqual("NONE");
86 expect(fetched.description).toContain("[alchemy ");
87 expect(fetched.description).toContain("tcp frontend");
88
89 const updated = yield* stack.deploy(
90 Effect.gen(function* () {
91 const check = yield* GCP.Compute.HealthCheck("Probe", {
92 healthCheckName: created.check.healthCheckName,
93 description: "tcp probe",
94 type: "TCP",
95 tcpHealthCheck: { port: 80 },
96 });
97 yield* GCP.Compute.BackendService("TcpA", {
98 name: created.backend.name,
99 protocol: "TCP",
100 loadBalancingScheme: "EXTERNAL",
101 description: "tcp backend a",
102 healthChecks: [check.selfLink.as<string>()],
103 });
104 const other = yield* GCP.Compute.BackendService("TcpB", {
105 protocol: "TCP",
106 loadBalancingScheme: "EXTERNAL",
107 description: "tcp backend b",
108 healthChecks: [check.selfLink.as<string>()],
109 });
110 const proxy = yield* GCP.Compute.TargetTcpProxy("Proxy", {
111 targetTcpProxyName: created.proxy.targetTcpProxyName,
112 description: "tcp frontend",
113 service: other.name,
114 proxyHeader: "PROXY_V1",
115 });
116 return { other, proxy };
117 }),
118 );
119
120 expect(updated.proxy.targetTcpProxyName).toEqual(
121 created.proxy.targetTcpProxyName,
122 );
123 expect(updated.proxy.description).toEqual("tcp frontend");
124 expect(updated.proxy.proxyHeader).toEqual("PROXY_V1");
125 expect(resourceTail(updated.proxy.service)).toEqual(updated.other.name);
126
127 const refetched = yield* compute.getTargetTcpProxies({
128 project,
129 targetTcpProxy: updated.proxy.targetTcpProxyName,
130 });
131 expect(refetched.proxyHeader).toEqual("PROXY_V1");
132 expect(resourceTail(refetched.service)).toEqual(
133 resourceTail(updated.proxy.service),
134 );
135 expect(resourceTail(refetched.service)).not.toEqual(
136 resourceTail(created.proxy.service),
137 );
138
139 yield* stack.destroy();
140
141 const gone = yield* waitUntilGone(created.proxy.targetTcpProxyName);
142 expect(gone).toEqual("gone");
143 }).pipe(logLevel),
144 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 120_000 },
145);