solidus_subscriptions 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +57 -9
  3. data/.github/dependabot.yml +7 -0
  4. data/.rubocop.yml +3 -0
  5. data/CHANGELOG.md +217 -169
  6. data/Gemfile +14 -1
  7. data/README.md +17 -7
  8. data/app/controllers/solidus_subscriptions/api/v1/subscriptions_controller.rb +24 -0
  9. data/app/controllers/spree/admin/subscriptions_controller.rb +32 -4
  10. data/app/decorators/models/solidus_subscriptions/spree/wallet_payment_source/report_default_change_to_subscriptions.rb +2 -2
  11. data/app/jobs/solidus_subscriptions/create_subscription_job.rb +11 -0
  12. data/app/jobs/solidus_subscriptions/process_installment_job.rb +1 -1
  13. data/app/models/solidus_subscriptions/subscription.rb +75 -31
  14. data/app/subscribers/solidus_subscriptions/churn_buster_subscriber.rb +1 -0
  15. data/app/subscribers/solidus_subscriptions/event_storage_subscriber.rb +1 -0
  16. data/app/subscribers/solidus_subscriptions/order_subscriber.rb +16 -0
  17. data/app/views/spree/admin/shared/_subscription_actions.html.erb +27 -8
  18. data/app/views/spree/admin/shared/_subscription_sidebar.html.erb +2 -0
  19. data/app/views/spree/admin/subscriptions/_state_pill.html.erb +3 -2
  20. data/app/views/spree/admin/subscriptions/index.html.erb +50 -13
  21. data/bin/sandbox +1 -6
  22. data/config/locales/en.yml +16 -0
  23. data/config/routes.rb +9 -3
  24. data/db/migrate/20210905145955_add_paused_to_subscriptions.rb +5 -0
  25. data/lib/decorators/api/controllers/solidus_subscriptions/spree/api/line_items_controller/create_subscription_line_items.rb +14 -0
  26. data/lib/generators/solidus_subscriptions/install/install_generator.rb +16 -0
  27. data/lib/generators/solidus_subscriptions/install/templates/app/controllers/concerns/create_subscription.rb +17 -0
  28. data/lib/generators/solidus_subscriptions/install/templates/app/views/cart_line_items/_subscription_fields.html.erb +30 -0
  29. data/lib/generators/solidus_subscriptions/install/templates/initializer.rb +3 -1
  30. data/lib/solidus_subscriptions/configuration.rb +29 -6
  31. data/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher.rb +2 -2
  32. data/lib/solidus_subscriptions/dispatcher/success_dispatcher.rb +2 -2
  33. data/lib/solidus_subscriptions/engine.rb +28 -0
  34. data/lib/solidus_subscriptions/permission_sets/default_customer.rb +1 -1
  35. data/lib/solidus_subscriptions/processing_error_handlers/rails_logger.rb +25 -0
  36. data/lib/solidus_subscriptions/subscription_generator.rb +6 -0
  37. data/lib/solidus_subscriptions/version.rb +1 -1
  38. data/solidus_subscriptions.gemspec +6 -6
  39. data/spec/controllers/spree/api/line_items_controller_spec.rb +63 -28
  40. data/spec/controllers/spree/api/users_controller_spec.rb +3 -0
  41. data/spec/decorators/controllers/solidus_subscriptions/spree/orders_controller/create_subscription_line_items_spec.rb +4 -4
  42. data/spec/decorators/models/solidus_subscriptions/spree/variant/auto_delete_from_subscriptions_spec.rb +2 -2
  43. data/spec/features/admin/subscriptions_spec.rb +13 -3
  44. data/spec/jobs/solidus_subscriptions/create_subscription_job_spec.rb +20 -0
  45. data/spec/jobs/solidus_subscriptions/process_installment_job_spec.rb +17 -0
  46. data/spec/lib/solidus_subscriptions/dispatcher/payment_failed_dispatcher_spec.rb +3 -3
  47. data/spec/lib/solidus_subscriptions/dispatcher/success_dispatcher_spec.rb +3 -3
  48. data/spec/lib/solidus_subscriptions/subscription_generator_spec.rb +1 -1
  49. data/spec/models/solidus_subscriptions/installment_spec.rb +1 -1
  50. data/spec/models/solidus_subscriptions/subscription_spec.rb +893 -16
  51. data/spec/models/spree/wallet_payment_source_spec.rb +3 -3
  52. data/spec/requests/api/v1/subscriptions_spec.rb +229 -0
  53. data/spec/subscribers/solidus_subscriptions/churn_buster_subscriber_spec.rb +8 -8
  54. data/spec/subscribers/solidus_subscriptions/order_subscriber_spec.rb +13 -0
  55. metadata +24 -17
  56. data/app/decorators/models/solidus_subscriptions/spree/order/finalize_creates_subscriptions.rb +0 -25
  57. data/config/initializers/subscribers.rb +0 -9
  58. data/spec/decorators/models/solidus_subscriptions/spree/order/finalize_creates_subscriptions_spec.rb +0 -32
  59. /data/{app → lib}/views/spree/frontend/products/_subscription_line_item_fields.html.erb +0 -0
@@ -28,12 +28,12 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do
28
28
 
29
29
  describe 'creating a subscription' do
30
30
  it 'tracks the creation' do
31
- stub_const('Spree::Event', class_spy(Spree::Event))
31
+ stub_const('SolidusSupport::LegacyEventCompat::Bus', class_spy(SolidusSupport::LegacyEventCompat::Bus))
32
32
 
33
33
  subscription = create(:subscription)
34
34
 
35
- expect(Spree::Event).to have_received(:fire).with(
36
- 'solidus_subscriptions.subscription_created',
35
+ expect(SolidusSupport::LegacyEventCompat::Bus).to have_received(:publish).with(
36
+ :'solidus_subscriptions.subscription_created',
37
37
  subscription: subscription,
38
38
  )
39
39
  end
@@ -53,49 +53,49 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do
53
53
 
54
54
  describe 'updating a subscription' do
55
55
  it 'tracks interval changes' do
56
- stub_const('Spree::Event', class_spy(Spree::Event))
56
+ stub_const('SolidusSupport::LegacyEventCompat::Bus', class_spy(SolidusSupport::LegacyEventCompat::Bus))
57
57
  subscription = create(:subscription)
58
58
 
59
59
  subscription.update!(interval_length: subscription.interval_length + 1)
60
60
 
61
- expect(Spree::Event).to have_received(:fire).with(
62
- 'solidus_subscriptions.subscription_frequency_changed',
61
+ expect(SolidusSupport::LegacyEventCompat::Bus).to have_received(:publish).with(
62
+ :'solidus_subscriptions.subscription_frequency_changed',
63
63
  subscription: subscription,
64
64
  )
65
65
  end
66
66
 
67
67
  it 'tracks shipping address changes' do
68
- stub_const('Spree::Event', class_spy(Spree::Event))
68
+ stub_const('SolidusSupport::LegacyEventCompat::Bus', class_spy(SolidusSupport::LegacyEventCompat::Bus))
69
69
  subscription = create(:subscription)
70
70
 
71
71
  subscription.update!(shipping_address: create(:address))
72
72
 
73
- expect(Spree::Event).to have_received(:fire).with(
74
- 'solidus_subscriptions.subscription_shipping_address_changed',
73
+ expect(SolidusSupport::LegacyEventCompat::Bus).to have_received(:publish).with(
74
+ :'solidus_subscriptions.subscription_shipping_address_changed',
75
75
  subscription: subscription,
76
76
  )
77
77
  end
78
78
 
79
79
  it 'tracks billing address changes' do
80
- stub_const('Spree::Event', class_spy(Spree::Event))
80
+ stub_const('SolidusSupport::LegacyEventCompat::Bus', class_spy(SolidusSupport::LegacyEventCompat::Bus))
81
81
  subscription = create(:subscription)
82
82
 
83
83
  subscription.update!(billing_address: create(:address))
84
84
 
85
- expect(Spree::Event).to have_received(:fire).with(
86
- 'solidus_subscriptions.subscription_billing_address_changed',
85
+ expect(SolidusSupport::LegacyEventCompat::Bus).to have_received(:publish).with(
86
+ :'solidus_subscriptions.subscription_billing_address_changed',
87
87
  subscription: subscription,
88
88
  )
89
89
  end
90
90
 
91
91
  it 'tracks payment method changes' do
92
- stub_const('Spree::Event', class_spy(Spree::Event))
92
+ stub_const('SolidusSupport::LegacyEventCompat::Bus', class_spy(SolidusSupport::LegacyEventCompat::Bus))
93
93
 
94
94
  subscription = create(:subscription)
95
95
  subscription.update!(payment_source: create(:credit_card, user: subscription.user))
96
96
 
97
- expect(Spree::Event).to have_received(:fire).with(
98
- 'solidus_subscriptions.subscription_payment_method_changed',
97
+ expect(SolidusSupport::LegacyEventCompat::Bus).to have_received(:publish).with(
98
+ :'solidus_subscriptions.subscription_payment_method_changed',
99
99
  subscription: subscription,
100
100
  )
101
101
  end
@@ -155,6 +155,866 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do
155
155
  end
156
156
  end
157
157
 
158
+ describe '#pause' do
159
+ context 'when an active subscription is paused' do
160
+ it 'sets the paused column to true' do
161
+ subscription = create(
162
+ :subscription,
163
+ :actionable,
164
+ :with_shipping_address,
165
+ state: 'active',
166
+ paused: false
167
+ )
168
+
169
+ subscription.pause
170
+
171
+ expect(subscription.reload.paused).to be_truthy
172
+ end
173
+
174
+ it 'does not change the state' do
175
+ subscription = create(
176
+ :subscription,
177
+ :actionable,
178
+ :with_shipping_address,
179
+ state: 'active',
180
+ paused: false
181
+ )
182
+
183
+ subscription.pause
184
+
185
+ expect(subscription.reload.state).to eq('active')
186
+ end
187
+
188
+ it 'creates a paused event' do
189
+ subscription = create(
190
+ :subscription,
191
+ :actionable,
192
+ :with_shipping_address,
193
+ state: 'active',
194
+ paused: false
195
+ )
196
+
197
+ subscription.pause
198
+
199
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_paused')
200
+ end
201
+
202
+ context 'when today is used as the actionable date' do
203
+ it 'sets actionable_date to the next day' do
204
+ subscription = create(
205
+ :subscription,
206
+ :actionable,
207
+ :with_shipping_address,
208
+ state: 'active',
209
+ paused: false
210
+ )
211
+
212
+ subscription.pause(actionable_date: Time.zone.today)
213
+
214
+ expect(subscription.reload.actionable_date).to eq(Time.zone.tomorrow)
215
+ end
216
+
217
+ it 'is not actionable' do
218
+ subscription = create(
219
+ :subscription,
220
+ :actionable,
221
+ :with_shipping_address,
222
+ state: 'active',
223
+ paused: false
224
+ )
225
+
226
+ subscription.pause(actionable_date: Time.zone.today)
227
+
228
+ expect(described_class.actionable).not_to include subscription
229
+ end
230
+
231
+ it 'pauses correctly' do
232
+ subscription = create(
233
+ :subscription,
234
+ :actionable,
235
+ :with_shipping_address,
236
+ state: 'active',
237
+ paused: false
238
+ )
239
+
240
+ subscription.pause(actionable_date: Time.zone.today)
241
+
242
+ aggregate_failures do
243
+ expect(subscription.reload.paused).to be_truthy
244
+ expect(subscription.state).to eq('active')
245
+ end
246
+ end
247
+
248
+ it 'creates a paused event' do
249
+ subscription = create(
250
+ :subscription,
251
+ :actionable,
252
+ :with_shipping_address,
253
+ state: 'active',
254
+ paused: false
255
+ )
256
+
257
+ subscription.pause(actionable_date: Time.zone.today)
258
+
259
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_paused')
260
+ end
261
+ end
262
+
263
+ context 'when a past date is used as the actionable date' do
264
+ it 'sets actionable_date to the next day' do
265
+ subscription = create(
266
+ :subscription,
267
+ :actionable,
268
+ :with_shipping_address,
269
+ state: 'active',
270
+ paused: false
271
+ )
272
+
273
+ subscription.pause(actionable_date: Time.zone.yesterday)
274
+
275
+ expect(subscription.reload.actionable_date).to eq(Time.zone.tomorrow)
276
+ end
277
+
278
+ it 'is not actionable' do
279
+ subscription = create(
280
+ :subscription,
281
+ :actionable,
282
+ :with_shipping_address,
283
+ state: 'active',
284
+ paused: false
285
+ )
286
+
287
+ subscription.pause(actionable_date: Time.zone.yesterday)
288
+
289
+ expect(described_class.actionable).not_to include subscription
290
+ end
291
+
292
+ it 'pauses correctly' do
293
+ subscription = create(
294
+ :subscription,
295
+ :actionable,
296
+ :with_shipping_address,
297
+ state: 'active',
298
+ paused: false
299
+ )
300
+
301
+ subscription.pause(actionable_date: Time.zone.yesterday)
302
+
303
+ aggregate_failures do
304
+ expect(subscription.reload.paused).to be_truthy
305
+ expect(subscription.state).to eq('active')
306
+ end
307
+ end
308
+
309
+ it 'creates a paused event' do
310
+ subscription = create(
311
+ :subscription,
312
+ :actionable,
313
+ :with_shipping_address,
314
+ state: 'active',
315
+ paused: false
316
+ )
317
+
318
+ subscription.pause(actionable_date: Time.zone.yesterday)
319
+
320
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_paused')
321
+ end
322
+ end
323
+
324
+ context 'when nil is used as the actionable date' do
325
+ it 'sets actionable_date to nil' do
326
+ subscription = create(
327
+ :subscription,
328
+ :actionable,
329
+ :with_shipping_address,
330
+ state: 'active',
331
+ paused: false
332
+ )
333
+
334
+ subscription.pause(actionable_date: nil)
335
+
336
+ expect(subscription.reload.actionable_date).to eq(nil)
337
+ end
338
+
339
+ it 'is not actionable' do
340
+ subscription = create(
341
+ :subscription,
342
+ :actionable,
343
+ :with_shipping_address,
344
+ state: 'active',
345
+ paused: false
346
+ )
347
+
348
+ subscription.pause(actionable_date: nil)
349
+
350
+ expect(described_class.actionable).not_to include subscription
351
+ end
352
+
353
+ it 'pauses correctly' do
354
+ subscription = create(
355
+ :subscription,
356
+ :actionable,
357
+ :with_shipping_address,
358
+ state: 'active',
359
+ paused: false
360
+ )
361
+
362
+ subscription.pause(actionable_date: nil)
363
+
364
+ aggregate_failures do
365
+ expect(subscription.reload.paused).to be_truthy
366
+ expect(subscription.state).to eq('active')
367
+ end
368
+ end
369
+
370
+ it 'creates a paused event' do
371
+ subscription = create(
372
+ :subscription,
373
+ :actionable,
374
+ :with_shipping_address,
375
+ state: 'active',
376
+ paused: false
377
+ )
378
+
379
+ subscription.pause(actionable_date: nil)
380
+
381
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_paused')
382
+ end
383
+ end
384
+
385
+ context 'when a future date is used as the actionable date' do
386
+ it 'sets actionable_date to the specified date' do
387
+ subscription = create(
388
+ :subscription,
389
+ :actionable,
390
+ :with_shipping_address,
391
+ state: 'active',
392
+ paused: false
393
+ )
394
+
395
+ subscription.pause(actionable_date: (Time.zone.tomorrow + 1.day))
396
+
397
+ expect(subscription.reload.actionable_date).to eq((Time.zone.tomorrow + 1.day))
398
+ end
399
+
400
+ it 'is not actionable' do
401
+ subscription = create(
402
+ :subscription,
403
+ :actionable,
404
+ :with_shipping_address,
405
+ state: 'active',
406
+ paused: false
407
+ )
408
+
409
+ subscription.pause(actionable_date: (Time.zone.tomorrow + 1.day))
410
+
411
+ expect(described_class.actionable).not_to include subscription
412
+ end
413
+
414
+ it 'pauses correctly' do
415
+ subscription = create(
416
+ :subscription,
417
+ :actionable,
418
+ :with_shipping_address,
419
+ state: 'active',
420
+ paused: false
421
+ )
422
+
423
+ subscription.pause(actionable_date: (Time.zone.tomorrow + 1.day))
424
+
425
+ aggregate_failures do
426
+ expect(subscription.reload.paused).to be_truthy
427
+ expect(subscription.state).to eq('active')
428
+ end
429
+ end
430
+
431
+ it 'creates a paused event' do
432
+ subscription = create(
433
+ :subscription,
434
+ :actionable,
435
+ :with_shipping_address,
436
+ state: 'active',
437
+ paused: false
438
+ )
439
+
440
+ subscription.pause(actionable_date: (Time.zone.tomorrow + 1.day))
441
+
442
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_paused')
443
+ end
444
+ end
445
+
446
+ context 'when the actionable date has been reached' do
447
+ it 'is actionable' do
448
+ subscription = create(
449
+ :subscription,
450
+ :actionable,
451
+ :with_shipping_address,
452
+ state: 'active',
453
+ paused: true
454
+ )
455
+
456
+ subscription.next_actionable_date
457
+
458
+ expect(described_class.actionable).to include subscription
459
+ end
460
+
461
+ it 'processes and resumes the subscription' do
462
+ subscription = create(
463
+ :subscription,
464
+ :actionable,
465
+ :with_shipping_address,
466
+ state: 'active',
467
+ paused: true
468
+ )
469
+ expected_actionable_date = subscription.actionable_date + subscription.interval
470
+
471
+ SolidusSubscriptions::ProcessSubscriptionJob.perform_now(subscription)
472
+
473
+ aggregate_failures do
474
+ expect(subscription.reload.paused).to be_falsy
475
+ expect(subscription.installments.last.created_at).to be_within(1.hour).of(Time.zone.now)
476
+ expect(subscription.actionable_date).to eq(expected_actionable_date)
477
+ end
478
+ end
479
+ end
480
+ end
481
+
482
+ context 'when a canceled subscription is paused' do
483
+ it 'adds an error when the method is called on a subscription which is not active' do
484
+ subscription = create(
485
+ :subscription,
486
+ :actionable,
487
+ :with_shipping_address,
488
+ state: 'canceled',
489
+ paused: false
490
+ )
491
+
492
+ subscription.pause
493
+
494
+ expect(subscription.errors[:paused].first).to include 'not active'
495
+ end
496
+
497
+ it 'does not alter the subscription' do
498
+ subscription = create(
499
+ :subscription,
500
+ :actionable,
501
+ :with_shipping_address,
502
+ state: 'canceled',
503
+ paused: false
504
+ )
505
+
506
+ expect { subscription.pause }.not_to(change { subscription.reload })
507
+ end
508
+
509
+ it 'does not create an event' do
510
+ subscription = create(
511
+ :subscription,
512
+ :actionable,
513
+ :with_shipping_address,
514
+ state: 'canceled',
515
+ paused: false
516
+ )
517
+
518
+ subscription.pause
519
+
520
+ expect(subscription.events.last).not_to have_attributes(event_type: "subscription_paused")
521
+ end
522
+ end
523
+
524
+ context 'when a `pending_cancellation` subscription is paused' do
525
+ it 'adds an error when the method is called on a subscription which is not active' do
526
+ subscription = create(
527
+ :subscription,
528
+ :actionable,
529
+ :with_shipping_address,
530
+ state: 'pending_cancellation',
531
+ paused: false
532
+ )
533
+
534
+ subscription.pause
535
+
536
+ expect(subscription.errors[:paused].first).to include 'not active'
537
+ end
538
+
539
+ it 'does not alter the subscription' do
540
+ subscription = create(
541
+ :subscription,
542
+ :actionable,
543
+ :with_shipping_address,
544
+ state: 'pending_cancellation',
545
+ paused: false
546
+ )
547
+
548
+ expect { subscription.pause }.not_to(change { subscription.reload })
549
+ end
550
+
551
+ it 'does not create an event' do
552
+ subscription = create(
553
+ :subscription,
554
+ :actionable,
555
+ :with_shipping_address,
556
+ state: 'pending_cancellation',
557
+ paused: true
558
+ )
559
+
560
+ subscription.pause
561
+
562
+ expect(subscription.events.last).not_to have_attributes(event_type: "subscription_paused")
563
+ end
564
+ end
565
+
566
+ context 'when an `inactive` subscription is paused' do
567
+ it 'adds an error when the method is called on a subscription which is not active' do
568
+ subscription = create(
569
+ :subscription,
570
+ :actionable,
571
+ :with_shipping_address,
572
+ state: 'inactive',
573
+ paused: false
574
+ )
575
+
576
+ subscription.pause
577
+
578
+ expect(subscription.errors[:paused].first).to include 'not active'
579
+ end
580
+
581
+ it 'does not alter the subscription' do
582
+ subscription = create(
583
+ :subscription,
584
+ :actionable,
585
+ :with_shipping_address,
586
+ state: 'inactive',
587
+ paused: false
588
+ )
589
+
590
+ expect { subscription.pause }.not_to(change { subscription.reload })
591
+ end
592
+
593
+ it 'does not create an event' do
594
+ subscription = create(
595
+ :subscription,
596
+ :actionable,
597
+ :with_shipping_address,
598
+ state: 'inactive',
599
+ paused: false
600
+ )
601
+
602
+ subscription.pause
603
+
604
+ expect(subscription.events.last).not_to have_attributes(event_type: "subscription_paused")
605
+ end
606
+ end
607
+ end
608
+
609
+ describe '#resume' do
610
+ context 'when an active subscription is resumed' do
611
+ it 'sets the paused column to false' do
612
+ subscription = create(
613
+ :subscription,
614
+ :actionable,
615
+ :with_shipping_address,
616
+ state: 'active',
617
+ paused: true
618
+ )
619
+
620
+ subscription.resume
621
+
622
+ expect(subscription.reload.paused).to be_falsy
623
+ end
624
+
625
+ it 'does not change the state' do
626
+ subscription = create(
627
+ :subscription,
628
+ :actionable,
629
+ :with_shipping_address,
630
+ state: 'active',
631
+ paused: true
632
+ )
633
+
634
+ subscription.resume
635
+
636
+ expect(subscription.reload.state).to eq('active')
637
+ end
638
+
639
+ it 'creates a resumed event' do
640
+ subscription = create(
641
+ :subscription,
642
+ :actionable,
643
+ :with_shipping_address,
644
+ state: 'active',
645
+ paused: true
646
+ )
647
+
648
+ subscription.resume
649
+
650
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_resumed')
651
+ end
652
+
653
+ context 'when a past date is used as the actionable date' do
654
+ it 'sets actionable_date to the next day' do
655
+ subscription = create(
656
+ :subscription,
657
+ :actionable,
658
+ :with_shipping_address,
659
+ state: 'active',
660
+ paused: true
661
+ )
662
+
663
+ subscription.resume(actionable_date: Time.zone.yesterday)
664
+
665
+ expect(subscription.reload.actionable_date).to eq(Time.zone.tomorrow)
666
+ end
667
+
668
+ it 'is not actionable' do
669
+ subscription = create(
670
+ :subscription,
671
+ :actionable,
672
+ :with_shipping_address,
673
+ state: 'active',
674
+ paused: true
675
+ )
676
+
677
+ subscription.resume(actionable_date: Time.zone.yesterday)
678
+
679
+ expect(described_class.actionable).not_to include subscription
680
+ end
681
+
682
+ it 'resumes correctly' do
683
+ subscription = create(
684
+ :subscription,
685
+ :actionable,
686
+ :with_shipping_address,
687
+ state: 'active',
688
+ paused: true
689
+ )
690
+
691
+ subscription.resume(actionable_date: Time.zone.yesterday)
692
+
693
+ aggregate_failures do
694
+ expect(subscription.reload.paused).to be_falsy
695
+ expect(subscription.state).to eq('active')
696
+ end
697
+ end
698
+
699
+ it 'creates a resumed event' do
700
+ subscription = create(
701
+ :subscription,
702
+ :actionable,
703
+ :with_shipping_address,
704
+ state: 'active',
705
+ paused: true
706
+ )
707
+
708
+ subscription.resume(actionable_date: Time.zone.yesterday)
709
+
710
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_resumed')
711
+ end
712
+ end
713
+
714
+ context 'when today is used as the actionable date' do
715
+ it 'sets actionable_date to the next day' do
716
+ subscription = create(
717
+ :subscription,
718
+ :actionable,
719
+ :with_shipping_address,
720
+ state: 'active',
721
+ paused: true
722
+ )
723
+
724
+ subscription.resume(actionable_date: Time.zone.today)
725
+
726
+ expect(subscription.reload.actionable_date).to eq(Time.zone.tomorrow)
727
+ end
728
+
729
+ it 'is not actionable' do
730
+ subscription = create(
731
+ :subscription,
732
+ :actionable,
733
+ :with_shipping_address,
734
+ state: 'active',
735
+ paused: true
736
+ )
737
+
738
+ subscription.resume(actionable_date: Time.zone.today)
739
+
740
+ expect(described_class.actionable).not_to include subscription
741
+ end
742
+
743
+ it 'resumes correctly' do
744
+ subscription = create(
745
+ :subscription,
746
+ :actionable,
747
+ :with_shipping_address,
748
+ state: 'active',
749
+ paused: true
750
+ )
751
+
752
+ subscription.resume(actionable_date: Time.zone.today)
753
+
754
+ aggregate_failures do
755
+ expect(subscription.reload.paused).to be_falsy
756
+ expect(subscription.state).to eq('active')
757
+ end
758
+ end
759
+
760
+ it 'creates a resumed event' do
761
+ subscription = create(
762
+ :subscription,
763
+ :actionable,
764
+ :with_shipping_address,
765
+ state: 'active',
766
+ paused: true
767
+ )
768
+
769
+ subscription.resume(actionable_date: Time.zone.today)
770
+
771
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_resumed')
772
+ end
773
+ end
774
+
775
+ context 'when nil is used as the actionable date' do
776
+ it 'sets actionable_date to the next day' do
777
+ subscription = create(
778
+ :subscription,
779
+ :actionable,
780
+ :with_shipping_address,
781
+ state: 'active',
782
+ paused: true
783
+ )
784
+
785
+ subscription.resume(actionable_date: nil)
786
+
787
+ expect(subscription.reload.actionable_date).to eq(Time.zone.tomorrow)
788
+ end
789
+
790
+ it 'is not actionable' do
791
+ subscription = create(
792
+ :subscription,
793
+ :actionable,
794
+ :with_shipping_address,
795
+ state: 'active',
796
+ paused: true
797
+ )
798
+
799
+ subscription.resume(actionable_date: nil)
800
+
801
+ expect(described_class.actionable).not_to include subscription
802
+ end
803
+
804
+ it 'resumes correctly' do
805
+ subscription = create(
806
+ :subscription,
807
+ :actionable,
808
+ :with_shipping_address,
809
+ state: 'active',
810
+ paused: true
811
+ )
812
+
813
+ subscription.resume(actionable_date: nil)
814
+
815
+ aggregate_failures do
816
+ expect(subscription.reload.paused).to be_falsy
817
+ expect(subscription.state).to eq('active')
818
+ end
819
+ end
820
+
821
+ it 'creates a resumed event' do
822
+ subscription = create(
823
+ :subscription,
824
+ :actionable,
825
+ :with_shipping_address,
826
+ state: 'active',
827
+ paused: true
828
+ )
829
+
830
+ subscription.resume(actionable_date: nil)
831
+
832
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_resumed')
833
+ end
834
+ end
835
+
836
+ context 'when a future date is used as the actionable date' do
837
+ it 'sets actionable_date to the specified date' do
838
+ subscription = create(
839
+ :subscription,
840
+ :actionable,
841
+ :with_shipping_address,
842
+ state: 'active',
843
+ paused: true
844
+ )
845
+
846
+ subscription.resume(actionable_date: (Time.zone.tomorrow + 1.day))
847
+
848
+ expect(subscription.reload.actionable_date).to eq((Time.zone.tomorrow + 1.day))
849
+ end
850
+
851
+ it 'is not actionable' do
852
+ subscription = create(
853
+ :subscription,
854
+ :actionable,
855
+ :with_shipping_address,
856
+ state: 'active',
857
+ paused: true
858
+ )
859
+
860
+ subscription.resume(actionable_date: (Time.zone.tomorrow + 1.day))
861
+
862
+ expect(described_class.actionable).not_to include subscription
863
+ end
864
+
865
+ it 'resumes correctly' do
866
+ subscription = create(
867
+ :subscription,
868
+ :actionable,
869
+ :with_shipping_address,
870
+ state: 'active',
871
+ paused: true
872
+ )
873
+
874
+ subscription.resume(actionable_date: (Time.zone.tomorrow + 1.day))
875
+
876
+ aggregate_failures do
877
+ expect(subscription.reload.paused).to be_falsy
878
+ expect(subscription.state).to eq('active')
879
+ end
880
+ end
881
+
882
+ it 'creates a resumed event' do
883
+ subscription = create(
884
+ :subscription,
885
+ :actionable,
886
+ :with_shipping_address,
887
+ state: 'active',
888
+ paused: true
889
+ )
890
+
891
+ subscription.resume(actionable_date: (Time.zone.tomorrow + 1.day))
892
+
893
+ expect(subscription.events.last).to have_attributes(event_type: 'subscription_resumed')
894
+ end
895
+ end
896
+ end
897
+
898
+ context 'when a `canceled` subscription is resumed' do
899
+ it 'does not alter the subscription' do
900
+ subscription = create(
901
+ :subscription,
902
+ :actionable,
903
+ :with_shipping_address,
904
+ state: 'canceled',
905
+ paused: true
906
+ )
907
+
908
+ expect { subscription.resume }.not_to(change { subscription.reload })
909
+ end
910
+
911
+ it 'does not create an event' do
912
+ subscription = create(
913
+ :subscription,
914
+ :actionable,
915
+ :with_shipping_address,
916
+ state: 'canceled',
917
+ paused: true
918
+ )
919
+
920
+ subscription.resume
921
+
922
+ expect(subscription.events.last).not_to have_attributes(event_type: "subscription_resumed")
923
+ end
924
+ end
925
+
926
+ context 'when a `pending_cancellation` subscription is resumed' do
927
+ it 'does not alter the subscription' do
928
+ subscription = create(
929
+ :subscription,
930
+ :actionable,
931
+ :with_shipping_address,
932
+ state: 'pending_cancellation',
933
+ paused: true
934
+ )
935
+
936
+ expect { subscription.resume }.not_to(change { subscription.reload })
937
+ end
938
+
939
+ it 'does not create an event' do
940
+ subscription = create(
941
+ :subscription,
942
+ :actionable,
943
+ :with_shipping_address,
944
+ state: 'pending_cancellation',
945
+ paused: true
946
+ )
947
+
948
+ subscription.resume
949
+
950
+ expect(subscription.events.last).not_to have_attributes(event_type: "subscription_resumed")
951
+ end
952
+ end
953
+
954
+ context 'when an `inactive` subscription is resumed' do
955
+ it 'does not alter the subscription' do
956
+ subscription = create(
957
+ :subscription,
958
+ :actionable,
959
+ :with_shipping_address,
960
+ state: 'inactive',
961
+ paused: true
962
+ )
963
+
964
+ expect { subscription.resume }.not_to(change { subscription.reload })
965
+ end
966
+
967
+ it 'does not create an event' do
968
+ subscription = create(
969
+ :subscription,
970
+ :actionable,
971
+ :with_shipping_address,
972
+ state: 'inactive',
973
+ paused: true
974
+ )
975
+
976
+ subscription.resume
977
+
978
+ expect(subscription.events.last).not_to have_attributes(event_type: "subscription_resumed")
979
+ end
980
+ end
981
+ end
982
+
983
+ describe '#state_with_pause' do
984
+ it 'returns `paused` when the subscription is active and paused' do
985
+ subscription = create(
986
+ :subscription,
987
+ :with_shipping_address,
988
+ paused: true,
989
+ state: 'active'
990
+ )
991
+
992
+ expect(subscription.state_with_pause).to eq('paused')
993
+ end
994
+
995
+ it 'returns `active` when the subscription is active and not paused' do
996
+ subscription = create(
997
+ :subscription,
998
+ :with_shipping_address,
999
+ paused: false,
1000
+ state: 'active'
1001
+ )
1002
+
1003
+ expect(subscription.state_with_pause).to eq('active')
1004
+ end
1005
+
1006
+ it 'returns `canceled` when the subscription is canceled and not paused' do
1007
+ subscription = create(
1008
+ :subscription,
1009
+ :with_shipping_address,
1010
+ paused: false,
1011
+ state: 'canceled'
1012
+ )
1013
+
1014
+ expect(subscription.state_with_pause).to eq('canceled')
1015
+ end
1016
+ end
1017
+
158
1018
  describe '#skip' do
159
1019
  subject { subscription.skip&.to_date }
160
1020
 
@@ -402,7 +1262,24 @@ RSpec.describe SolidusSubscriptions::Subscription, type: :model do
402
1262
  describe '.ransackable_scopes' do
403
1263
  subject { described_class.ransackable_scopes }
404
1264
 
405
- it { is_expected.to match_array [:in_processing_state] }
1265
+ it { is_expected.to match_array [:in_processing_state, :with_subscribable] }
1266
+ end
1267
+
1268
+ describe '.with_subscribable' do
1269
+ let(:subscription) do
1270
+ create :subscription, :with_line_item
1271
+ end
1272
+ let(:other_subscription) do
1273
+ create :subscription, :with_line_item
1274
+ end
1275
+
1276
+ it 'can find subscription with line items of the provided subscribable' do
1277
+ subscribable = subscription.line_items.first.subscribable
1278
+ other_subscribable = other_subscription.line_items.first.subscribable
1279
+
1280
+ expect(described_class.with_subscribable(subscribable)).to match_array([subscription])
1281
+ expect(described_class.with_subscribable(other_subscribable)).to match_array([other_subscription])
1282
+ end
406
1283
  end
407
1284
 
408
1285
  describe '.in_processing_state' do