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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -0
- data/Gemfile +3 -0
- data/Rakefile +16 -0
- data/script/rails +9 -0
- data/solidus_core.gemspec +48 -0
- data/spec/fixtures/thinking-cat.jpg +0 -0
- data/spec/helpers/base_helper_spec.rb +173 -0
- data/spec/helpers/order_helper_spec.rb +12 -0
- data/spec/helpers/products_helper_spec.rb +220 -0
- data/spec/helpers/taxons_helper_spec.rb +17 -0
- data/spec/lib/calculated_adjustments_spec.rb +7 -0
- data/spec/lib/i18n_spec.rb +123 -0
- data/spec/lib/search/base_spec.rb +86 -0
- data/spec/lib/search/variant_spec.rb +92 -0
- data/spec/lib/spree/core/controller_helpers/auth_spec.rb +66 -0
- data/spec/lib/spree/core/controller_helpers/order_spec.rb +92 -0
- data/spec/lib/spree/core/controller_helpers/search_spec.rb +17 -0
- data/spec/lib/spree/core/controller_helpers/store_spec.rb +16 -0
- data/spec/lib/spree/core/controller_helpers/strong_parameters_spec.rb +39 -0
- data/spec/lib/spree/core/current_store_spec.rb +36 -0
- data/spec/lib/spree/core/delegate_belongs_to_spec.rb +22 -0
- data/spec/lib/spree/core/importer/order_spec.rb +431 -0
- data/spec/lib/spree/core/role_configuration_spec.rb +138 -0
- data/spec/lib/spree/core/validators/email_spec.rb +48 -0
- data/spec/lib/spree/localized_number_spec.rb +38 -0
- data/spec/lib/spree/migrations_spec.rb +36 -0
- data/spec/lib/spree/money_spec.rb +127 -0
- data/spec/lib/tasks/exchanges_spec.rb +231 -0
- data/spec/lib/tasks/migrations/copy_shipped_shipments_to_cartons_spec.rb +115 -0
- data/spec/lib/tasks/order_capturing_spec.rb +56 -0
- data/spec/mailers/carton_mailer_spec.rb +43 -0
- data/spec/mailers/order_mailer_spec.rb +122 -0
- data/spec/mailers/reimbursement_mailer_spec.rb +40 -0
- data/spec/mailers/test_mailer_spec.rb +15 -0
- data/spec/models/spree/ability_spec.rb +276 -0
- data/spec/models/spree/address_spec.rb +250 -0
- data/spec/models/spree/adjustment_reason_spec.rb +13 -0
- data/spec/models/spree/adjustment_spec.rb +177 -0
- data/spec/models/spree/app_configuration_spec.rb +20 -0
- data/spec/models/spree/asset_spec.rb +24 -0
- data/spec/models/spree/calculator/default_tax_spec.rb +127 -0
- data/spec/models/spree/calculator/flat_percent_item_total_spec.rb +25 -0
- data/spec/models/spree/calculator/flat_rate_spec.rb +47 -0
- data/spec/models/spree/calculator/flexi_rate_spec.rb +41 -0
- data/spec/models/spree/calculator/percent_on_line_item_spec.rb +15 -0
- data/spec/models/spree/calculator/price_sack_spec.rb +30 -0
- data/spec/models/spree/calculator/refunds/default_refund_amount_spec.rb +51 -0
- data/spec/models/spree/calculator/shipping/flat_percent_item_total_spec.rb +23 -0
- data/spec/models/spree/calculator/shipping/flat_rate_spec.rb +13 -0
- data/spec/models/spree/calculator/shipping/flexi_rate_spec.rb +52 -0
- data/spec/models/spree/calculator/shipping/per_item_spec.rb +20 -0
- data/spec/models/spree/calculator/shipping/price_sack_spec.rb +30 -0
- data/spec/models/spree/calculator/tiered_flat_rate_spec.rb +36 -0
- data/spec/models/spree/calculator/tiered_percent_spec.rb +47 -0
- data/spec/models/spree/calculator_spec.rb +36 -0
- data/spec/models/spree/carton_spec.rb +133 -0
- data/spec/models/spree/classification_spec.rb +15 -0
- data/spec/models/spree/concerns/display_money_spec.rb +43 -0
- data/spec/models/spree/concerns/user_methods_spec.rb +41 -0
- data/spec/models/spree/credit_card_spec.rb +334 -0
- data/spec/models/spree/customer_return_spec.rb +276 -0
- data/spec/models/spree/exchange_spec.rb +79 -0
- data/spec/models/spree/gateway/bogus_simple.rb +20 -0
- data/spec/models/spree/gateway/bogus_spec.rb +13 -0
- data/spec/models/spree/gateway_spec.rb +82 -0
- data/spec/models/spree/inventory_unit_spec.rb +307 -0
- data/spec/models/spree/item_adjustments_spec.rb +256 -0
- data/spec/models/spree/line_item_spec.rb +191 -0
- data/spec/models/spree/option_type_spec.rb +14 -0
- data/spec/models/spree/option_value_spec.rb +22 -0
- data/spec/models/spree/order/address_spec.rb +50 -0
- data/spec/models/spree/order/adjustments_spec.rb +39 -0
- data/spec/models/spree/order/callbacks_spec.rb +42 -0
- data/spec/models/spree/order/checkout_spec.rb +902 -0
- data/spec/models/spree/order/currency_updater_spec.rb +32 -0
- data/spec/models/spree/order/finalizing_spec.rb +111 -0
- data/spec/models/spree/order/payment_spec.rb +210 -0
- data/spec/models/spree/order/risk_assessment_spec.rb +68 -0
- data/spec/models/spree/order/state_machine_spec.rb +221 -0
- data/spec/models/spree/order/tax_spec.rb +84 -0
- data/spec/models/spree/order/totals_spec.rb +24 -0
- data/spec/models/spree/order/updating_spec.rb +18 -0
- data/spec/models/spree/order/validations_spec.rb +15 -0
- data/spec/models/spree/order_cancellations_spec.rb +120 -0
- data/spec/models/spree/order_capturing_spec.rb +116 -0
- data/spec/models/spree/order_contents_spec.rb +265 -0
- data/spec/models/spree/order_inventory_spec.rb +228 -0
- data/spec/models/spree/order_mutex_spec.rb +85 -0
- data/spec/models/spree/order_promotion_spec.rb +31 -0
- data/spec/models/spree/order_shipping_spec.rb +247 -0
- data/spec/models/spree/order_spec.rb +1412 -0
- data/spec/models/spree/order_stock_location_spec.rb +18 -0
- data/spec/models/spree/order_updater_spec.rb +299 -0
- data/spec/models/spree/payment_method/store_credit_spec.rb +294 -0
- data/spec/models/spree/payment_method_spec.rb +96 -0
- data/spec/models/spree/payment_spec.rb +1044 -0
- data/spec/models/spree/permission_sets/base_spec.rb +12 -0
- data/spec/models/spree/permission_sets/configuration_display.rb +82 -0
- data/spec/models/spree/permission_sets/configuration_management_spec.rb +50 -0
- data/spec/models/spree/permission_sets/dashboard_display_spec.rb +22 -0
- data/spec/models/spree/permission_sets/order_display_spec.rb +49 -0
- data/spec/models/spree/permission_sets/order_management_spec.rb +36 -0
- data/spec/models/spree/permission_sets/product_display_spec.rb +60 -0
- data/spec/models/spree/permission_sets/product_management_spec.rb +40 -0
- data/spec/models/spree/permission_sets/promotion_display_spec.rb +34 -0
- data/spec/models/spree/permission_sets/promotion_management_spec.rb +26 -0
- data/spec/models/spree/permission_sets/report_display_spec.rb +24 -0
- data/spec/models/spree/permission_sets/restricted_transfer_management_spec.rb +132 -0
- data/spec/models/spree/permission_sets/stock_display_spec.rb +26 -0
- data/spec/models/spree/permission_sets/stock_management_spec.rb +24 -0
- data/spec/models/spree/permission_sets/user_display_spec.rb +36 -0
- data/spec/models/spree/permission_sets/user_management_spec.rb +28 -0
- data/spec/models/spree/preference_spec.rb +80 -0
- data/spec/models/spree/preferences/configuration_spec.rb +30 -0
- data/spec/models/spree/preferences/preferable_spec.rb +294 -0
- data/spec/models/spree/preferences/scoped_store_spec.rb +58 -0
- data/spec/models/spree/preferences/static_model_preferences_spec.rb +78 -0
- data/spec/models/spree/preferences/statically_configurable_spec.rb +60 -0
- data/spec/models/spree/preferences/store_spec.rb +39 -0
- data/spec/models/spree/price_spec.rb +42 -0
- data/spec/models/spree/product/scopes_spec.rb +148 -0
- data/spec/models/spree/product_duplicator_spec.rb +103 -0
- data/spec/models/spree/product_filter_spec.rb +26 -0
- data/spec/models/spree/product_property_spec.rb +20 -0
- data/spec/models/spree/product_spec.rb +437 -0
- data/spec/models/spree/promotion/actions/create_adjustment_spec.rb +96 -0
- data/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb +165 -0
- data/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb +115 -0
- data/spec/models/spree/promotion/actions/free_shipping_spec.rb +40 -0
- data/spec/models/spree/promotion/rules/first_order_spec.rb +75 -0
- data/spec/models/spree/promotion/rules/item_total_spec.rb +67 -0
- data/spec/models/spree/promotion/rules/nth_order_spec.rb +70 -0
- data/spec/models/spree/promotion/rules/one_use_per_user_spec.rb +42 -0
- data/spec/models/spree/promotion/rules/option_value_spec.rb +94 -0
- data/spec/models/spree/promotion/rules/product_spec.rb +143 -0
- data/spec/models/spree/promotion/rules/taxon_spec.rb +102 -0
- data/spec/models/spree/promotion/rules/user_logged_in_spec.rb +27 -0
- data/spec/models/spree/promotion/rules/user_spec.rb +37 -0
- data/spec/models/spree/promotion_builder_spec.rb +118 -0
- data/spec/models/spree/promotion_category_spec.rb +17 -0
- data/spec/models/spree/promotion_code/code_builder_spec.rb +79 -0
- data/spec/models/spree/promotion_code_spec.rb +187 -0
- data/spec/models/spree/promotion_handler/cart_spec.rb +114 -0
- data/spec/models/spree/promotion_handler/coupon_spec.rb +335 -0
- data/spec/models/spree/promotion_handler/free_shipping_spec.rb +47 -0
- data/spec/models/spree/promotion_handler/page_spec.rb +44 -0
- data/spec/models/spree/promotion_rule_spec.rb +28 -0
- data/spec/models/spree/promotion_spec.rb +767 -0
- data/spec/models/spree/refund_spec.rb +204 -0
- data/spec/models/spree/reimbursement/credit_spec.rb +36 -0
- data/spec/models/spree/reimbursement/reimbursement_type_engine_spec.rb +140 -0
- data/spec/models/spree/reimbursement/reimbursement_type_validator_spec.rb +83 -0
- data/spec/models/spree/reimbursement_performer_spec.rb +30 -0
- data/spec/models/spree/reimbursement_spec.rb +231 -0
- data/spec/models/spree/reimbursement_tax_calculator_spec.rb +51 -0
- data/spec/models/spree/reimbursement_type/credit_spec.rb +53 -0
- data/spec/models/spree/reimbursement_type/exchange_spec.rb +46 -0
- data/spec/models/spree/reimbursement_type/original_payment_spec.rb +107 -0
- data/spec/models/spree/reimbursement_type/store_credit_spec.rb +97 -0
- data/spec/models/spree/return_authorization_spec.rb +290 -0
- data/spec/models/spree/return_item/eligibility_validator/default_spec.rb +77 -0
- data/spec/models/spree/return_item/eligibility_validator/inventory_shipped_spec.rb +58 -0
- data/spec/models/spree/return_item/eligibility_validator/no_reimbursements_spec.rb +85 -0
- data/spec/models/spree/return_item/eligibility_validator/order_completed_spec.rb +32 -0
- data/spec/models/spree/return_item/eligibility_validator/rma_required_spec.rb +29 -0
- data/spec/models/spree/return_item/eligibility_validator/time_since_purchase_spec.rb +35 -0
- data/spec/models/spree/return_item/exchange_variant_eligibility/same_option_value_spec.rb +65 -0
- data/spec/models/spree/return_item/exchange_variant_eligibility/same_product_spec.rb +43 -0
- data/spec/models/spree/return_item_spec.rb +775 -0
- data/spec/models/spree/returns_calculator_spec.rb +14 -0
- data/spec/models/spree/shipment_spec.rb +709 -0
- data/spec/models/spree/shipping_calculator_spec.rb +45 -0
- data/spec/models/spree/shipping_method_spec.rb +88 -0
- data/spec/models/spree/shipping_rate_spec.rb +142 -0
- data/spec/models/spree/state_spec.rb +14 -0
- data/spec/models/spree/stock/availability_validator_spec.rb +83 -0
- data/spec/models/spree/stock/coordinator_spec.rb +116 -0
- data/spec/models/spree/stock/differentiator_spec.rb +39 -0
- data/spec/models/spree/stock/estimator_spec.rb +146 -0
- data/spec/models/spree/stock/inventory_unit_builder_spec.rb +38 -0
- data/spec/models/spree/stock/package_spec.rb +163 -0
- data/spec/models/spree/stock/packer_spec.rb +91 -0
- data/spec/models/spree/stock/prioritizer_spec.rb +125 -0
- data/spec/models/spree/stock/quantifier_spec.rb +115 -0
- data/spec/models/spree/stock/splitter/backordered_spec.rb +29 -0
- data/spec/models/spree/stock/splitter/base_spec.rb +21 -0
- data/spec/models/spree/stock/splitter/shipping_category_spec.rb +50 -0
- data/spec/models/spree/stock/splitter/weight_spec.rb +29 -0
- data/spec/models/spree/stock_item_spec.rb +426 -0
- data/spec/models/spree/stock_location_spec.rb +279 -0
- data/spec/models/spree/stock_movement_spec.rb +56 -0
- data/spec/models/spree/stock_transfer_spec.rb +290 -0
- data/spec/models/spree/store_credit_category_spec.rb +17 -0
- data/spec/models/spree/store_credit_event_spec.rb +314 -0
- data/spec/models/spree/store_credit_spec.rb +876 -0
- data/spec/models/spree/store_spec.rb +55 -0
- data/spec/models/spree/tax_category_spec.rb +27 -0
- data/spec/models/spree/tax_rate_spec.rb +378 -0
- data/spec/models/spree/taxon_spec.rb +74 -0
- data/spec/models/spree/taxonomy_spec.rb +18 -0
- data/spec/models/spree/tracker_spec.rb +21 -0
- data/spec/models/spree/transfer_item_spec.rb +264 -0
- data/spec/models/spree/unit_cancel_spec.rb +148 -0
- data/spec/models/spree/user_spec.rb +223 -0
- data/spec/models/spree/validations/db_maximum_length_validator_spec.rb +23 -0
- data/spec/models/spree/variant/scopes_spec.rb +55 -0
- data/spec/models/spree/variant_spec.rb +546 -0
- data/spec/models/spree/zone_spec.rb +305 -0
- data/spec/spec_helper.rb +78 -0
- data/spec/support/big_decimal.rb +5 -0
- data/spec/support/concerns/default_price.rb +34 -0
- data/spec/support/dummy_ability.rb +4 -0
- data/spec/support/test_gateway.rb +2 -0
- metadata +229 -3
- 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
|