1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as servicedirectory from "@distilled.cloud/gcp/servicedirectory_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 = (name: string) =>
18 servicedirectory
19 .getProjectsLocationsNamespacesServicesEndpoints({ name })
20 .pipe(
21 Effect.as("found" as const),
22 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
23 Effect.repeat({
24 schedule: Schedule.spaced("1 second"),
25 until: (status) => status === "gone",
26 times: 10,
27 }),
28 );
29
30test.provider(
31 "getProjectsLocationsNamespacesServicesEndpoints on a missing endpoint fails with NotFound",
32 () =>
33 Effect.gen(function* () {
34 const { project } = yield* GcpEnvironment.current;
35
36 const error = yield* Effect.flip(
37 servicedirectory.getProjectsLocationsNamespacesServicesEndpoints({
38 name: `projects/${project}/locations/us-central1/namespaces/alchemy-endpoint-missing/services/api/endpoints/missing`,
39 }),
40 );
41 expect(error._tag).toBe("NotFound");
42 }).pipe(logLevel),
43 { tags: ["provider:gcp", "provider:gcp:servicedirectory", "live"] },
44);
45
46test.provider(
47 "create, update, replace, and delete an endpoint",
48 (stack) =>
49 Effect.gen(function* () {
50 const { project } = yield* GcpEnvironment.current;
51
52 yield* stack.destroy();
53
54 const created = yield* stack.deploy(
55 Effect.gen(function* () {
56 const ns = yield* GCP.ServiceDirectory.Namespace("Services", {
57 location: "us-central1",
58 labels: { env: "test" },
59 });
60 const api = yield* GCP.ServiceDirectory.Service("Api", {
61 namespace: ns.name,
62 });
63 const endpoint = yield* GCP.ServiceDirectory.Endpoint("Https", {
64 service: api.name,
65 address: "10.0.0.1",
66 port: 8080,
67 annotations: { protocol: "http" },
68 });
69 return { ns, api, endpoint };
70 }),
71 );
72
73 expect(created.endpoint.name).toContain("/endpoints/");
74 expect(created.endpoint.endpointId).toEqual(expect.any(String));
75 expect(created.endpoint.service).toEqual(created.api.name);
76 expect(created.endpoint.namespace).toEqual(created.ns.name);
77 expect(created.endpoint.location).toEqual("us-central1");
78 expect(created.endpoint.project).toEqual(project);
79 expect(created.endpoint.address).toEqual("10.0.0.1");
80 expect(created.endpoint.port).toEqual(8080);
81 expect(created.endpoint.annotations).toMatchObject({ protocol: "http" });
82 expect(created.endpoint.uid).toEqual(expect.any(String));
83
84 const fetched =
85 yield* servicedirectory.getProjectsLocationsNamespacesServicesEndpoints(
86 {
87 name: created.endpoint.name,
88 },
89 );
90 expect(fetched.name).toEqual(created.endpoint.name);
91 expect(fetched.address).toEqual("10.0.0.1");
92 expect(fetched.port).toEqual(8080);
93 expect(fetched.annotations?.protocol).toEqual("http");
94 expect(fetched.annotations?.["alchemy-id"]).toEqual(expect.any(String));
95 expect(fetched.uid).toEqual(created.endpoint.uid);
96
97 const updated = yield* stack.deploy(
98 Effect.gen(function* () {
99 const ns = yield* GCP.ServiceDirectory.Namespace("Services", {
100 namespaceId: created.ns.namespaceId,
101 location: "us-central1",
102 labels: { env: "test" },
103 });
104 const api = yield* GCP.ServiceDirectory.Service("Api", {
105 namespace: ns.name,
106 serviceId: created.api.serviceId,
107 });
108 const endpoint = yield* GCP.ServiceDirectory.Endpoint("Https", {
109 service: api.name,
110 endpointId: created.endpoint.endpointId,
111 address: "10.0.0.2",
112 port: 443,
113 annotations: { protocol: "https", role: "api" },
114 });
115 return { ns, api, endpoint };
116 }),
117 );
118
119 expect(updated.endpoint.name).toEqual(created.endpoint.name);
120 expect(updated.endpoint.uid).toEqual(created.endpoint.uid);
121 expect(updated.endpoint.address).toEqual("10.0.0.2");
122 expect(updated.endpoint.port).toEqual(443);
123 expect(updated.endpoint.annotations).toMatchObject({
124 protocol: "https",
125 role: "api",
126 });
127
128 const refetched =
129 yield* servicedirectory.getProjectsLocationsNamespacesServicesEndpoints(
130 {
131 name: created.endpoint.name,
132 },
133 );
134 expect(refetched.address).toEqual("10.0.0.2");
135 expect(refetched.port).toEqual(443);
136 expect(refetched.annotations?.protocol).toEqual("https");
137 expect(refetched.annotations?.role).toEqual("api");
138
139 const last = created.endpoint.endpointId.at(-1) ?? "a";
140 const nextEndpointId = `${created.endpoint.endpointId.slice(0, -1)}${last === "z" ? "0" : "z"}`;
141
142 const replaced = yield* stack.deploy(
143 Effect.gen(function* () {
144 const ns = yield* GCP.ServiceDirectory.Namespace("Services", {
145 namespaceId: created.ns.namespaceId,
146 location: "us-central1",
147 labels: { env: "test" },
148 });
149 const api = yield* GCP.ServiceDirectory.Service("Api", {
150 namespace: ns.name,
151 serviceId: created.api.serviceId,
152 });
153 const endpoint = yield* GCP.ServiceDirectory.Endpoint("Https", {
154 service: api.name,
155 endpointId: nextEndpointId,
156 address: "10.0.0.3",
157 port: 8443,
158 annotations: { protocol: "https" },
159 });
160 return { ns, api, endpoint };
161 }),
162 );
163
164 expect(replaced.endpoint.name).not.toEqual(created.endpoint.name);
165 expect(replaced.endpoint.endpointId).toEqual(nextEndpointId);
166 expect(replaced.endpoint.address).toEqual("10.0.0.3");
167 expect(replaced.endpoint.port).toEqual(8443);
168
169 const fetchedReplacement =
170 yield* servicedirectory.getProjectsLocationsNamespacesServicesEndpoints(
171 {
172 name: replaced.endpoint.name,
173 },
174 );
175 expect(fetchedReplacement.name).toEqual(replaced.endpoint.name);
176 expect(fetchedReplacement.address).toEqual("10.0.0.3");
177
178 const previousGone = yield* waitUntilGone(created.endpoint.name);
179 expect(previousGone).toEqual("gone");
180
181 yield* stack.destroy();
182
183 const gone = yield* waitUntilGone(replaced.endpoint.name);
184 expect(gone).toEqual("gone");
185 }).pipe(logLevel),
186 {
187 tags: ["provider:gcp", "provider:gcp:servicedirectory", "live"],
188 timeout: 90_000,
189 },
190);