solidus_core 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/Gemfile +3 -0
  4. data/Rakefile +16 -0
  5. data/script/rails +9 -0
  6. data/solidus_core.gemspec +48 -0
  7. data/spec/fixtures/thinking-cat.jpg +0 -0
  8. data/spec/helpers/base_helper_spec.rb +173 -0
  9. data/spec/helpers/order_helper_spec.rb +12 -0
  10. data/spec/helpers/products_helper_spec.rb +220 -0
  11. data/spec/helpers/taxons_helper_spec.rb +17 -0
  12. data/spec/lib/calculated_adjustments_spec.rb +7 -0
  13. data/spec/lib/i18n_spec.rb +123 -0
  14. data/spec/lib/search/base_spec.rb +86 -0
  15. data/spec/lib/search/variant_spec.rb +92 -0
  16. data/spec/lib/spree/core/controller_helpers/auth_spec.rb +66 -0
  17. data/spec/lib/spree/core/controller_helpers/order_spec.rb +92 -0
  18. data/spec/lib/spree/core/controller_helpers/search_spec.rb +17 -0
  19. data/spec/lib/spree/core/controller_helpers/store_spec.rb +16 -0
  20. data/spec/lib/spree/core/controller_helpers/strong_parameters_spec.rb +39 -0
  21. data/spec/lib/spree/core/current_store_spec.rb +36 -0
  22. data/spec/lib/spree/core/delegate_belongs_to_spec.rb +22 -0
  23. data/spec/lib/spree/core/importer/order_spec.rb +431 -0
  24. data/spec/lib/spree/core/role_configuration_spec.rb +138 -0
  25. data/spec/lib/spree/core/validators/email_spec.rb +48 -0
  26. data/spec/lib/spree/localized_number_spec.rb +38 -0
  27. data/spec/lib/spree/migrations_spec.rb +36 -0
  28. data/spec/lib/spree/money_spec.rb +127 -0
  29. data/spec/lib/tasks/exchanges_spec.rb +231 -0
  30. data/spec/lib/tasks/migrations/copy_shipped_shipments_to_cartons_spec.rb +115 -0
  31. data/spec/lib/tasks/order_capturing_spec.rb +56 -0
  32. data/spec/mailers/carton_mailer_spec.rb +43 -0
  33. data/spec/mailers/order_mailer_spec.rb +122 -0
  34. data/spec/mailers/reimbursement_mailer_spec.rb +40 -0
  35. data/spec/mailers/test_mailer_spec.rb +15 -0
  36. data/spec/models/spree/ability_spec.rb +276 -0
  37. data/spec/models/spree/address_spec.rb +250 -0
  38. data/spec/models/spree/adjustment_reason_spec.rb +13 -0
  39. data/spec/models/spree/adjustment_spec.rb +177 -0
  40. data/spec/models/spree/app_configuration_spec.rb +20 -0
  41. data/spec/models/spree/asset_spec.rb +24 -0
  42. data/spec/models/spree/calculator/default_tax_spec.rb +127 -0
  43. data/spec/models/spree/calculator/flat_percent_item_total_spec.rb +25 -0
  44. data/spec/models/spree/calculator/flat_rate_spec.rb +47 -0
  45. data/spec/models/spree/calculator/flexi_rate_spec.rb +41 -0
  46. data/spec/models/spree/calculator/percent_on_line_item_spec.rb +15 -0
  47. data/spec/models/spree/calculator/price_sack_spec.rb +30 -0
  48. data/spec/models/spree/calculator/refunds/default_refund_amount_spec.rb +51 -0
  49. data/spec/models/spree/calculator/shipping/flat_percent_item_total_spec.rb +23 -0
  50. data/spec/models/spree/calculator/shipping/flat_rate_spec.rb +13 -0
  51. data/spec/models/spree/calculator/shipping/flexi_rate_spec.rb +52 -0
  52. data/spec/models/spree/calculator/shipping/per_item_spec.rb +20 -0
  53. data/spec/models/spree/calculator/shipping/price_sack_spec.rb +30 -0
  54. data/spec/models/spree/calculator/tiered_flat_rate_spec.rb +36 -0
  55. data/spec/models/spree/calculator/tiered_percent_spec.rb +47 -0
  56. data/spec/models/spree/calculator_spec.rb +36 -0
  57. data/spec/models/spree/carton_spec.rb +133 -0
  58. data/spec/models/spree/classification_spec.rb +15 -0
  59. data/spec/models/spree/concerns/display_money_spec.rb +43 -0
  60. data/spec/models/spree/concerns/user_methods_spec.rb +41 -0
  61. data/spec/models/spree/credit_card_spec.rb +334 -0
  62. data/spec/models/spree/customer_return_spec.rb +276 -0
  63. data/spec/models/spree/exchange_spec.rb +79 -0
  64. data/spec/models/spree/gateway/bogus_simple.rb +20 -0
  65. data/spec/models/spree/gateway/bogus_spec.rb +13 -0
  66. data/spec/models/spree/gateway_spec.rb +82 -0
  67. data/spec/models/spree/inventory_unit_spec.rb +307 -0
  68. data/spec/models/spree/item_adjustments_spec.rb +256 -0
  69. data/spec/models/spree/line_item_spec.rb +191 -0
  70. data/spec/models/spree/option_type_spec.rb +14 -0
  71. data/spec/models/spree/option_value_spec.rb +22 -0
  72. data/spec/models/spree/order/address_spec.rb +50 -0
  73. data/spec/models/spree/order/adjustments_spec.rb +39 -0
  74. data/spec/models/spree/order/callbacks_spec.rb +42 -0
  75. data/spec/models/spree/order/checkout_spec.rb +902 -0
  76. data/spec/models/spree/order/currency_updater_spec.rb +32 -0
  77. data/spec/models/spree/order/finalizing_spec.rb +111 -0
  78. data/spec/models/spree/order/payment_spec.rb +210 -0
  79. data/spec/models/spree/order/risk_assessment_spec.rb +68 -0
  80. data/spec/models/spree/order/state_machine_spec.rb +221 -0
  81. data/spec/models/spree/order/tax_spec.rb +84 -0
  82. data/spec/models/spree/order/totals_spec.rb +24 -0
  83. data/spec/models/spree/order/updating_spec.rb +18 -0
  84. data/spec/models/spree/order/validations_spec.rb +15 -0
  85. data/spec/models/spree/order_cancellations_spec.rb +120 -0
  86. data/spec/models/spree/order_capturing_spec.rb +116 -0
  87. data/spec/models/spree/order_contents_spec.rb +265 -0
  88. data/spec/models/spree/order_inventory_spec.rb +228 -0
  89. data/spec/models/spree/order_mutex_spec.rb +85 -0
  90. data/spec/models/spree/order_promotion_spec.rb +31 -0
  91. data/spec/models/spree/order_shipping_spec.rb +247 -0
  92. data/spec/models/spree/order_spec.rb +1412 -0
  93. data/spec/models/spree/order_stock_location_spec.rb +18 -0
  94. data/spec/models/spree/order_updater_spec.rb +299 -0
  95. data/spec/models/spree/payment_method/store_credit_spec.rb +294 -0
  96. data/spec/models/spree/payment_method_spec.rb +96 -0
  97. data/spec/models/spree/payment_spec.rb +1044 -0
  98. data/spec/models/spree/permission_sets/base_spec.rb +12 -0
  99. data/spec/models/spree/permission_sets/configuration_display.rb +82 -0
  100. data/spec/models/spree/permission_sets/configuration_management_spec.rb +50 -0
  101. data/spec/models/spree/permission_sets/dashboard_display_spec.rb +22 -0
  102. data/spec/models/spree/permission_sets/order_display_spec.rb +49 -0
  103. data/spec/models/spree/permission_sets/order_management_spec.rb +36 -0
  104. data/spec/models/spree/permission_sets/product_display_spec.rb +60 -0
  105. data/spec/models/spree/permission_sets/product_management_spec.rb +40 -0
  106. data/spec/models/spree/permission_sets/promotion_display_spec.rb +34 -0
  107. data/spec/models/spree/permission_sets/promotion_management_spec.rb +26 -0
  108. data/spec/models/spree/permission_sets/report_display_spec.rb +24 -0
  109. data/spec/models/spree/permission_sets/restricted_transfer_management_spec.rb +132 -0
  110. data/spec/models/spree/permission_sets/stock_display_spec.rb +26 -0
  111. data/spec/models/spree/permission_sets/stock_management_spec.rb +24 -0
  112. data/spec/models/spree/permission_sets/user_display_spec.rb +36 -0
  113. data/spec/models/spree/permission_sets/user_management_spec.rb +28 -0
  114. data/spec/models/spree/preference_spec.rb +80 -0
  115. data/spec/models/spree/preferences/configuration_spec.rb +30 -0
  116. data/spec/models/spree/preferences/preferable_spec.rb +294 -0
  117. data/spec/models/spree/preferences/scoped_store_spec.rb +58 -0
  118. data/spec/models/spree/preferences/static_model_preferences_spec.rb +78 -0
  119. data/spec/models/spree/preferences/statically_configurable_spec.rb +60 -0
  120. data/spec/models/spree/preferences/store_spec.rb +39 -0
  121. data/spec/models/spree/price_spec.rb +42 -0
  122. data/spec/models/spree/product/scopes_spec.rb +148 -0
  123. data/spec/models/spree/product_duplicator_spec.rb +103 -0
  124. data/spec/models/spree/product_filter_spec.rb +26 -0
  125. data/spec/models/spree/product_property_spec.rb +20 -0
  126. data/spec/models/spree/product_spec.rb +437 -0
  127. data/spec/models/spree/promotion/actions/create_adjustment_spec.rb +96 -0
  128. data/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb +165 -0
  129. data/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb +115 -0
  130. data/spec/models/spree/promotion/actions/free_shipping_spec.rb +40 -0
  131. data/spec/models/spree/promotion/rules/first_order_spec.rb +75 -0
  132. data/spec/models/spree/promotion/rules/item_total_spec.rb +67 -0
  133. data/spec/models/spree/promotion/rules/nth_order_spec.rb +70 -0
  134. data/spec/models/spree/promotion/rules/one_use_per_user_spec.rb +42 -0
  135. data/spec/models/spree/promotion/rules/option_value_spec.rb +94 -0
  136. data/spec/models/spree/promotion/rules/product_spec.rb +143 -0
  137. data/spec/models/spree/promotion/rules/taxon_spec.rb +102 -0
  138. data/spec/models/spree/promotion/rules/user_logged_in_spec.rb +27 -0
  139. data/spec/models/spree/promotion/rules/user_spec.rb +37 -0
  140. data/spec/models/spree/promotion_builder_spec.rb +118 -0
  141. data/spec/models/spree/promotion_category_spec.rb +17 -0
  142. data/spec/models/spree/promotion_code/code_builder_spec.rb +79 -0
  143. data/spec/models/spree/promotion_code_spec.rb +187 -0
  144. data/spec/models/spree/promotion_handler/cart_spec.rb +114 -0
  145. data/spec/models/spree/promotion_handler/coupon_spec.rb +335 -0
  146. data/spec/models/spree/promotion_handler/free_shipping_spec.rb +47 -0
  147. data/spec/models/spree/promotion_handler/page_spec.rb +44 -0
  148. data/spec/models/spree/promotion_rule_spec.rb +28 -0
  149. data/spec/models/spree/promotion_spec.rb +767 -0
  150. data/spec/models/spree/refund_spec.rb +204 -0
  151. data/spec/models/spree/reimbursement/credit_spec.rb +36 -0
  152. data/spec/models/spree/reimbursement/reimbursement_type_engine_spec.rb +140 -0
  153. data/spec/models/spree/reimbursement/reimbursement_type_validator_spec.rb +83 -0
  154. data/spec/models/spree/reimbursement_performer_spec.rb +30 -0
  155. data/spec/models/spree/reimbursement_spec.rb +231 -0
  156. data/spec/models/spree/reimbursement_tax_calculator_spec.rb +51 -0
  157. data/spec/models/spree/reimbursement_type/credit_spec.rb +53 -0
  158. data/spec/models/spree/reimbursement_type/exchange_spec.rb +46 -0
  159. data/spec/models/spree/reimbursement_type/original_payment_spec.rb +107 -0
  160. data/spec/models/spree/reimbursement_type/store_credit_spec.rb +97 -0
  161. data/spec/models/spree/return_authorization_spec.rb +290 -0
  162. data/spec/models/spree/return_item/eligibility_validator/default_spec.rb +77 -0
  163. data/spec/models/spree/return_item/eligibility_validator/inventory_shipped_spec.rb +58 -0
  164. data/spec/models/spree/return_item/eligibility_validator/no_reimbursements_spec.rb +85 -0
  165. data/spec/models/spree/return_item/eligibility_validator/order_completed_spec.rb +32 -0
  166. data/spec/models/spree/return_item/eligibility_validator/rma_required_spec.rb +29 -0
  167. data/spec/models/spree/return_item/eligibility_validator/time_since_purchase_spec.rb +35 -0
  168. data/spec/models/spree/return_item/exchange_variant_eligibility/same_option_value_spec.rb +65 -0
  169. data/spec/models/spree/return_item/exchange_variant_eligibility/same_product_spec.rb +43 -0
  170. data/spec/models/spree/return_item_spec.rb +775 -0
  171. data/spec/models/spree/returns_calculator_spec.rb +14 -0
  172. data/spec/models/spree/shipment_spec.rb +709 -0
  173. data/spec/models/spree/shipping_calculator_spec.rb +45 -0
  174. data/spec/models/spree/shipping_method_spec.rb +88 -0
  175. data/spec/models/spree/shipping_rate_spec.rb +142 -0
  176. data/spec/models/spree/state_spec.rb +14 -0
  177. data/spec/models/spree/stock/availability_validator_spec.rb +83 -0
  178. data/spec/models/spree/stock/coordinator_spec.rb +116 -0
  179. data/spec/models/spree/stock/differentiator_spec.rb +39 -0
  180. data/spec/models/spree/stock/estimator_spec.rb +146 -0
  181. data/spec/models/spree/stock/inventory_unit_builder_spec.rb +38 -0
  182. data/spec/models/spree/stock/package_spec.rb +163 -0
  183. data/spec/models/spree/stock/packer_spec.rb +91 -0
  184. data/spec/models/spree/stock/prioritizer_spec.rb +125 -0
  185. data/spec/models/spree/stock/quantifier_spec.rb +115 -0
  186. data/spec/models/spree/stock/splitter/backordered_spec.rb +29 -0
  187. data/spec/models/spree/stock/splitter/base_spec.rb +21 -0
  188. data/spec/models/spree/stock/splitter/shipping_category_spec.rb +50 -0
  189. data/spec/models/spree/stock/splitter/weight_spec.rb +29 -0
  190. data/spec/models/spree/stock_item_spec.rb +426 -0
  191. data/spec/models/spree/stock_location_spec.rb +279 -0
  192. data/spec/models/spree/stock_movement_spec.rb +56 -0
  193. data/spec/models/spree/stock_transfer_spec.rb +290 -0
  194. data/spec/models/spree/store_credit_category_spec.rb +17 -0
  195. data/spec/models/spree/store_credit_event_spec.rb +314 -0
  196. data/spec/models/spree/store_credit_spec.rb +876 -0
  197. data/spec/models/spree/store_spec.rb +55 -0
  198. data/spec/models/spree/tax_category_spec.rb +27 -0
  199. data/spec/models/spree/tax_rate_spec.rb +378 -0
  200. data/spec/models/spree/taxon_spec.rb +74 -0
  201. data/spec/models/spree/taxonomy_spec.rb +18 -0
  202. data/spec/models/spree/tracker_spec.rb +21 -0
  203. data/spec/models/spree/transfer_item_spec.rb +264 -0
  204. data/spec/models/spree/unit_cancel_spec.rb +148 -0
  205. data/spec/models/spree/user_spec.rb +223 -0
  206. data/spec/models/spree/validations/db_maximum_length_validator_spec.rb +23 -0
  207. data/spec/models/spree/variant/scopes_spec.rb +55 -0
  208. data/spec/models/spree/variant_spec.rb +546 -0
  209. data/spec/models/spree/zone_spec.rb +305 -0
  210. data/spec/spec_helper.rb +78 -0
  211. data/spec/support/big_decimal.rb +5 -0
  212. data/spec/support/concerns/default_price.rb +34 -0
  213. data/spec/support/dummy_ability.rb +4 -0
  214. data/spec/support/test_gateway.rb +2 -0
  215. metadata +229 -3
  216. data/lib/spree/testing_support/rspec-activemodel-mocks_patch.rb +0 -8
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ module PromotionHandler
5
+ describe Page, :type => :model do
6
+ let(:order) { create(:order_with_line_items, :line_items_count => 1) }
7
+
8
+ let(:promotion) { Promotion.create(name: "10% off", :path => '10off') }
9
+ before do
10
+ calculator = Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10)
11
+ action = Promotion::Actions::CreateItemAdjustments.create(:calculator => calculator)
12
+ promotion.actions << action
13
+ end
14
+
15
+ it "activates at the right path" do
16
+ expect(order.line_item_adjustments.count).to eq(0)
17
+ Spree::PromotionHandler::Page.new(order, '10off').activate
18
+ expect(order.line_item_adjustments.count).to eq(1)
19
+ end
20
+
21
+ context "when promotion is expired" do
22
+ before do
23
+ promotion.update_columns(
24
+ :starts_at => 1.week.ago,
25
+ :expires_at => 1.day.ago
26
+ )
27
+ end
28
+
29
+ it "is not activated" do
30
+ expect(order.line_item_adjustments.count).to eq(0)
31
+ Spree::PromotionHandler::Page.new(order, '10off').activate
32
+ expect(order.line_item_adjustments.count).to eq(0)
33
+ end
34
+ end
35
+
36
+ it "does not activate at the wrong path" do
37
+ expect(order.line_item_adjustments.count).to eq(0)
38
+ Spree::PromotionHandler::Page.new(order, 'wrongpath').activate
39
+ expect(order.line_item_adjustments.count).to eq(0)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ describe Spree::PromotionRule, type: :model do
5
+
6
+ class BadTestRule < Spree::PromotionRule; end
7
+
8
+ class TestRule < Spree::PromotionRule
9
+ def eligible?(promotable, options = {})
10
+ true
11
+ end
12
+ end
13
+
14
+ it "forces developer to implement eligible? method" do
15
+ expect { BadTestRule.new.eligible?("promotable") }.to raise_error NotImplementedError
16
+ end
17
+
18
+ it "validates unique rules for a promotion" do
19
+ p1 = TestRule.new
20
+ p1.promotion_id = 1
21
+ p1.save
22
+
23
+ p2 = TestRule.new
24
+ p2.promotion_id = 1
25
+ expect(p2).not_to be_valid
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,767 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Promotion, :type => :model do
4
+ let(:promotion) { Spree::Promotion.new }
5
+
6
+ describe "validations" do
7
+ before :each do
8
+ @valid_promotion = Spree::Promotion.new :name => "A promotion"
9
+ end
10
+
11
+ it "valid_promotion is valid" do
12
+ expect(@valid_promotion).to be_valid
13
+ end
14
+
15
+ it "validates usage limit" do
16
+ @valid_promotion.usage_limit = -1
17
+ expect(@valid_promotion).not_to be_valid
18
+
19
+ @valid_promotion.usage_limit = 100
20
+ expect(@valid_promotion).to be_valid
21
+ end
22
+
23
+ it "validates name" do
24
+ @valid_promotion.name = nil
25
+ expect(@valid_promotion).not_to be_valid
26
+ end
27
+ end
28
+
29
+ describe ".applied" do
30
+ it "scopes promotions that have been applied to an order only" do
31
+ promotion = Spree::Promotion.create! name: "test"
32
+ expect(Spree::Promotion.applied).to be_empty
33
+
34
+ promotion.orders << create(:order)
35
+ expect(Spree::Promotion.applied.first).to eq promotion
36
+ end
37
+ end
38
+
39
+ describe ".advertised" do
40
+ let(:promotion) { create(:promotion) }
41
+ let(:advertised_promotion) { create(:promotion, :advertise => true) }
42
+
43
+ it "only shows advertised promotions" do
44
+ advertised = Spree::Promotion.advertised
45
+ expect(advertised).to include(advertised_promotion)
46
+ expect(advertised).not_to include(promotion)
47
+ end
48
+ end
49
+
50
+ describe "#destroy" do
51
+ let(:promotion) { Spree::Promotion.create(:name => "delete me") }
52
+
53
+ before(:each) do
54
+ promotion.actions << Spree::Promotion::Actions::CreateAdjustment.new
55
+ promotion.rules << Spree::Promotion::Rules::FirstOrder.new
56
+ promotion.save!
57
+ promotion.destroy
58
+ end
59
+
60
+ it "should delete actions" do
61
+ expect(Spree::PromotionAction.count).to eq(0)
62
+ end
63
+
64
+ it "should delete rules" do
65
+ expect(Spree::PromotionRule.count).to eq(0)
66
+ end
67
+ end
68
+
69
+ describe "#save" do
70
+ let(:promotion) { Spree::Promotion.create(:name => "delete me") }
71
+
72
+ before(:each) do
73
+ promotion.actions << Spree::Promotion::Actions::CreateAdjustment.new
74
+ promotion.rules << Spree::Promotion::Rules::FirstOrder.new
75
+ promotion.save!
76
+ end
77
+
78
+ it "should deeply autosave records and preferences" do
79
+ promotion.actions[0].calculator.preferred_flat_percent = 10
80
+ promotion.save!
81
+ expect(Spree::Calculator.first.preferred_flat_percent).to eq(10)
82
+ end
83
+ end
84
+
85
+ describe "#activate" do
86
+ let(:promotion) { create(:promotion) }
87
+
88
+ before do
89
+ @action1 = Spree::Promotion::Actions::CreateAdjustment.create!
90
+ @action2 = Spree::Promotion::Actions::CreateAdjustment.create!
91
+ allow(@action1).to receive_messages perform: true
92
+ allow(@action2).to receive_messages perform: true
93
+
94
+ promotion.promotion_actions = [@action1, @action2]
95
+ promotion.created_at = 2.days.ago
96
+
97
+ @user = create(:user)
98
+ @order = create(:order, user: @user, created_at: DateTime.now)
99
+ @payload = { :order => @order, :user => @user }
100
+ end
101
+
102
+ it "should check path if present" do
103
+ promotion.path = 'content/cvv'
104
+ @payload[:path] = 'content/cvv'
105
+ expect(@action1).to receive(:perform).with(hash_including(@payload))
106
+ expect(@action2).to receive(:perform).with(hash_including(@payload))
107
+ promotion.activate(@payload)
108
+ end
109
+
110
+ it "does not perform actions against an order in a finalized state" do
111
+ expect(@action1).not_to receive(:perform)
112
+
113
+ @order.state = 'complete'
114
+ promotion.activate(@payload)
115
+
116
+ @order.state = 'awaiting_return'
117
+ promotion.activate(@payload)
118
+
119
+ @order.state = 'returned'
120
+ promotion.activate(@payload)
121
+ end
122
+
123
+ it "does activate if newer then order" do
124
+ expect(@action1).to receive(:perform).with(hash_including(@payload))
125
+ promotion.created_at = DateTime.now + 2
126
+ expect(promotion.activate(@payload)).to be true
127
+ end
128
+
129
+ context "keeps track of the orders" do
130
+ context "when activated" do
131
+ it "assigns the order" do
132
+ expect(promotion.orders).to be_empty
133
+ expect(promotion.activate(@payload)).to be true
134
+ expect(promotion.orders.first).to eql @order
135
+ end
136
+ end
137
+ context "when not activated" do
138
+ it "will not assign the order" do
139
+ @order.state = 'complete'
140
+ expect(promotion.orders).to be_empty
141
+ expect(promotion.activate(@payload)).to be_falsey
142
+ expect(promotion.orders).to be_empty
143
+ end
144
+ end
145
+ context "when the order is already associated" do
146
+ before do
147
+ expect(promotion.orders).to be_empty
148
+ expect(promotion.activate(@payload)).to be true
149
+ expect(promotion.orders.to_a).to eql [@order]
150
+ end
151
+
152
+ it "will not assign the order again" do
153
+ expect(promotion.activate(@payload)).to be true
154
+ expect(promotion.orders.reload.to_a).to eql [@order]
155
+ end
156
+ end
157
+
158
+ end
159
+
160
+ context "when there is a code" do
161
+ let(:promotion_code) { create(:promotion_code) }
162
+ let(:promotion) { promotion_code.promotion }
163
+
164
+ it "assigns the code" do
165
+ expect(promotion.activate(order: @order, promotion_code: promotion_code)).to be true
166
+ expect(promotion.order_promotions.map(&:promotion_code)).to eq [promotion_code]
167
+ end
168
+ end
169
+ end
170
+
171
+ describe "#usage_limit_exceeded?" do
172
+ subject { promotion.usage_limit_exceeded? }
173
+
174
+ shared_examples "it should" do
175
+ context "when there is a usage limit" do
176
+ context "and the limit is not exceeded" do
177
+ let(:usage_limit) { 10 }
178
+ it { is_expected.to be_falsy }
179
+ end
180
+ context "and the limit is exceeded" do
181
+ let(:usage_limit) { 1 }
182
+ context "on a different order" do
183
+ before do
184
+ FactoryGirl.create(
185
+ :completed_order_with_promotion,
186
+ promotion: promotion
187
+ )
188
+ promotion.actions.first.adjustments.update_all(eligible: true)
189
+ end
190
+ it { is_expected.to be_truthy }
191
+ end
192
+ context "on the same order" do
193
+ it { is_expected.to be_falsy }
194
+ end
195
+ end
196
+ end
197
+ context "when there is no usage limit" do
198
+ let(:usage_limit) { nil }
199
+ it { is_expected.to be_falsy }
200
+ end
201
+ end
202
+
203
+ context "with an order-level adjustment" do
204
+ let(:promotion) do
205
+ FactoryGirl.create(
206
+ :promotion,
207
+ :with_order_adjustment,
208
+ code: "discount",
209
+ usage_limit: usage_limit
210
+ )
211
+ end
212
+ let(:promotable) do
213
+ FactoryGirl.create(
214
+ :completed_order_with_promotion,
215
+ promotion: promotion
216
+ )
217
+ end
218
+ it_behaves_like "it should"
219
+ end
220
+
221
+ context "with an item-level adjustment" do
222
+ let(:promotion) do
223
+ FactoryGirl.create(
224
+ :promotion,
225
+ :with_line_item_adjustment,
226
+ code: "discount",
227
+ usage_limit: usage_limit
228
+ )
229
+ end
230
+ before do
231
+ promotion.actions.first.perform({
232
+ order: order,
233
+ promotion: promotion,
234
+ promotion_code: promotion.codes.first
235
+ })
236
+ end
237
+ context "when there are multiple line items" do
238
+ let(:order) { FactoryGirl.create(:order_with_line_items, line_items_count: 2) }
239
+ describe "the first item" do
240
+ let(:promotable) { order.line_items.first }
241
+ it_behaves_like "it should"
242
+ end
243
+ describe "the second item" do
244
+ let(:promotable) { order.line_items.last }
245
+ it_behaves_like "it should"
246
+ end
247
+ end
248
+ context "when there is a single line item" do
249
+ let(:order) { FactoryGirl.create(:order_with_line_items) }
250
+ let(:promotable) { order.line_items.first }
251
+ it_behaves_like "it should"
252
+ end
253
+ end
254
+ end
255
+
256
+ describe "#usage_count" do
257
+ let(:promotion) do
258
+ FactoryGirl.create(
259
+ :promotion,
260
+ :with_order_adjustment,
261
+ code: "discount"
262
+ )
263
+ end
264
+
265
+ subject { promotion.usage_count }
266
+
267
+ context "when the code is applied to a non-complete order" do
268
+ let(:order) { FactoryGirl.create(:order_with_line_items) }
269
+ before { promotion.activate(order: order, promotion_code: promotion.codes.first) }
270
+ it { is_expected.to eq 0 }
271
+ end
272
+ context "when the code is applied to a complete order" do
273
+ let!(:order) do
274
+ FactoryGirl.create(
275
+ :completed_order_with_promotion,
276
+ promotion: promotion
277
+ )
278
+ end
279
+ context "and the promo is eligible" do
280
+ it { is_expected.to eq 1 }
281
+ end
282
+ context "and the promo is ineligible" do
283
+ before { order.adjustments.promotion.update_all(eligible: false) }
284
+ it { is_expected.to eq 0 }
285
+ end
286
+ end
287
+ end
288
+
289
+ context "#expired" do
290
+ it "should not be exipired" do
291
+ expect(promotion).not_to be_expired
292
+ end
293
+
294
+ it "should be expired if it hasn't started yet" do
295
+ promotion.starts_at = Time.now + 1.day
296
+ expect(promotion).to be_expired
297
+ end
298
+
299
+ it "should be expired if it has already ended" do
300
+ promotion.expires_at = Time.now - 1.day
301
+ expect(promotion).to be_expired
302
+ end
303
+
304
+ it "should not be expired if it has started already" do
305
+ promotion.starts_at = Time.now - 1.day
306
+ expect(promotion).not_to be_expired
307
+ end
308
+
309
+ it "should not be expired if it has not ended yet" do
310
+ promotion.expires_at = Time.now + 1.day
311
+ expect(promotion).not_to be_expired
312
+ end
313
+
314
+ it "should not be expired if current time is within starts_at and expires_at range" do
315
+ promotion.starts_at = Time.now - 1.day
316
+ promotion.expires_at = Time.now + 1.day
317
+ expect(promotion).not_to be_expired
318
+ end
319
+ end
320
+
321
+ context "#active" do
322
+ it "should be active" do
323
+ expect(promotion.active?).to eq(true)
324
+ end
325
+
326
+ it "should not be active if it hasn't started yet" do
327
+ promotion.starts_at = Time.now + 1.day
328
+ expect(promotion.active?).to eq(false)
329
+ end
330
+
331
+ it "should not be active if it has already ended" do
332
+ promotion.expires_at = Time.now - 1.day
333
+ expect(promotion.active?).to eq(false)
334
+ end
335
+
336
+ it "should be active if it has started already" do
337
+ promotion.starts_at = Time.now - 1.day
338
+ expect(promotion.active?).to eq(true)
339
+ end
340
+
341
+ it "should be active if it has not ended yet" do
342
+ promotion.expires_at = Time.now + 1.day
343
+ expect(promotion.active?).to eq(true)
344
+ end
345
+
346
+ it "should be active if current time is within starts_at and expires_at range" do
347
+ promotion.starts_at = Time.now - 1.day
348
+ promotion.expires_at = Time.now + 1.day
349
+ expect(promotion.active?).to eq(true)
350
+ end
351
+
352
+ it "should be active if there are no start and end times set" do
353
+ promotion.starts_at = nil
354
+ promotion.expires_at = nil
355
+ expect(promotion.active?).to eq(true)
356
+ end
357
+ end
358
+
359
+ context "#usage_count" do
360
+ let!(:promotion) do
361
+ create(
362
+ :promotion,
363
+ name: "Foo",
364
+ code: "XXX",
365
+ )
366
+ end
367
+
368
+ let!(:action) do
369
+ calculator = Spree::Calculator::FlatRate.new
370
+ action_params = { :promotion => promotion, :calculator => calculator }
371
+ action = Spree::Promotion::Actions::CreateAdjustment.create(action_params)
372
+ promotion.actions << action
373
+ action
374
+ end
375
+
376
+ let!(:adjustment) do
377
+ order = create(:order)
378
+ Spree::Adjustment.create!(
379
+ order: order,
380
+ adjustable: order,
381
+ source: action,
382
+ promotion_code: promotion.codes.first,
383
+ amount: 10,
384
+ label: 'Promotional adjustment'
385
+ )
386
+ end
387
+
388
+ it "counts eligible adjustments" do
389
+ adjustment.update_column(:eligible, true)
390
+ expect(promotion.usage_count).to eq(0)
391
+ end
392
+
393
+ # Regression test for #4112
394
+ it "does not count ineligible adjustments" do
395
+ adjustment.update_column(:eligible, false)
396
+ expect(promotion.usage_count).to eq(0)
397
+ end
398
+ end
399
+
400
+ context "#products" do
401
+ let(:promotion) { create(:promotion) }
402
+
403
+ context "when it has product rules with products associated" do
404
+ let(:promotion_rule) { Spree::Promotion::Rules::Product.new }
405
+
406
+ before do
407
+ promotion_rule.promotion = promotion
408
+ promotion_rule.products << create(:product)
409
+ promotion_rule.save
410
+ end
411
+
412
+ it "should have products" do
413
+ expect(promotion.reload.products.size).to eq(1)
414
+ end
415
+ end
416
+
417
+ context "when there's no product rule associated" do
418
+ it "should not have products but still return an empty array" do
419
+ expect(promotion.products).to be_blank
420
+ end
421
+ end
422
+ end
423
+
424
+ context "#eligible?" do
425
+ subject do
426
+ promotion.eligible?(promotable)
427
+ end
428
+
429
+ let(:promotable) { create :order }
430
+
431
+ it { is_expected.to be true }
432
+
433
+ context "when promotion is expired" do
434
+ before { promotion.expires_at = Time.now - 10.days }
435
+ it { is_expected.to be false }
436
+ end
437
+
438
+ context "when the promotion's usage limit is exceeded" do
439
+ let(:order) { FactoryGirl.create(:completed_order_with_promotion, promotion: promotion) }
440
+ let(:promotion) { FactoryGirl.create(:promotion, :with_order_adjustment) }
441
+
442
+ before do
443
+ FactoryGirl.create(
444
+ :completed_order_with_promotion,
445
+ promotion: promotion
446
+ )
447
+ promotion.usage_limit = 1
448
+ end
449
+
450
+ it "returns false" do
451
+ expect(promotion.eligible?(promotable)).to eq(false)
452
+ end
453
+ end
454
+
455
+ context "when the promotion code's usage limit is exceeded" do
456
+ let(:order) { FactoryGirl.create(:completed_order_with_promotion, promotion: promotion) }
457
+ let(:promotion) { create(:promotion, :with_order_adjustment, code: 'abc123', per_code_usage_limit: 1) }
458
+ let(:promotion_code) { promotion.codes.first }
459
+
460
+ before do
461
+ FactoryGirl.create(
462
+ :completed_order_with_promotion,
463
+ promotion: promotion
464
+ )
465
+ promotion_code.adjustments.update_all(eligible: true)
466
+ end
467
+
468
+ it "returns false" do
469
+ expect(promotion.eligible?(promotable, promotion_code: promotion_code)).to eq(false)
470
+ end
471
+ end
472
+
473
+ context "when promotable is a Spree::LineItem" do
474
+ let(:promotable) { create :line_item }
475
+ let(:product) { promotable.product }
476
+
477
+ before do
478
+ product.promotionable = promotionable
479
+ end
480
+
481
+ context "and product is promotionable" do
482
+ let(:promotionable) { true }
483
+ it { is_expected.to be true }
484
+ end
485
+
486
+ context "and product is not promotionable" do
487
+ let(:promotionable) { false }
488
+ it { is_expected.to be false }
489
+ end
490
+ end
491
+
492
+ context "when promotable is a Spree::Order" do
493
+ let(:promotable) { create :order }
494
+
495
+ context "and it is empty" do
496
+ it { is_expected.to be true }
497
+ end
498
+
499
+ context "and it contains items" do
500
+ let!(:line_item) { create(:line_item, order: promotable) }
501
+ let!(:line_item2) { create(:line_item, order: promotable) }
502
+
503
+ context "and at least one item is non-promotionable" do
504
+ before do
505
+ line_item.product.update_column(:promotionable, false)
506
+ end
507
+ it { is_expected.to be false }
508
+ end
509
+
510
+ context "and the items are all non-promotionable" do
511
+ before do
512
+ line_item.product.update_column(:promotionable, false)
513
+ line_item2.product.update_column(:promotionable, false)
514
+ end
515
+ it { is_expected.to be false }
516
+ end
517
+
518
+ context "and at least one item is promotionable" do
519
+ it { is_expected.to be true }
520
+ end
521
+ end
522
+ end
523
+ end
524
+
525
+ context "#eligible_rules" do
526
+ let(:promotable) { double('Promotable') }
527
+ it "true if there are no rules" do
528
+ expect(promotion.eligible_rules(promotable)).to eq []
529
+ end
530
+
531
+ it "true if there are no applicable rules" do
532
+ promotion.promotion_rules = [stub_model(Spree::PromotionRule, :eligible? => true, :applicable? => false)]
533
+ allow(promotion.promotion_rules).to receive(:for).and_return([])
534
+ expect(promotion.eligible_rules(promotable)).to eq []
535
+ end
536
+
537
+ context "with 'all' match policy" do
538
+ let(:rule1) { Spree::PromotionRule.create!(promotion: promotion) }
539
+ let(:rule2) { Spree::PromotionRule.create!(promotion: promotion) }
540
+
541
+ before { promotion.match_policy = 'all' }
542
+
543
+ context "when all rules are eligible" do
544
+ before do
545
+ allow(rule1).to receive_messages(eligible?: true, applicable?: true)
546
+ allow(rule2).to receive_messages(eligible?: true, applicable?: true)
547
+
548
+ promotion.promotion_rules = [rule1, rule2]
549
+ allow(promotion.promotion_rules).to receive(:for).and_return(promotion.promotion_rules)
550
+ end
551
+ it "returns the eligible rules" do
552
+ expect(promotion.eligible_rules(promotable)).to eq [rule1, rule2]
553
+ end
554
+ it "does set anything to eligiblity errors" do
555
+ promotion.eligible_rules(promotable)
556
+ expect(promotion.eligibility_errors).to be_nil
557
+ end
558
+ end
559
+
560
+ context "when any of the rules is not eligible" do
561
+ let(:errors) { double ActiveModel::Errors, empty?: false }
562
+ before do
563
+ allow(rule1).to receive_messages(eligible?: true, applicable?: true, eligibility_errors: nil)
564
+ allow(rule2).to receive_messages(eligible?: false, applicable?: true, eligibility_errors: errors)
565
+
566
+ promotion.promotion_rules = [rule1, rule2]
567
+ allow(promotion.promotion_rules).to receive(:for).and_return(promotion.promotion_rules)
568
+ end
569
+ it "returns nil" do
570
+ expect(promotion.eligible_rules(promotable)).to be_nil
571
+ end
572
+ it "sets eligibility errors to the first non-nil one" do
573
+ promotion.eligible_rules(promotable)
574
+ expect(promotion.eligibility_errors).to eq errors
575
+ end
576
+ end
577
+ end
578
+
579
+ context "with 'any' match policy" do
580
+ let(:promotion) { Spree::Promotion.create(:name => "Promo", :match_policy => 'any') }
581
+ let(:promotable) { double('Promotable') }
582
+
583
+ it "should have eligible rules if any of the rules are eligible" do
584
+ allow_any_instance_of(Spree::PromotionRule).to receive_messages(:applicable? => true)
585
+ true_rule = Spree::PromotionRule.create(:promotion => promotion)
586
+ allow(true_rule).to receive_messages(:eligible? => true)
587
+ allow(promotion).to receive_messages(:rules => [true_rule])
588
+ allow(promotion).to receive_message_chain(:rules, :for).and_return([true_rule])
589
+ expect(promotion.eligible_rules(promotable)).to eq [true_rule]
590
+ end
591
+
592
+ context "when none of the rules are eligible" do
593
+ let(:rule) { Spree::PromotionRule.create!(promotion: promotion) }
594
+ let(:errors) { double ActiveModel::Errors, empty?: false }
595
+ before do
596
+ allow(rule).to receive_messages(eligible?: false, applicable?: true, eligibility_errors: errors)
597
+
598
+ promotion.promotion_rules = [rule]
599
+ allow(promotion.promotion_rules).to receive(:for).and_return(promotion.promotion_rules)
600
+ end
601
+ it "returns nil" do
602
+ expect(promotion.eligible_rules(promotable)).to be_nil
603
+ end
604
+ it "sets eligibility errors to the first non-nil one" do
605
+ promotion.eligible_rules(promotable)
606
+ expect(promotion.eligibility_errors).to eq errors
607
+ end
608
+ end
609
+ end
610
+ end
611
+
612
+ describe '#line_item_actionable?' do
613
+ let(:order) { double Spree::Order }
614
+ let(:line_item) { double Spree::LineItem}
615
+ let(:true_rule) { double Spree::PromotionRule, eligible?: true, applicable?: true, actionable?: true }
616
+ let(:false_rule) { double Spree::PromotionRule, eligible?: true, applicable?: true, actionable?: false }
617
+ let(:rules) { [] }
618
+
619
+ before do
620
+ allow(promotion).to receive(:rules) { rules }
621
+ allow(rules).to receive(:for) { rules }
622
+ end
623
+
624
+ subject { promotion.line_item_actionable? order, line_item }
625
+
626
+ context 'when the order is eligible for promotion' do
627
+ context 'when there are no rules' do
628
+ it { is_expected.to be }
629
+ end
630
+
631
+ context 'when there are rules' do
632
+ context 'when the match policy is all' do
633
+ before { promotion.match_policy = 'all' }
634
+
635
+ context 'when all rules allow action on the line item' do
636
+ let(:rules) { [true_rule] }
637
+ it { is_expected.to be}
638
+ end
639
+
640
+ context 'when at least one rule does not allow action on the line item' do
641
+ let(:rules) { [true_rule, false_rule] }
642
+ it { is_expected.not_to be}
643
+ end
644
+ end
645
+
646
+ context 'when the match policy is any' do
647
+ before { promotion.match_policy = 'any' }
648
+
649
+ context 'when at least one rule allows action on the line item' do
650
+ let(:rules) { [true_rule, false_rule] }
651
+ it { is_expected.to be }
652
+ end
653
+
654
+ context 'when no rules allow action on the line item' do
655
+ let(:rules) { [false_rule] }
656
+ it { is_expected.not_to be}
657
+ end
658
+ end
659
+ end
660
+ end
661
+
662
+ context 'when the order is not eligible for the promotion' do
663
+ context "due to promotion expiration" do
664
+ before { promotion.starts_at = Time.current + 2.days }
665
+ it { is_expected.not_to be }
666
+ end
667
+
668
+ context "due to promotion code not being eligible" do
669
+ let(:order) { create(:order) }
670
+ let(:promotion) { create(:promotion, per_code_usage_limit: 0) }
671
+ let(:promotion_code) { create(:promotion_code, promotion: promotion) }
672
+
673
+ subject { promotion.line_item_actionable? order, line_item, promotion_code: promotion_code }
674
+
675
+ it "returns false" do
676
+ expect(subject).to eq false
677
+ end
678
+ end
679
+ end
680
+ end
681
+
682
+ # regression for #4059
683
+ # admin form posts the code and path as empty string
684
+ describe "normalize blank values for path" do
685
+ it "will save blank value as nil value instead" do
686
+ promotion = Spree::Promotion.create(:name => "A promotion", :path => "")
687
+ expect(promotion.path).to be_nil
688
+ end
689
+ end
690
+
691
+ describe '#used_by?' do
692
+ subject { promotion.used_by? user, [excluded_order] }
693
+
694
+ let(:promotion) { create :promotion, :with_order_adjustment }
695
+ let(:user) { create :user }
696
+ let(:order) { create :order_with_line_items, user: user }
697
+ let(:excluded_order) { create :order_with_line_items, user: user }
698
+
699
+ before do
700
+ order.user_id = user.id
701
+ order.save!
702
+ end
703
+
704
+ context 'when the user has used this promo' do
705
+ before do
706
+ promotion.activate(order: order)
707
+ order.update!
708
+ order.completed_at = Time.now
709
+ order.save!
710
+ end
711
+
712
+ context 'when the order is complete' do
713
+ it { is_expected.to be true }
714
+
715
+ context 'when the promotion was not eligible' do
716
+ let(:adjustment) { order.adjustments.first }
717
+
718
+ before do
719
+ adjustment.eligible = false
720
+ adjustment.save!
721
+ end
722
+
723
+ it { is_expected.to be false }
724
+ end
725
+
726
+ context 'when the only matching order is the excluded order' do
727
+ let(:excluded_order) { order }
728
+ it { is_expected.to be false }
729
+ end
730
+ end
731
+
732
+ context 'when the order is not complete' do
733
+ let(:order) { create :order, user: user }
734
+ it { is_expected.to be false }
735
+ end
736
+ end
737
+
738
+ context 'when the user has not used this promo' do
739
+ it { is_expected.to be false }
740
+ end
741
+ end
742
+
743
+ describe "adding items to the cart" do
744
+ let(:order) { create :order }
745
+ let(:line_item) { create :line_item, order: order }
746
+ let(:promo) { create :promotion_with_item_adjustment, adjustment_rate: 5, code: 'promo' }
747
+ let(:promotion_code) { promo.codes.first }
748
+ let(:variant) { create :variant }
749
+
750
+ it "updates the promotions for new line items" do
751
+ expect(line_item.adjustments).to be_empty
752
+ expect(order.adjustment_total).to eq 0
753
+
754
+ promo.activate order: order, promotion_code: promotion_code
755
+ order.update!
756
+
757
+ expect(line_item.adjustments.size).to eq(1)
758
+ expect(order.adjustment_total).to eq -5
759
+
760
+ other_line_item = order.contents.add(variant, 1, currency: order.currency)
761
+
762
+ expect(other_line_item).not_to eq line_item
763
+ expect(other_line_item.adjustments.size).to eq(1)
764
+ expect(order.adjustment_total).to eq -10
765
+ end
766
+ end
767
+ end