1import * as Provider from "@/Provider";
2import * as Stripe from "@/Stripe";
3import * as Test from "@/Test/Alchemy";
4import { GetBillingMeter } 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 waitUntilInactive = (id: string) =>
21 GetBillingMeter({ id }).pipe(
22 Effect.map((meter) =>
23 meter.status === "inactive" ? ("inactive" as const) : ("active" as const),
24 ),
25 Effect.catchIf(isMissing, () => Effect.succeed("inactive" as const)),
26 Effect.repeat({
27 schedule: Schedule.spaced("1 second"),
28 until: (status) => status === "inactive",
29 times: 10,
30 }),
31 );
32
33test.provider(
34 "create, update, and deactivate a billing meter",
35 (stack) =>
36 Effect.gen(function* () {
37 yield* stack.destroy();
38
39 const created = yield* stack.deploy(
40 Effect.gen(function* () {
41 return yield* Stripe.BillingMeter("UsageMeter", {
42 displayName: "Alchemy API Calls",
43 eventName: "alchemy_bm_lifecycle",
44 defaultAggregation: { formula: "sum" },
45 valueSettings: { eventPayloadKey: "value" },
46 customerMapping: {
47 type: "by_id",
48 eventPayloadKey: "stripe_customer_id",
49 },
50 });
51 }),
52 );
53
54 expect(created.id).toMatch(/^mtr_/);
55 expect(created.displayName).toEqual("Alchemy API Calls");
56 expect(created.eventName).toEqual("alchemy_bm_lifecycle");
57 expect(created.defaultAggregation).toEqual({ formula: "sum" });
58 expect(created.valueSettings).toEqual({ eventPayloadKey: "value" });
59 expect(created.customerMapping).toEqual({
60 type: "by_id",
61 eventPayloadKey: "stripe_customer_id",
62 });
63 expect(created.status).toEqual("active");
64 expect(created.created).toEqual(expect.any(Number));
65 expect(created.updated).toEqual(expect.any(Number));
66 expect(created.livemode).toEqual(false);
67
68 const fetched = yield* GetBillingMeter({ id: created.id });
69 expect(fetched.id).toEqual(created.id);
70 expect(fetched.display_name).toEqual("Alchemy API Calls");
71 expect(fetched.event_name).toEqual("alchemy_bm_lifecycle");
72 expect(fetched.default_aggregation.formula).toEqual("sum");
73 expect(fetched.value_settings.event_payload_key).toEqual("value");
74 expect(fetched.customer_mapping.type).toEqual("by_id");
75 expect(fetched.customer_mapping.event_payload_key).toEqual(
76 "stripe_customer_id",
77 );
78 expect(fetched.status).toEqual("active");
79
80 const updated = yield* stack.deploy(
81 Effect.gen(function* () {
82 return yield* Stripe.BillingMeter("UsageMeter", {
83 displayName: "Alchemy API Calls Updated",
84 eventName: "alchemy_bm_lifecycle",
85 defaultAggregation: { formula: "sum" },
86 valueSettings: { eventPayloadKey: "value" },
87 customerMapping: {
88 type: "by_id",
89 eventPayloadKey: "stripe_customer_id",
90 },
91 });
92 }),
93 );
94
95 expect(updated.id).toEqual(created.id);
96 expect(updated.displayName).toEqual("Alchemy API Calls Updated");
97 expect(updated.eventName).toEqual("alchemy_bm_lifecycle");
98 expect(updated.status).toEqual("active");
99
100 const refetched = yield* GetBillingMeter({ id: updated.id });
101 expect(refetched.id).toEqual(updated.id);
102 expect(refetched.display_name).toEqual("Alchemy API Calls Updated");
103 expect(refetched.event_name).toEqual("alchemy_bm_lifecycle");
104 expect(refetched.status).toEqual("active");
105
106 yield* stack.destroy();
107
108 const inactive = yield* waitUntilInactive(created.id);
109 expect(inactive).toEqual("inactive");
110 const deactivated = yield* GetBillingMeter({ id: created.id });
111 expect(deactivated.status).toEqual("inactive");
112 }).pipe(logLevel),
113 {
114 tags: ["provider:stripe", "provider:stripe:billingmeter", "live"],
115 timeout: 120_000,
116 },
117);
118
119test.provider(
120 "replace when event name changes",
121 (stack) =>
122 Effect.gen(function* () {
123 yield* stack.destroy();
124
125 const created = yield* stack.deploy(
126 Effect.gen(function* () {
127 return yield* Stripe.BillingMeter("ReplaceMeter", {
128 displayName: "Alchemy Replace Meter",
129 eventName: "alchemy_bm_replace_a",
130 defaultAggregation: { formula: "sum" },
131 });
132 }),
133 );
134
135 expect(created.eventName).toEqual("alchemy_bm_replace_a");
136 expect(created.status).toEqual("active");
137
138 const replaced = yield* stack.deploy(
139 Effect.gen(function* () {
140 return yield* Stripe.BillingMeter("ReplaceMeter", {
141 displayName: "Alchemy Replace Meter",
142 eventName: "alchemy_bm_replace_b",
143 defaultAggregation: { formula: "sum" },
144 });
145 }),
146 );
147
148 expect(replaced.id).not.toEqual(created.id);
149 expect(replaced.eventName).toEqual("alchemy_bm_replace_b");
150 expect(replaced.status).toEqual("active");
151
152 const fetched = yield* GetBillingMeter({ id: replaced.id });
153 expect(fetched.id).toEqual(replaced.id);
154 expect(fetched.event_name).toEqual("alchemy_bm_replace_b");
155 expect(fetched.status).toEqual("active");
156
157 const oldInactive = yield* waitUntilInactive(created.id);
158 expect(oldInactive).toEqual("inactive");
159
160 yield* stack.destroy();
161
162 const gone = yield* waitUntilInactive(replaced.id);
163 expect(gone).toEqual("inactive");
164 }).pipe(logLevel),
165 {
166 tags: ["provider:stripe", "provider:stripe:billingmeter", "live"],
167 timeout: 120_000,
168 },
169);
170
171test.provider(
172 "list enumerates the deployed billing meter",
173 (stack) =>
174 Effect.gen(function* () {
175 yield* stack.destroy();
176
177 const deployed = yield* stack.deploy(
178 Effect.gen(function* () {
179 return yield* Stripe.BillingMeter("ListMeter", {
180 displayName: "Alchemy List Meter",
181 eventName: "alchemy_bm_list",
182 defaultAggregation: { formula: "count" },
183 });
184 }),
185 );
186
187 const provider = yield* Provider.findProvider(Stripe.BillingMeter);
188 const all = yield* provider.list();
189 const found = all.find((meter) => meter.id === deployed.id);
190 expect(found).toBeDefined();
191 expect(found?.displayName).toEqual(deployed.displayName);
192 expect(found?.eventName).toEqual("alchemy_bm_list");
193 expect(found?.defaultAggregation).toEqual({ formula: "count" });
194
195 yield* stack.destroy();
196
197 const inactive = yield* waitUntilInactive(deployed.id);
198 expect(inactive).toEqual("inactive");
199
200 const after = yield* provider.list();
201 expect(after.find((meter) => meter.id === deployed.id)).toBeUndefined();
202 }).pipe(logLevel),
203 {
204 tags: ["provider:stripe", "provider:stripe:billingmeter", "live"],
205 timeout: 120_000,
206 },
207);