1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as monitoring from "@distilled.cloud/gcp/monitoring_v3";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const logLevel = Effect.provideService(
12 MinimumLogLevel,
13 process.env.DEBUG ? "Debug" : "Info",
14);
15
16const waitUntilGone = (name: string) =>
17 monitoring.getProjectsMetricDescriptors({ name }).pipe(
18 Effect.as("found" as const),
19 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
20 Effect.repeat({
21 schedule: Schedule.spaced("1 second"),
22 until: (status) => status === "gone",
23 times: 10,
24 }),
25 );
26
27test.provider(
28 "create, update, replace, and delete a metric descriptor",
29 (stack) =>
30 Effect.gen(function* () {
31 yield* stack.destroy();
32
33 const created = yield* stack.deploy(
34 Effect.gen(function* () {
35 return yield* GCP.Monitoring.MetricDescriptor("Paid", {
36 displayName: "Invoice paid amount",
37 description: "amount collected per invoice",
38 labels: [{ key: "currency", valueType: "STRING" }],
39 });
40 }),
41 );
42
43 expect(created.name).toContain("/metricDescriptors/");
44 expect(created.type.startsWith("custom.googleapis.com/")).toEqual(true);
45 expect(created.metricKind).toEqual("GAUGE");
46 expect(created.valueType).toEqual("DOUBLE");
47 expect(created.unit).toEqual("1");
48 expect(created.displayName).toEqual("Invoice paid amount");
49 expect(created.description).toEqual("amount collected per invoice");
50 expect(created.labels.some((label) => label.key === "currency")).toEqual(
51 true,
52 );
53
54 const fetched = yield* monitoring
55 .getProjectsMetricDescriptors({
56 name: created.name,
57 })
58 .pipe(
59 Effect.retry({
60 times: 10,
61 schedule: Schedule.exponential("200 millis"),
62 while: (error) => error._tag === "NotFound",
63 }),
64 );
65 expect(fetched.type).toEqual(created.type);
66 expect(fetched.metricKind).toEqual("GAUGE");
67 expect(fetched.description).toContain("alchemy-id=");
68 expect(fetched.description).toContain("amount collected per invoice");
69
70 const updated = yield* stack.deploy(
71 Effect.gen(function* () {
72 return yield* GCP.Monitoring.MetricDescriptor("Paid", {
73 type: created.type,
74 displayName: "Invoice paid (cents)",
75 description: "amount collected per invoice in cents",
76 unit: "1",
77 labels: [
78 { key: "currency", valueType: "STRING" },
79 { key: "source", valueType: "STRING" },
80 ],
81 });
82 }),
83 );
84
85 expect(updated.name).toEqual(created.name);
86 expect(updated.type).toEqual(created.type);
87 expect(updated.displayName).toEqual("Invoice paid (cents)");
88 expect(updated.description).toEqual(
89 "amount collected per invoice in cents",
90 );
91 expect(updated.labels.map((label) => label.key).sort()).toEqual(
92 ["currency", "source"].sort(),
93 );
94
95 const fetchedUpdate = yield* monitoring
96 .getProjectsMetricDescriptors({
97 name: updated.name,
98 })
99 .pipe(
100 Effect.repeat({
101 schedule: Schedule.spaced("500 millis"),
102 until: (descriptor) =>
103 descriptor.displayName === "Invoice paid (cents)" &&
104 (descriptor.description ?? "").includes("in cents"),
105 times: 10,
106 }),
107 );
108 expect(fetchedUpdate.displayName).toEqual("Invoice paid (cents)");
109 expect(fetchedUpdate.description).toContain("in cents");
110
111 const replaced = yield* stack.deploy(
112 Effect.gen(function* () {
113 return yield* GCP.Monitoring.MetricDescriptor("Paid", {
114 metricKind: "CUMULATIVE",
115 valueType: "INT64",
116 displayName: "Invoice paid count",
117 description: "count of paid invoices",
118 });
119 }),
120 );
121
122 expect(replaced.metricKind).toEqual("CUMULATIVE");
123 expect(replaced.valueType).toEqual("INT64");
124 expect(replaced.description).toEqual("count of paid invoices");
125
126 yield* stack.destroy();
127
128 const gone = yield* waitUntilGone(replaced.name);
129 expect(gone).toEqual("gone");
130 }).pipe(logLevel),
131 {
132 tags: ["provider:gcp", "provider:gcp:monitoring", "live"],
133 timeout: 90_000,
134 },
135);