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 = (targetGrpcProxyName: string) =>
18 GcpEnvironment.current.pipe(
19 Effect.flatMap(({ project }) =>
20 compute
21 .getTargetGrpcProxies({ project, targetGrpcProxy: targetGrpcProxyName })
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 grpc 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 backend = yield* GCP.Compute.BackendService("Grpc", {
50 protocol: "GRPC",
51 loadBalancingScheme: "INTERNAL_SELF_MANAGED",
52 description: "grpc backend",
53 });
54 const map = yield* GCP.Compute.UrlMap("Map", {
55 description: "grpc routes",
56 defaultService: backend.selfLink,
57 });
58 return yield* GCP.Compute.TargetGrpcProxy("Proxy", {
59 description: "grpc frontend",
60 urlMap: map.urlMapName,
61 });
62 }),
63 );
64
65 expect(created.targetGrpcProxyName).toEqual(expect.any(String));
66 expect(created.description).toEqual("grpc frontend");
67 expect(created.validateForProxyless).toEqual(false);
68 expect(resourceTail(created.urlMap).length).toBeGreaterThan(0);
69
70 const fetched = yield* compute.getTargetGrpcProxies({
71 project,
72 targetGrpcProxy: created.targetGrpcProxyName,
73 });
74 expect(fetched.name).toEqual(created.targetGrpcProxyName);
75 expect(resourceTail(fetched.urlMap)).toEqual(
76 resourceTail(created.urlMap),
77 );
78 expect(fetched.validateForProxyless).toEqual(false);
79 expect(fetched.description).toContain("[alchemy ");
80 expect(fetched.description).toContain("grpc frontend");
81
82 const updated = yield* stack.deploy(
83 Effect.gen(function* () {
84 const backend = yield* GCP.Compute.BackendService("Grpc", {
85 protocol: "GRPC",
86 loadBalancingScheme: "INTERNAL_SELF_MANAGED",
87 description: "grpc backend",
88 });
89 const map = yield* GCP.Compute.UrlMap("Map", {
90 description: "grpc routes",
91 defaultService: backend.selfLink,
92 });
93 return yield* GCP.Compute.TargetGrpcProxy("Proxy", {
94 targetGrpcProxyName: created.targetGrpcProxyName,
95 description: "updated frontend",
96 urlMap: map.urlMapName,
97 });
98 }),
99 );
100
101 expect(updated.targetGrpcProxyName).toEqual(created.targetGrpcProxyName);
102 expect(updated.description).toEqual("updated frontend");
103 expect(resourceTail(updated.urlMap)).toEqual(
104 resourceTail(created.urlMap),
105 );
106
107 const refetched = yield* compute.getTargetGrpcProxies({
108 project,
109 targetGrpcProxy: updated.targetGrpcProxyName,
110 });
111 expect(refetched.description).toContain("updated frontend");
112 expect(resourceTail(refetched.urlMap)).toEqual(
113 resourceTail(updated.urlMap),
114 );
115
116 yield* stack.destroy();
117
118 const gone = yield* waitUntilGone(created.targetGrpcProxyName);
119 expect(gone).toEqual("gone");
120 }).pipe(logLevel),
121 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 180_000 },
122);