1import * as Provider from "@/Provider";
2import * as Stripe from "@/Stripe";
3import * as Test from "@/Test/Alchemy";
4import { GetAppsSecretsFind } 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 ACCOUNT_SCOPE = { type: "account" as const };
19
20const LIFECYCLE_NAME = "alchemy-apps-secret-lifecycle";
21const LIST_NAME = "alchemy-apps-secret-list";
22const REPLACE_FROM_NAME = "alchemy-apps-secret-replace-a";
23const REPLACE_TO_NAME = "alchemy-apps-secret-replace-b";
24
25const LIFECYCLE_PAYLOAD = "alchemy-apps-secret-payload";
26const LIFECYCLE_PAYLOAD_ROTATED = "alchemy-apps-secret-payload-rotated";
27const LIST_PAYLOAD = "alchemy-apps-secret-list-payload";
28const REPLACE_PAYLOAD = "alchemy-apps-secret-replace-payload";
29
30const EXPIRES_AT = 2_000_000_000;
31const EXPIRES_AT_UPDATED = 2_100_000_000;
32
33const waitUntilGone = (name: string) =>
34 GetAppsSecretsFind({ name, scope: ACCOUNT_SCOPE }).pipe(
35 Effect.map((secret) =>
36 secret.deleted === true ? ("gone" as const) : ("found" as const),
37 ),
38 Effect.catchIf(isMissingStripeResource, () =>
39 Effect.succeed("gone" as const),
40 ),
41 Effect.repeat({
42 schedule: Schedule.spaced("1 second"),
43 until: (status) => status === "gone",
44 times: 10,
45 }),
46 );
47
48test.provider(
49 "create, update, and delete an apps secret",
50 (stack) =>
51 Effect.gen(function* () {
52 yield* stack.destroy();
53
54 const created = yield* stack.deploy(
55 Effect.gen(function* () {
56 return yield* Stripe.AppsSecret("LifecycleSecret", {
57 name: LIFECYCLE_NAME,
58 payload: LIFECYCLE_PAYLOAD,
59 scope: ACCOUNT_SCOPE,
60 expiresAt: EXPIRES_AT,
61 });
62 }),
63 );
64
65 expect(created.id).toMatch(/^(appsecret_|secret_)/);
66 expect(created.name).toEqual(LIFECYCLE_NAME);
67 expect(created.scope).toEqual(ACCOUNT_SCOPE);
68 expect(created.expiresAt).toEqual(EXPIRES_AT);
69 expect(created.livemode).toEqual(false);
70 expect(created.created).toEqual(expect.any(Number));
71
72 const fetched = yield* GetAppsSecretsFind({
73 name: LIFECYCLE_NAME,
74 scope: ACCOUNT_SCOPE,
75 expand: ["payload"],
76 });
77 expect(fetched.id).toEqual(created.id);
78 expect(fetched.name).toEqual(LIFECYCLE_NAME);
79 expect(fetched.scope.type).toEqual("account");
80 expect(fetched.expires_at).toEqual(EXPIRES_AT);
81 if (fetched.payload != null) {
82 expect(fetched.payload).toEqual(LIFECYCLE_PAYLOAD);
83 }
84
85 const updated = yield* stack.deploy(
86 Effect.gen(function* () {
87 return yield* Stripe.AppsSecret("LifecycleSecret", {
88 name: LIFECYCLE_NAME,
89 payload: LIFECYCLE_PAYLOAD_ROTATED,
90 scope: ACCOUNT_SCOPE,
91 expiresAt: EXPIRES_AT_UPDATED,
92 });
93 }),
94 );
95
96 expect(updated.name).toEqual(LIFECYCLE_NAME);
97 expect(updated.scope).toEqual(ACCOUNT_SCOPE);
98 expect(updated.expiresAt).toEqual(EXPIRES_AT_UPDATED);
99
100 const refetched = yield* GetAppsSecretsFind({
101 name: LIFECYCLE_NAME,
102 scope: ACCOUNT_SCOPE,
103 expand: ["payload"],
104 });
105 expect(refetched.name).toEqual(LIFECYCLE_NAME);
106 expect(refetched.expires_at).toEqual(EXPIRES_AT_UPDATED);
107 if (refetched.payload != null) {
108 expect(refetched.payload).toEqual(LIFECYCLE_PAYLOAD_ROTATED);
109 }
110
111 yield* stack.destroy();
112
113 const gone = yield* waitUntilGone(LIFECYCLE_NAME);
114 expect(gone).toEqual("gone");
115 }).pipe(logLevel),
116 {
117 tags: ["provider:stripe", "provider:stripe:appssecret", "live"],
118 timeout: 120_000,
119 },
120);
121
122test.provider(
123 "replace when the name changes",
124 (stack) =>
125 Effect.gen(function* () {
126 yield* stack.destroy();
127
128 const created = yield* stack.deploy(
129 Effect.gen(function* () {
130 return yield* Stripe.AppsSecret("ReplaceSecret", {
131 name: REPLACE_FROM_NAME,
132 payload: REPLACE_PAYLOAD,
133 scope: ACCOUNT_SCOPE,
134 });
135 }),
136 );
137
138 expect(created.name).toEqual(REPLACE_FROM_NAME);
139
140 const replaced = yield* stack.deploy(
141 Effect.gen(function* () {
142 return yield* Stripe.AppsSecret("ReplaceSecret", {
143 name: REPLACE_TO_NAME,
144 payload: REPLACE_PAYLOAD,
145 scope: ACCOUNT_SCOPE,
146 });
147 }),
148 );
149
150 expect(replaced.name).toEqual(REPLACE_TO_NAME);
151 expect(replaced.id).not.toEqual(created.id);
152
153 const fetched = yield* GetAppsSecretsFind({
154 name: REPLACE_TO_NAME,
155 scope: ACCOUNT_SCOPE,
156 });
157 expect(fetched.id).toEqual(replaced.id);
158 expect(fetched.name).toEqual(REPLACE_TO_NAME);
159
160 const oldGone = yield* waitUntilGone(REPLACE_FROM_NAME);
161 expect(oldGone).toEqual("gone");
162
163 yield* stack.destroy();
164
165 const gone = yield* waitUntilGone(REPLACE_TO_NAME);
166 expect(gone).toEqual("gone");
167 }).pipe(logLevel),
168 {
169 tags: ["provider:stripe", "provider:stripe:appssecret", "live"],
170 timeout: 120_000,
171 },
172);
173
174test.provider(
175 "list enumerates the deployed apps secret",
176 (stack) =>
177 Effect.gen(function* () {
178 yield* stack.destroy();
179
180 const deployed = yield* stack.deploy(
181 Effect.gen(function* () {
182 return yield* Stripe.AppsSecret("ListSecret", {
183 name: LIST_NAME,
184 payload: LIST_PAYLOAD,
185 scope: ACCOUNT_SCOPE,
186 });
187 }),
188 );
189
190 const provider = yield* Provider.findProvider(Stripe.AppsSecret);
191 const all = yield* provider.list();
192 const found = all.find((secret) => secret.id === deployed.id);
193 expect(found).toBeDefined();
194 expect(found?.name).toEqual(LIST_NAME);
195 expect(found?.scope).toEqual(ACCOUNT_SCOPE);
196
197 yield* stack.destroy();
198
199 const gone = yield* waitUntilGone(LIST_NAME);
200 expect(gone).toEqual("gone");
201 }).pipe(logLevel),
202 {
203 tags: ["provider:stripe", "provider:stripe:appssecret", "live"],
204 timeout: 120_000,
205 },
206);