1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as logging from "@distilled.cloud/gcp/logging_v2";
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
12// Organization-scoped: set GOOGLE_ORGANIZATION_ID when the credentials
13// administer the organization (the testing service account does not).
14const organizationId = process.env.GOOGLE_ORGANIZATION_ID?.trim().replace(
15 /^organizations\//,
16 "",
17);
18const organization = `organizations/${organizationId}`;
19
20const logLevel = Effect.provideService(
21 MinimumLogLevel,
22 process.env.DEBUG ? "Debug" : "Info",
23);
24const projectNameOf = (project: string) => `projects/${project}`;
25
26const waitUntilGone = (name: string) =>
27 logging.getOrganizationsLocationsLogScopes({ name }).pipe(
28 Effect.as("found" as const),
29 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
30 Effect.repeat({
31 schedule: Schedule.spaced("1 second"),
32 until: (status) => status === "gone",
33 times: 10,
34 }),
35 );
36
37test.provider.skipIf(!organizationId)(
38 "getOrganizationsLocationsLogScopes on a missing log scope fails with NotFound",
39 (stack) =>
40 Effect.gen(function* () {
41 yield* stack.destroy();
42
43 const error = yield* Effect.flip(
44 logging.getOrganizationsLocationsLogScopes({
45 name: `${organization}/locations/global/logScopes/alchemy-missing`,
46 }),
47 );
48 expect(error._tag).toEqual("NotFound");
49
50 yield* stack.destroy();
51 }).pipe(logLevel),
52 { tags: ["provider:gcp", "provider:gcp:logging", "live"], timeout: 90_000 },
53);
54
55test.provider.skipIf(!organizationId)(
56 "create, update, replace, and delete an organization logging log scope",
57 (stack) =>
58 Effect.gen(function* () {
59 const { project } = yield* GcpEnvironment.current;
60
61 yield* stack.destroy();
62
63 const created = yield* stack.deploy(
64 Effect.gen(function* () {
65 return yield* GCP.Logging.OrganizationLogScope("App", {
66 organization,
67 resourceNames: [projectNameOf(project)],
68 description: "application logs",
69 });
70 }),
71 );
72
73 expect(created.logScopeId).toEqual(expect.any(String));
74 expect(created.location).toEqual("global");
75 expect(created.organization).toEqual(organization);
76 expect(created.name).toEqual(
77 `${organization}/locations/global/logScopes/${created.logScopeId}`,
78 );
79 expect(created.resourceNames).toEqual([projectNameOf(project)]);
80 expect(created.description).toEqual("application logs");
81
82 const fetched = yield* logging.getOrganizationsLocationsLogScopes({
83 name: created.name,
84 });
85 expect(fetched.resourceNames).toEqual([projectNameOf(project)]);
86 expect(fetched.description).toContain("alchemy-id=");
87
88 const updated = yield* stack.deploy(
89 Effect.gen(function* () {
90 return yield* GCP.Logging.OrganizationLogScope("App", {
91 organization,
92 logScopeId: created.logScopeId,
93 resourceNames: [projectNameOf(project)],
94 description: "updated application logs",
95 });
96 }),
97 );
98
99 expect(updated.name).toEqual(created.name);
100 expect(updated.description).toEqual("updated application logs");
101
102 const last = created.logScopeId.at(-1) ?? "a";
103 const nextId = `${created.logScopeId.slice(0, -1)}${last === "z" ? "0" : "z"}`;
104
105 const replaced = yield* stack.deploy(
106 Effect.gen(function* () {
107 return yield* GCP.Logging.OrganizationLogScope("App", {
108 organization,
109 logScopeId: nextId,
110 resourceNames: [projectNameOf(project)],
111 description: "replaced scope",
112 });
113 }),
114 );
115
116 expect(replaced.logScopeId).not.toEqual(created.logScopeId);
117
118 const previousGone = yield* waitUntilGone(created.name);
119 expect(previousGone).toEqual("gone");
120
121 yield* stack.destroy();
122
123 const gone = yield* waitUntilGone(replaced.name);
124 expect(gone).toEqual("gone");
125 }).pipe(logLevel),
126 { tags: ["provider:gcp", "provider:gcp:logging", "live"], timeout: 90_000 },
127);