solidus_core 1.1.0 → 1.1.1

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