1import * as Provider from "@/Provider";
2import * as Stripe from "@/Stripe";
3import * as Test from "@/Test/Alchemy";
4import { GetBillingAlert } from "@distilled.cloud/stripe/stripe";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9import { isMissingStripeResource } from "@/Stripe/missing.ts";
10
11const { test } = Test.make({ providers: Stripe.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18const isMissing = isMissingStripeResource;
19
20const waitUntilArchived = (id: string) =>
21 GetBillingAlert({ id }).pipe(
22 Effect.map((alert) =>
23 alert.status === "archived"
24 ? ("archived" as const)
25 : ("present" as const),
26 ),
27 Effect.catchIf(isMissing, () => Effect.succeed("archived" as const)),
28 Effect.repeat({
29 schedule: Schedule.spaced("1 second"),
30 until: (status) => status === "archived",
31 times: 10,
32 }),
33 );
34
35test.provider(
36 "create, deactivate, and archive a billing alert",
37 (stack) =>
38 Effect.gen(function* () {
39 yield* stack.destroy();
40
41 const created = yield* stack.deploy(
42 Effect.gen(function* () {
43 const meter = yield* Stripe.BillingMeter("AlertMeter", {
44 displayName: "Alchemy Alert Meter",
45 eventName: "alchemy_alrt_lifecycle",
46 defaultAggregation: { formula: "sum" },
47 });
48 const alert = yield* Stripe.Alert("UsageAlert", {
49 title: "Alchemy Usage Alert",
50 usageThreshold: {
51 gte: 100,
52 meter: meter.id,
53 recurrence: "one_time",
54 },
55 });
56 return { meter, alert };
57 }),
58 );
59
60 expect(created.alert.id).toMatch(/^alrt_/);
61 expect(created.alert.title).toEqual("Alchemy Usage Alert");
62 expect(created.alert.alertType).toEqual("usage_threshold");
63 expect(created.alert.status).toEqual("active");
64 expect(created.alert.usageThreshold).toEqual({
65 gte: 100,
66 meter: created.meter.id,
67 recurrence: "one_time",
68 });
69 expect(created.alert.livemode).toEqual(false);
70
71 const fetched = yield* GetBillingAlert({ id: created.alert.id });
72 expect(fetched.id).toEqual(created.alert.id);
73 expect(fetched.title).toEqual("Alchemy Usage Alert");
74 expect(fetched.alert_type).toEqual("usage_threshold");
75 expect(fetched.status).toEqual("active");
76 expect(fetched.usage_threshold?.gte).toEqual(100);
77 expect(
78 typeof fetched.usage_threshold?.meter === "string"
79 ? fetched.usage_threshold.meter
80 : fetched.usage_threshold?.meter.id,
81 ).toEqual(created.meter.id);
82 expect(fetched.usage_threshold?.recurrence).toEqual("one_time");
83
84 const updated = yield* stack.deploy(
85 Effect.gen(function* () {
86 const meter = yield* Stripe.BillingMeter("AlertMeter", {
87 displayName: "Alchemy Alert Meter",
88 eventName: "alchemy_alrt_lifecycle",
89 defaultAggregation: { formula: "sum" },
90 });
91 const alert = yield* Stripe.Alert("UsageAlert", {
92 title: "Alchemy Usage Alert",
93 usageThreshold: {
94 gte: 100,
95 meter: meter.id,
96 recurrence: "one_time",
97 },
98 status: "inactive",
99 });
100 return { meter, alert };
101 }),
102 );
103
104 expect(updated.alert.id).toEqual(created.alert.id);
105 expect(updated.alert.title).toEqual("Alchemy Usage Alert");
106 expect(updated.alert.status).toEqual("inactive");
107 expect(updated.alert.usageThreshold?.gte).toEqual(100);
108
109 const refetched = yield* GetBillingAlert({ id: updated.alert.id });
110 expect(refetched.id).toEqual(updated.alert.id);
111 expect(refetched.title).toEqual("Alchemy Usage Alert");
112 expect(refetched.status).toEqual("inactive");
113
114 yield* stack.destroy();
115
116 const archived = yield* waitUntilArchived(created.alert.id);
117 expect(archived).toEqual("archived");
118 }).pipe(logLevel),
119 {
120 tags: [
121 "provider:stripe",
122 "provider:stripe:alert",
123 "provider:stripe:billingmeter",
124 "live",
125 ],
126 timeout: 120_000,
127 },
128);
129
130test.provider(
131 "replace when usage threshold changes",
132 (stack) =>
133 Effect.gen(function* () {
134 yield* stack.destroy();
135
136 const created = yield* stack.deploy(
137 Effect.gen(function* () {
138 const meter = yield* Stripe.BillingMeter("ReplaceAlertMeter", {
139 displayName: "Alchemy Replace Alert Meter",
140 eventName: "alchemy_alrt_replace",
141 defaultAggregation: { formula: "sum" },
142 });
143 const alert = yield* Stripe.Alert("ReplaceAlert", {
144 title: "Alchemy Replace Alert",
145 usageThreshold: {
146 gte: 50,
147 meter: meter.id,
148 recurrence: "one_time",
149 },
150 });
151 return { meter, alert };
152 }),
153 );
154
155 expect(created.alert.usageThreshold?.gte).toEqual(50);
156 expect(created.alert.status).toEqual("active");
157
158 const replaced = yield* stack.deploy(
159 Effect.gen(function* () {
160 const meter = yield* Stripe.BillingMeter("ReplaceAlertMeter", {
161 displayName: "Alchemy Replace Alert Meter",
162 eventName: "alchemy_alrt_replace",
163 defaultAggregation: { formula: "sum" },
164 });
165 const alert = yield* Stripe.Alert("ReplaceAlert", {
166 title: "Alchemy Replace Alert",
167 usageThreshold: {
168 gte: 500,
169 meter: meter.id,
170 recurrence: "one_time",
171 },
172 });
173 return { meter, alert };
174 }),
175 );
176
177 expect(replaced.alert.id).not.toEqual(created.alert.id);
178 expect(replaced.alert.usageThreshold?.gte).toEqual(500);
179 expect(replaced.alert.status).toEqual("active");
180
181 const fetched = yield* GetBillingAlert({ id: replaced.alert.id });
182 expect(fetched.id).toEqual(replaced.alert.id);
183 expect(fetched.usage_threshold?.gte).toEqual(500);
184 expect(fetched.status).toEqual("active");
185
186 const oldArchived = yield* waitUntilArchived(created.alert.id);
187 expect(oldArchived).toEqual("archived");
188
189 yield* stack.destroy();
190
191 const gone = yield* waitUntilArchived(replaced.alert.id);
192 expect(gone).toEqual("archived");
193 }).pipe(logLevel),
194 {
195 tags: [
196 "provider:stripe",
197 "provider:stripe:alert",
198 "provider:stripe:billingmeter",
199 "live",
200 ],
201 timeout: 120_000,
202 },
203);
204
205test.provider(
206 "list enumerates the deployed billing alert",
207 (stack) =>
208 Effect.gen(function* () {
209 yield* stack.destroy();
210
211 const deployed = yield* stack.deploy(
212 Effect.gen(function* () {
213 const meter = yield* Stripe.BillingMeter("ListAlertMeter", {
214 displayName: "Alchemy List Alert Meter",
215 eventName: "alchemy_alrt_list",
216 defaultAggregation: { formula: "count" },
217 });
218 const alert = yield* Stripe.Alert("ListAlert", {
219 title: "Alchemy List Alert",
220 usageThreshold: {
221 gte: 10,
222 meter: meter.id,
223 recurrence: "one_time",
224 },
225 });
226 return { meter, alert };
227 }),
228 );
229
230 const provider = yield* Provider.findProvider(Stripe.Alert);
231 const all = yield* provider.list();
232 const found = all.find((alert) => alert.id === deployed.alert.id);
233 expect(found).toBeDefined();
234 expect(found?.title).toEqual(deployed.alert.title);
235 expect(found?.usageThreshold?.gte).toEqual(10);
236 expect(found?.usageThreshold?.meter).toEqual(deployed.meter.id);
237
238 yield* stack.destroy();
239
240 const archived = yield* waitUntilArchived(deployed.alert.id);
241 expect(archived).toEqual("archived");
242
243 const after = yield* provider.list();
244 expect(
245 after.find((alert) => alert.id === deployed.alert.id),
246 ).toBeUndefined();
247 }).pipe(logLevel),
248 {
249 tags: [
250 "provider:stripe",
251 "provider:stripe:alert",
252 "provider:stripe:billingmeter",
253 "live",
254 ],
255 timeout: 120_000,
256 },
257);