1import * as GCP from "@/GCP";
2import { GcpEnvironment } from "@/GCP/Environment";
3import * as Test from "@/Test/Alchemy";
4import * as firebaseappdistribution from "@distilled.cloud/gcp/firebaseappdistribution_v1";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const currentParent = GcpEnvironment.current.pipe(
18 Effect.map(({ project }) => `projects/${project}`),
19);
20// The Firebase App Distribution API is not enabled in the testing project.
21// Set GCP_TEST_FIREBASE_APP_DISTRIBUTION=1 on a project where it is.
22const entitled = !!process.env.GCP_TEST_FIREBASE_APP_DISTRIBUTION;
23
24const waitUntilGone = (name: string) =>
25 firebaseappdistribution.getProjectsGroups({ name }).pipe(
26 Effect.as("found" as const),
27 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
28 Effect.repeat({
29 schedule: Schedule.spaced("1 second"),
30 until: (status) => status === "gone",
31 times: 10,
32 }),
33 );
34
35test.provider.skipIf(!entitled)(
36 "getProjectsGroups on a missing group fails with a typed tag",
37 (stack) =>
38 Effect.gen(function* () {
39 const parent = yield* currentParent;
40 yield* stack.destroy();
41
42 const error = yield* Effect.flip(
43 firebaseappdistribution.getProjectsGroups({
44 name: `${parent}/groups/alchemy-missing`,
45 }),
46 );
47 expect(error._tag).toEqual("NotFound");
48
49 yield* stack.destroy();
50 }).pipe(logLevel),
51 {
52 tags: ["provider:gcp", "provider:gcp:firebaseappdistribution", "live"],
53 timeout: 90_000,
54 },
55);
56
57test.provider.skipIf(entitled)(
58 "getProjectsGroups fails with ServiceDisabled while the API is disabled",
59 (stack) =>
60 Effect.gen(function* () {
61 const parent = yield* currentParent;
62 yield* stack.destroy();
63
64 const error = yield* Effect.flip(
65 firebaseappdistribution.getProjectsGroups({
66 name: `${parent}/groups/alchemy-missing`,
67 }),
68 );
69 expect(error._tag).toEqual("ServiceDisabled");
70
71 yield* stack.destroy();
72 }).pipe(logLevel),
73 {
74 tags: ["provider:gcp", "provider:gcp:firebaseappdistribution", "live"],
75 timeout: 90_000,
76 },
77);
78
79test.provider.skipIf(!entitled)(
80 "create, update, and delete a tester group",
81 (stack) =>
82 Effect.gen(function* () {
83 yield* stack.destroy();
84
85 const created = yield* stack.deploy(
86 Effect.gen(function* () {
87 return yield* GCP.FirebaseAppDistribution.Group("Qa", {
88 displayName: "qa",
89 });
90 }),
91 );
92
93 expect(created.name).toContain("/groups/");
94 expect(created.groupId).toEqual(expect.any(String));
95 expect(created.groupId.length).toBeGreaterThanOrEqual(4);
96 expect(created.project).toEqual(expect.any(String));
97 expect(created.displayName).toEqual("qa");
98
99 const fetched = yield* firebaseappdistribution.getProjectsGroups({
100 name: created.name,
101 });
102 expect(fetched.name).toEqual(created.name);
103 // The display name is shown to testers: no ownership marker.
104 expect(fetched.displayName).toEqual("qa");
105
106 const updated = yield* stack.deploy(
107 Effect.gen(function* () {
108 return yield* GCP.FirebaseAppDistribution.Group("Qa", {
109 groupId: created.groupId,
110 displayName: "qa-prod",
111 });
112 }),
113 );
114
115 expect(updated.name).toEqual(created.name);
116 expect(updated.groupId).toEqual(created.groupId);
117 expect(updated.displayName).toEqual("qa-prod");
118
119 const refetched = yield* firebaseappdistribution.getProjectsGroups({
120 name: created.name,
121 });
122 expect(refetched.displayName).toEqual("qa-prod");
123
124 yield* stack.destroy();
125
126 const gone = yield* waitUntilGone(created.name);
127 expect(gone).toEqual("gone");
128 }).pipe(logLevel),
129 {
130 tags: ["provider:gcp", "provider:gcp:firebaseappdistribution", "live"],
131 timeout: 90_000,
132 },
133);