@@ -3,12 +3,15 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
33const mockCreate = vi . fn ( ) . mockResolvedValue ( { id : 'cs_test_123' , url : 'https://checkout.stripe.com/...' } ) ;
44const mockConstructEvent = vi . fn ( ) ;
55const mockCancel = vi . fn ( ) . mockResolvedValue ( { id : 'sub_123' , status : 'canceled' } ) ;
6+ const mockCustomersList = vi . fn ( ) . mockResolvedValue ( { data : [ ] } ) ;
7+ const mockSubscriptionsList = vi . fn ( ) . mockResolvedValue ( { data : [ ] } ) ;
68
79vi . mock ( 'stripe' , ( ) => ( {
810 default : class {
911 checkout = { sessions : { create : mockCreate } } ;
1012 webhooks = { constructEvent : mockConstructEvent } ;
11- subscriptions = { cancel : mockCancel } ;
13+ subscriptions = { cancel : mockCancel , list : mockSubscriptionsList } ;
14+ customers = { list : mockCustomersList } ;
1215 } ,
1316} ) ) ;
1417
@@ -34,9 +37,16 @@ function createMockDb() {
3437 limit : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
3538 } ;
3639
40+ const mockUpdateChain = {
41+ set : vi . fn ( ) . mockReturnThis ( ) ,
42+ where : vi . fn ( ) . mockResolvedValue ( undefined ) ,
43+ } ;
44+
3745 return {
3846 select : vi . fn ( ) . mockReturnValue ( mockChain ) ,
47+ update : vi . fn ( ) . mockReturnValue ( mockUpdateChain ) ,
3948 _chain : mockChain ,
49+ _updateChain : mockUpdateChain ,
4050 } as any ;
4151}
4252
@@ -167,4 +177,117 @@ describe('stripe.ts', () => {
167177 expect ( mockCancel ) . toHaveBeenCalledWith ( 'sub_123' ) ;
168178 } ) ;
169179 } ) ;
180+
181+ describe ( 'syncSubscriptionFromStripe' , ( ) => {
182+ it ( 'should return true immediately when member is already active with subscription' , async ( ) => {
183+ const { getStripeClient, syncSubscriptionFromStripe } = await import ( './stripe' ) ;
184+ const stripe = getStripeClient ( 'sk_test_xxx' ) ;
185+ const db = createMockDb ( ) ;
186+
187+ const member = {
188+ id : 'member-1' ,
189+ email : 'test@example.com' ,
190+ status : 'active' ,
191+ stripeCustomerId : 'cus_123' ,
192+ stripeSubscriptionId : 'sub_123' ,
193+ } ;
194+
195+ const result = await syncSubscriptionFromStripe ( member , stripe , db ) ;
196+ expect ( result ) . toBe ( true ) ;
197+ expect ( mockCustomersList ) . not . toHaveBeenCalled ( ) ;
198+ expect ( mockSubscriptionsList ) . not . toHaveBeenCalled ( ) ;
199+ } ) ;
200+
201+ it ( 'should return false when no Stripe customer found' , async ( ) => {
202+ const { getStripeClient, syncSubscriptionFromStripe } = await import ( './stripe' ) ;
203+ const stripe = getStripeClient ( 'sk_test_xxx' ) ;
204+ const db = createMockDb ( ) ;
205+ mockCustomersList . mockResolvedValueOnce ( { data : [ ] } ) ;
206+
207+ const member = {
208+ id : 'member-1' ,
209+ email : 'test@example.com' ,
210+ status : 'pending' ,
211+ stripeCustomerId : null ,
212+ stripeSubscriptionId : null ,
213+ } ;
214+
215+ const result = await syncSubscriptionFromStripe ( member , stripe , db ) ;
216+ expect ( result ) . toBe ( false ) ;
217+ expect ( mockCustomersList ) . toHaveBeenCalledWith ( { email : 'test@example.com' , limit : 1 } ) ;
218+ } ) ;
219+
220+ it ( 'should sync and return true when Stripe has active subscription' , async ( ) => {
221+ const { getStripeClient, syncSubscriptionFromStripe } = await import ( './stripe' ) ;
222+ const stripe = getStripeClient ( 'sk_test_xxx' ) ;
223+ const db = createMockDb ( ) ;
224+ // countActivePaidMembers query
225+ db . _chain . where . mockReturnValueOnce ( [ { total : 5 } ] ) ;
226+ mockCustomersList . mockResolvedValueOnce ( { data : [ { id : 'cus_456' } ] } ) ;
227+ mockSubscriptionsList . mockResolvedValueOnce ( { data : [ { id : 'sub_789' } ] } ) ;
228+
229+ const member = {
230+ id : 'member-1' ,
231+ email : 'test@example.com' ,
232+ status : 'pending' ,
233+ stripeCustomerId : null ,
234+ stripeSubscriptionId : null ,
235+ } ;
236+
237+ const result = await syncSubscriptionFromStripe ( member , stripe , db ) ;
238+ expect ( result ) . toBe ( true ) ;
239+ expect ( db . update ) . toHaveBeenCalled ( ) ;
240+ expect ( db . _updateChain . set ) . toHaveBeenCalledWith ( {
241+ stripeCustomerId : 'cus_456' ,
242+ stripeSubscriptionId : 'sub_789' ,
243+ tier : 'early' ,
244+ status : 'active' ,
245+ } ) ;
246+ } ) ;
247+
248+ it ( 'should use existing stripeCustomerId without looking up by email' , async ( ) => {
249+ const { getStripeClient, syncSubscriptionFromStripe } = await import ( './stripe' ) ;
250+ const stripe = getStripeClient ( 'sk_test_xxx' ) ;
251+ const db = createMockDb ( ) ;
252+ db . _chain . where . mockReturnValueOnce ( [ { total : 5 } ] ) ;
253+ mockSubscriptionsList . mockResolvedValueOnce ( { data : [ { id : 'sub_789' } ] } ) ;
254+
255+ const member = {
256+ id : 'member-1' ,
257+ email : 'test@example.com' ,
258+ status : 'pending' ,
259+ stripeCustomerId : 'cus_existing' ,
260+ stripeSubscriptionId : null ,
261+ } ;
262+
263+ const result = await syncSubscriptionFromStripe ( member , stripe , db ) ;
264+ expect ( result ) . toBe ( true ) ;
265+ expect ( mockCustomersList ) . not . toHaveBeenCalled ( ) ;
266+ expect ( mockSubscriptionsList ) . toHaveBeenCalledWith ( {
267+ customer : 'cus_existing' ,
268+ status : 'active' ,
269+ limit : 1 ,
270+ } ) ;
271+ } ) ;
272+
273+ it ( 'should return false when customer exists but no active subscription' , async ( ) => {
274+ const { getStripeClient, syncSubscriptionFromStripe } = await import ( './stripe' ) ;
275+ const stripe = getStripeClient ( 'sk_test_xxx' ) ;
276+ const db = createMockDb ( ) ;
277+ mockCustomersList . mockResolvedValueOnce ( { data : [ { id : 'cus_456' } ] } ) ;
278+ mockSubscriptionsList . mockResolvedValueOnce ( { data : [ ] } ) ;
279+
280+ const member = {
281+ id : 'member-1' ,
282+ email : 'test@example.com' ,
283+ status : 'pending' ,
284+ stripeCustomerId : null ,
285+ stripeSubscriptionId : null ,
286+ } ;
287+
288+ const result = await syncSubscriptionFromStripe ( member , stripe , db ) ;
289+ expect ( result ) . toBe ( false ) ;
290+ expect ( db . update ) . not . toHaveBeenCalled ( ) ;
291+ } ) ;
292+ } ) ;
170293} ) ;
0 commit comments