1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as appengine from "@distilled.cloud/gcp/appengine_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
17// Needs an App Engine application in the project, which is permanent once
18// created (it can never be deleted). The testing project has none and the
19// App Engine Admin API is off (ServiceDisabled: "App Engine Admin API has not been
20// used in project ... or it is disabled."). Set GCP_TEST_APPENGINE_APP=1 on a
21// project with an app to run the lifecycle.
22const runLifecycle = !!process.env.GCP_TEST_APPENGINE_APP;
23
24const location = "us-central";
25const domain =
26 process.env.GCP_TEST_APPENGINE_DOMAIN ?? "alchemy-appengine.test";
27
28const waitUntilGone = (
29 projectsId: string,
30 locationsId: string,
31 applicationsId: string,
32 domainId: string,
33) =>
34 appengine
35 .getProjectsLocationsApplicationsDomainMappings({
36 projectsId,
37 locationsId,
38 applicationsId,
39 domainMappingsId: domainId,
40 })
41 .pipe(
42 Effect.as("found" as const),
43 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
44 Effect.repeat({
45 schedule: Schedule.spaced("1 second"),
46 until: (status) => status === "gone",
47 times: 10,
48 }),
49 );
50
51test.provider(
52 "getProjectsLocationsApplicationsDomainMappings on a missing mapping fails with a typed tag",
53 (stack) =>
54 Effect.gen(function* () {
55 const { project } = yield* GcpEnvironment.current;
56 yield* stack.destroy();
57
58 const error = yield* Effect.flip(
59 appengine.getProjectsLocationsApplicationsDomainMappings({
60 projectsId: project,
61 locationsId: location,
62 applicationsId: project,
63 domainMappingsId: "alchemy-missing.example.com",
64 }),
65 );
66 expect(error._tag).toEqual("ServiceDisabled");
67
68 yield* stack.destroy();
69 }).pipe(logLevel),
70 { tags: ["provider:gcp", "provider:gcp:appengine", "live"], timeout: 90_000 },
71);
72
73test.provider.skipIf(runLifecycle)(
74 "createProjectsLocationsApplicationsDomainMappings without an App Engine app fails with a typed tag",
75 (stack) =>
76 Effect.gen(function* () {
77 const { project } = yield* GcpEnvironment.current;
78 yield* stack.destroy();
79
80 const error = yield* Effect.flip(
81 appengine.createProjectsLocationsApplicationsDomainMappings({
82 projectsId: project,
83 locationsId: location,
84 applicationsId: project,
85 body: {
86 id: "alchemy-missing.example.com",
87 sslSettings: { sslManagementType: "AUTOMATIC" },
88 },
89 }),
90 );
91 expect(error._tag).toEqual("ServiceDisabled");
92
93 yield* stack.destroy();
94 }).pipe(logLevel),
95 { tags: ["provider:gcp", "provider:gcp:appengine", "live"], timeout: 90_000 },
96);
97
98test.provider.skipIf(!runLifecycle)(
99 "create, update, and delete an applications domain mapping",
100 (stack) =>
101 Effect.gen(function* () {
102 const { project } = yield* GcpEnvironment.current;
103 yield* stack.destroy();
104
105 const created = yield* stack.deploy(
106 Effect.gen(function* () {
107 return yield* GCP.AppEngine.ApplicationsDomainMapping("Www", {
108 domain,
109 sslSettings: { sslManagementType: "AUTOMATIC" },
110 });
111 }),
112 );
113
114 expect(created.domain).toEqual(domain);
115 expect(created.applicationsId).toEqual(project);
116
117 const fetched =
118 yield* appengine.getProjectsLocationsApplicationsDomainMappings({
119 projectsId: created.project,
120 locationsId: created.location,
121 applicationsId: created.applicationsId,
122 domainMappingsId: created.domain,
123 });
124 expect(fetched.id).toEqual(created.domain);
125
126 const updated = yield* stack.deploy(
127 Effect.gen(function* () {
128 return yield* GCP.AppEngine.ApplicationsDomainMapping("Www", {
129 domain: created.domain,
130 location: created.location,
131 sslSettings: { sslManagementType: "MANUAL" },
132 });
133 }),
134 );
135
136 expect(updated.domain).toEqual(created.domain);
137 expect(updated.sslManagementType).toEqual("MANUAL");
138
139 yield* stack.destroy();
140
141 const gone = yield* waitUntilGone(
142 created.project,
143 created.location,
144 created.applicationsId,
145 created.domain,
146 );
147 expect(gone).toEqual("gone");
148 }).pipe(logLevel),
149 { tags: ["provider:gcp", "provider:gcp:appengine", "live"], timeout: 90_000 },
150);