1import * as Provider from "@/Provider";
2import * as Stripe from "@/Stripe";
3import * as Test from "@/Test/Alchemy";
4import { GetEntitlementsFeature } 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 GetEntitlementsFeature({ id }).pipe(
22 Effect.map((feature) =>
23 feature.active ? ("active" as const) : ("inactive" 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 an entitlements feature",
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.EntitlementsFeature("Seats", {
42 lookupKey: "alchemy-ent-feat-lifecycle",
43 name: "Alchemy Seats Feature",
44 metadata: { plan: "pro" },
45 });
46 }),
47 );
48
49 expect(created.id).toMatch(/^feat_/);
50 expect(created.lookupKey).toEqual("alchemy-ent-feat-lifecycle");
51 expect(created.name).toEqual("Alchemy Seats Feature");
52 expect(created.active).toEqual(true);
53 expect(created.metadata).toMatchObject({ plan: "pro" });
54 expect(created.livemode).toEqual(false);
55
56 const fetched = yield* GetEntitlementsFeature({ id: created.id });
57 expect(fetched.id).toEqual(created.id);
58 expect(fetched.lookup_key).toEqual("alchemy-ent-feat-lifecycle");
59 expect(fetched.name).toEqual("Alchemy Seats Feature");
60 expect(fetched.active).toEqual(true);
61 expect(fetched.metadata?.plan).toEqual("pro");
62 expect(
63 fetched.metadata?.[Stripe.alchemyMetadataKeys.stack],
64 ).toBeDefined();
65 expect(
66 fetched.metadata?.[Stripe.alchemyMetadataKeys.stage],
67 ).toBeDefined();
68 expect(fetched.metadata?.[Stripe.alchemyMetadataKeys.id]).toBeDefined();
69
70 const updated = yield* stack.deploy(
71 Effect.gen(function* () {
72 return yield* Stripe.EntitlementsFeature("Seats", {
73 lookupKey: "alchemy-ent-feat-lifecycle",
74 name: "Alchemy Seats Feature Updated",
75 metadata: { plan: "enterprise", sku: "ent-1" },
76 });
77 }),
78 );
79
80 expect(updated.id).toEqual(created.id);
81 expect(updated.lookupKey).toEqual(created.lookupKey);
82 expect(updated.name).toEqual("Alchemy Seats Feature Updated");
83 expect(updated.active).toEqual(true);
84 expect(updated.metadata).toEqual({ plan: "enterprise", sku: "ent-1" });
85
86 const refetched = yield* GetEntitlementsFeature({ id: updated.id });
87 expect(refetched.id).toEqual(updated.id);
88 expect(refetched.name).toEqual("Alchemy Seats Feature Updated");
89 expect(refetched.lookup_key).toEqual("alchemy-ent-feat-lifecycle");
90 expect(refetched.metadata?.plan).toEqual("enterprise");
91 expect(refetched.metadata?.sku).toEqual("ent-1");
92 expect(refetched.metadata?.[Stripe.alchemyMetadataKeys.id]).toBeDefined();
93
94 yield* stack.destroy();
95
96 const inactive = yield* waitUntilInactive(created.id);
97 expect(inactive).toEqual("inactive");
98 const deactivated = yield* GetEntitlementsFeature({ id: created.id });
99 expect(deactivated.active).toEqual(false);
100 }).pipe(logLevel),
101 {
102 tags: ["provider:stripe", "provider:stripe:entitlementsfeature", "live"],
103 timeout: 120_000,
104 },
105);
106
107test.provider(
108 "list enumerates the deployed entitlements feature",
109 (stack) =>
110 Effect.gen(function* () {
111 yield* stack.destroy();
112
113 const deployed = yield* stack.deploy(
114 Effect.gen(function* () {
115 return yield* Stripe.EntitlementsFeature("ListFeature", {
116 lookupKey: "alchemy-ent-feat-list",
117 name: "Alchemy List Feature",
118 metadata: { kind: "list" },
119 });
120 }),
121 );
122
123 const provider = yield* Provider.findProvider(Stripe.EntitlementsFeature);
124 const all = yield* provider.list();
125 const found = all.find((feature) => feature.id === deployed.id);
126 expect(found).toBeDefined();
127 expect(found?.name).toEqual(deployed.name);
128 expect(found?.lookupKey).toEqual("alchemy-ent-feat-list");
129 expect(found?.metadata).toMatchObject({ kind: "list" });
130
131 yield* stack.destroy();
132
133 const inactive = yield* waitUntilInactive(deployed.id);
134 expect(inactive).toEqual("inactive");
135
136 const after = yield* provider.list();
137 expect(
138 after.find((feature) => feature.id === deployed.id),
139 ).toBeUndefined();
140 }).pipe(logLevel),
141 {
142 tags: ["provider:stripe", "provider:stripe:entitlementsfeature", "live"],
143 timeout: 120_000,
144 },
145);
146
147test.provider(
148 "replace when lookup key changes",
149 (stack) =>
150 Effect.gen(function* () {
151 yield* stack.destroy();
152
153 const created = yield* stack.deploy(
154 Effect.gen(function* () {
155 return yield* Stripe.EntitlementsFeature("ReplaceFeature", {
156 lookupKey: "alchemy-ent-feat-replace-a",
157 name: "Alchemy Replace Feature",
158 });
159 }),
160 );
161
162 expect(created.lookupKey).toEqual("alchemy-ent-feat-replace-a");
163
164 const replaced = yield* stack.deploy(
165 Effect.gen(function* () {
166 return yield* Stripe.EntitlementsFeature("ReplaceFeature", {
167 lookupKey: "alchemy-ent-feat-replace-b",
168 name: "Alchemy Replace Feature",
169 });
170 }),
171 );
172
173 expect(replaced.id).not.toEqual(created.id);
174 expect(replaced.lookupKey).toEqual("alchemy-ent-feat-replace-b");
175 expect(replaced.name).toEqual("Alchemy Replace Feature");
176
177 const fetched = yield* GetEntitlementsFeature({ id: replaced.id });
178 expect(fetched.id).toEqual(replaced.id);
179 expect(fetched.lookup_key).toEqual("alchemy-ent-feat-replace-b");
180
181 const oldInactive = yield* waitUntilInactive(created.id);
182 expect(oldInactive).toEqual("inactive");
183
184 yield* stack.destroy();
185
186 const gone = yield* waitUntilInactive(replaced.id);
187 expect(gone).toEqual("inactive");
188 }).pipe(logLevel),
189 {
190 tags: ["provider:stripe", "provider:stripe:entitlementsfeature", "live"],
191 timeout: 120_000,
192 },
193);