1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as firebaseapphosting from "@distilled.cloud/gcp/firebaseapphosting_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import {
8 location,
9 logLevel,
10 missingBackendOf,
11 currentProject,
12 serviceAccountOf,
13} from "./common.ts";
14
15const { test } = Test.make({ providers: GCP.providers() });
16
17const helloImage = "us-docker.pkg.dev/cloudrun/container/hello";
18
19const waitUntilGone = (name: string) =>
20 firebaseapphosting.getProjectsLocationsBackendsBuilds({ name }).pipe(
21 Effect.as("found" as const),
22 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
23 Effect.repeat({
24 schedule: Schedule.spaced("3 seconds"),
25 until: (status) => status === "gone",
26 times: 10,
27 }),
28 );
29
30test.provider(
31 "getProjectsLocationsBackendsBuilds on a missing build fails with a typed tag",
32 (stack) =>
33 Effect.gen(function* () {
34 const project = yield* currentProject;
35 const missingBackend = missingBackendOf(project);
36
37 yield* stack.destroy();
38
39 const error = yield* Effect.flip(
40 firebaseapphosting.getProjectsLocationsBackendsBuilds({
41 name: `${missingBackend()}/builds/alchemy-missing-build`,
42 }),
43 );
44 expect(error._tag).toEqual("NotFound");
45
46 const page =
47 yield* firebaseapphosting.listProjectsLocationsBackendsBuilds({
48 parent: `projects/${project}/locations/-/backends/-`,
49 pageSize: 10,
50 });
51 expect((page.builds ?? []).map((item) => item.name)).not.toContain(
52 `${missingBackend()}/builds/alchemy-missing-build`,
53 );
54
55 yield* stack.destroy();
56 }).pipe(logLevel),
57 {
58 tags: ["provider:gcp", "provider:gcp:firebaseapphosting", "live"],
59 timeout: 90_000,
60 },
61);
62
63test.provider(
64 "create against a missing backend is rejected with a typed tag",
65 (stack) =>
66 Effect.gen(function* () {
67 const project = yield* currentProject;
68 const missingBackend = missingBackendOf(project);
69
70 yield* stack.destroy();
71
72 const error = yield* Effect.flip(
73 stack.deploy(
74 Effect.gen(function* () {
75 return yield* GCP.FirebaseAppHosting.BackendsBuild("Hello", {
76 backend: missingBackend(),
77 source: { container: { image: helloImage } },
78 labels: { env: "test" },
79 });
80 }),
81 ),
82 );
83 expect(error._tag).toEqual("NotFound");
84
85 yield* stack.destroy();
86 }).pipe(logLevel),
87 {
88 tags: ["provider:gcp", "provider:gcp:firebaseapphosting", "live"],
89 timeout: 90_000,
90 },
91);
92
93// A build reaches READY in about a minute, but its delete LRO was still
94// `done: false` after 14 minutes, so the test cannot clean up. Set
95// GCP_TEST_FIREBASE_APP_HOSTING_BUILD=1 to run it anyway.
96test.provider.skipIf(!process.env.GCP_TEST_FIREBASE_APP_HOSTING_BUILD)(
97 "create, verify, and delete a backend build",
98 (stack) =>
99 Effect.gen(function* () {
100 const project = yield* currentProject;
101 const serviceAccount = serviceAccountOf(project);
102
103 yield* stack.destroy();
104
105 const created = yield* stack.deploy(
106 Effect.gen(function* () {
107 const backend = yield* GCP.FirebaseAppHosting.Backend("Web", {
108 serviceAccount,
109 servingLocality: "GLOBAL_ACCESS",
110 displayName: "alchemy-test-build-backend",
111 labels: { env: "test" },
112 });
113 const build = yield* GCP.FirebaseAppHosting.BackendsBuild("Hello", {
114 backend: backend.name,
115 source: { container: { image: helloImage } },
116 displayName: "alchemy-test-build",
117 labels: { env: "test" },
118 });
119 return { backend, build };
120 }),
121 );
122
123 expect(created.build.name).toContain("/builds/");
124 expect(created.build.backend).toEqual(created.backend.name);
125 expect(created.build.location).toEqual(location);
126 expect(created.build.source?.container?.image).toEqual(helloImage);
127 expect(created.build.labels).toMatchObject({ env: "test" });
128
129 const fetched =
130 yield* firebaseapphosting.getProjectsLocationsBackendsBuilds({
131 name: created.build.name,
132 });
133 expect(fetched.name).toEqual(created.build.name);
134 expect(fetched.source?.container?.image).toEqual(helloImage);
135 expect(fetched.labels?.env).toEqual("test");
136
137 yield* stack.destroy();
138
139 const gone = yield* waitUntilGone(created.build.name);
140 expect(gone).toEqual("gone");
141 }).pipe(logLevel),
142 {
143 tags: ["provider:gcp", "provider:gcp:firebaseapphosting", "live"],
144 timeout: 900_000,
145 },
146);