solidus_active_shipping 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +1 -0
  4. data/.simplecov +5 -0
  5. data/.travis.yml +21 -0
  6. data/Gemfile +18 -0
  7. data/README.md +192 -0
  8. data/Rakefile +21 -0
  9. data/app/assets/javascripts/admin/product_packages/edit.js.coffee +4 -0
  10. data/app/assets/javascripts/admin/product_packages/index.js.coffee +28 -0
  11. data/app/assets/javascripts/admin/product_packages/new.js.coffee +7 -0
  12. data/app/assets/javascripts/spree/backend/solidus_active_shipping.js +0 -0
  13. data/app/assets/javascripts/spree/frontend/solidus_active_shipping.js +0 -0
  14. data/app/assets/stylesheets/spree/backend/solidus_active_shipping.css +0 -0
  15. data/app/assets/stylesheets/spree/frontend/solidus_active_shipping.css +0 -0
  16. data/app/controllers/spree/admin/active_shipping_settings_controller.rb +27 -0
  17. data/app/controllers/spree/admin/product_packages_controller.rb +17 -0
  18. data/app/controllers/spree/admin/products_controller_decorator.rb +12 -0
  19. data/app/controllers/spree/checkout_controller_decorator.rb +11 -0
  20. data/app/controllers/spree/orders_controller_decorator.rb +11 -0
  21. data/app/models/spree/calculator/shipping/active_shipping/base.rb +208 -0
  22. data/app/models/spree/calculator/shipping/canada_post/base.rb +17 -0
  23. data/app/models/spree/calculator/shipping/canada_post/expedited.rb +11 -0
  24. data/app/models/spree/calculator/shipping/canada_post/parcel_surface.rb +11 -0
  25. data/app/models/spree/calculator/shipping/canada_post/priority_worldwide_intl.rb +12 -0
  26. data/app/models/spree/calculator/shipping/canada_post/regular.rb +11 -0
  27. data/app/models/spree/calculator/shipping/canada_post/small_packets_air.rb +11 -0
  28. data/app/models/spree/calculator/shipping/canada_post/small_packets_surface.rb +11 -0
  29. data/app/models/spree/calculator/shipping/canada_post/xpresspost.rb +11 -0
  30. data/app/models/spree/calculator/shipping/canada_post/xpresspost_international.rb +11 -0
  31. data/app/models/spree/calculator/shipping/fedex/base.rb +21 -0
  32. data/app/models/spree/calculator/shipping/fedex/express_saver.rb +13 -0
  33. data/app/models/spree/calculator/shipping/fedex/first_overnight.rb +13 -0
  34. data/app/models/spree/calculator/shipping/fedex/ground.rb +13 -0
  35. data/app/models/spree/calculator/shipping/fedex/ground_home_delivery.rb +13 -0
  36. data/app/models/spree/calculator/shipping/fedex/international_economy.rb +13 -0
  37. data/app/models/spree/calculator/shipping/fedex/international_economy_freight.rb +13 -0
  38. data/app/models/spree/calculator/shipping/fedex/international_first.rb +13 -0
  39. data/app/models/spree/calculator/shipping/fedex/international_ground.rb +13 -0
  40. data/app/models/spree/calculator/shipping/fedex/international_priority.rb +13 -0
  41. data/app/models/spree/calculator/shipping/fedex/international_priority_freight.rb +13 -0
  42. data/app/models/spree/calculator/shipping/fedex/international_priority_saturday_delivery.rb +13 -0
  43. data/app/models/spree/calculator/shipping/fedex/one_day_freight.rb +13 -0
  44. data/app/models/spree/calculator/shipping/fedex/one_day_freight_saturday_delivery.rb +13 -0
  45. data/app/models/spree/calculator/shipping/fedex/priority_overnight.rb +13 -0
  46. data/app/models/spree/calculator/shipping/fedex/priority_overnight_saturday_delivery.rb +11 -0
  47. data/app/models/spree/calculator/shipping/fedex/saver.rb +11 -0
  48. data/app/models/spree/calculator/shipping/fedex/standard_overnight.rb +11 -0
  49. data/app/models/spree/calculator/shipping/fedex/three_day_freight.rb +11 -0
  50. data/app/models/spree/calculator/shipping/fedex/three_day_freight_saturday_delivery.rb +11 -0
  51. data/app/models/spree/calculator/shipping/fedex/two_day.rb +11 -0
  52. data/app/models/spree/calculator/shipping/fedex/two_day_freight.rb +11 -0
  53. data/app/models/spree/calculator/shipping/fedex/two_day_freight_saturday_delivery.rb +11 -0
  54. data/app/models/spree/calculator/shipping/fedex/two_day_saturday_delivery.rb +11 -0
  55. data/app/models/spree/calculator/shipping/ups/base.rb +30 -0
  56. data/app/models/spree/calculator/shipping/ups/express.rb +11 -0
  57. data/app/models/spree/calculator/shipping/ups/ground.rb +11 -0
  58. data/app/models/spree/calculator/shipping/ups/next_day_air.rb +11 -0
  59. data/app/models/spree/calculator/shipping/ups/next_day_air_early_am.rb +11 -0
  60. data/app/models/spree/calculator/shipping/ups/next_day_air_saver.rb +11 -0
  61. data/app/models/spree/calculator/shipping/ups/saver.rb +11 -0
  62. data/app/models/spree/calculator/shipping/ups/second_day_air.rb +11 -0
  63. data/app/models/spree/calculator/shipping/ups/standard.rb +11 -0
  64. data/app/models/spree/calculator/shipping/ups/three_day_select.rb +11 -0
  65. data/app/models/spree/calculator/shipping/ups/worldwide_expedited.rb +11 -0
  66. data/app/models/spree/calculator/shipping/usps/base.rb +84 -0
  67. data/app/models/spree/calculator/shipping/usps/express_mail.rb +19 -0
  68. data/app/models/spree/calculator/shipping/usps/express_mail_international.rb +46 -0
  69. data/app/models/spree/calculator/shipping/usps/first_class_mail_international.rb +31 -0
  70. data/app/models/spree/calculator/shipping/usps/first_class_mail_international_large_envelope.rb +31 -0
  71. data/app/models/spree/calculator/shipping/usps/first_class_mail_parcel.rb +28 -0
  72. data/app/models/spree/calculator/shipping/usps/first_class_package_international.rb +49 -0
  73. data/app/models/spree/calculator/shipping/usps/global_express_guaranteed.rb +48 -0
  74. data/app/models/spree/calculator/shipping/usps/media_mail.rb +19 -0
  75. data/app/models/spree/calculator/shipping/usps/priority_mail.rb +19 -0
  76. data/app/models/spree/calculator/shipping/usps/priority_mail_flat_rate_envelope.rb +19 -0
  77. data/app/models/spree/calculator/shipping/usps/priority_mail_international.rb +49 -0
  78. data/app/models/spree/calculator/shipping/usps/priority_mail_international_large_flat_rate_box.rb +43 -0
  79. data/app/models/spree/calculator/shipping/usps/priority_mail_international_medium_flat_rate_box.rb +43 -0
  80. data/app/models/spree/calculator/shipping/usps/priority_mail_international_small_flat_rate_box.rb +43 -0
  81. data/app/models/spree/calculator/shipping/usps/priority_mail_large_flat_rate_box.rb +19 -0
  82. data/app/models/spree/calculator/shipping/usps/priority_mail_medium_flat_rate_box.rb +19 -0
  83. data/app/models/spree/calculator/shipping/usps/priority_mail_small_flat_rate_box.rb +19 -0
  84. data/app/models/spree/calculator/shipping/usps/standard_post.rb +19 -0
  85. data/app/models/spree/content_item_decorator.rb +7 -0
  86. data/app/models/spree/line_item_decorator.rb +4 -0
  87. data/app/models/spree/package_builder.rb +114 -0
  88. data/app/models/spree/product_decorator.rb +10 -0
  89. data/app/models/spree/product_package.rb +10 -0
  90. data/app/models/spree/stock_location_decorator.rb +10 -0
  91. data/app/models/spree/variant_decorator.rb +3 -0
  92. data/app/overrides/spree/admin/shared/_configuration_menu/add_active_shipping_settings_tab.html.erb.deface +3 -0
  93. data/app/overrides/spree/admin/shared/_product_tabs/add_product_packages_tab.html.erb.deface +7 -0
  94. data/app/views/spree/admin/active_shipping_settings/edit.html.erb +92 -0
  95. data/app/views/spree/admin/product_packages/_form.html.erb +24 -0
  96. data/app/views/spree/admin/product_packages/edit.html.erb +15 -0
  97. data/app/views/spree/admin/product_packages/index.html.erb +46 -0
  98. data/app/views/spree/admin/product_packages/new.html.erb +15 -0
  99. data/config/locales/en.yml +93 -0
  100. data/config/locales/fr.yml +12 -0
  101. data/config/routes.rb +9 -0
  102. data/db/migrate/20130107030221_create_product_packages.rb +12 -0
  103. data/lib/solidus_active_shipping.rb +3 -0
  104. data/lib/solidus_active_shipping/engine.rb +44 -0
  105. data/lib/spree/active_shipping_configuration.rb +24 -0
  106. data/lib/spree/shipping_error.rb +3 -0
  107. data/solidus_active_shipping.gemspec +33 -0
  108. data/spec/cassettes/Checkout/with_valid_shipping_address/does_not_break_the_per-item_shipping_method_calculator.yml +134 -0
  109. data/spec/cassettes/FedEx_calculators/with_Canadian_origin_address/Spree_Calculator_Shipping_Fedex_Ground/1_1_1_1.yml +48 -0
  110. data/spec/cassettes/FedEx_calculators/with_Canadian_origin_address/Spree_Calculator_Shipping_Fedex_InternationalEconomy/1_1_2_1.yml +48 -0
  111. data/spec/cassettes/FedEx_calculators/with_Canadian_origin_address/Spree_Calculator_Shipping_Fedex_InternationalFirst/1_1_3_1.yml +48 -0
  112. data/spec/cassettes/FedEx_calculators/with_Canadian_origin_address/Spree_Calculator_Shipping_Fedex_InternationalPriority/1_1_4_1.yml +48 -0
  113. data/spec/cassettes/FedEx_calculators/with_US_origin_address/Spree_Calculator_Shipping_Fedex_ExpressSaver/1_2_5_1.yml +48 -0
  114. data/spec/cassettes/FedEx_calculators/with_US_origin_address/Spree_Calculator_Shipping_Fedex_FirstOvernight/1_2_1_1.yml +48 -0
  115. data/spec/cassettes/FedEx_calculators/with_US_origin_address/Spree_Calculator_Shipping_Fedex_GroundHomeDelivery/1_2_6_1.yml +48 -0
  116. data/spec/cassettes/FedEx_calculators/with_US_origin_address/Spree_Calculator_Shipping_Fedex_PriorityOvernight/1_2_2_1.yml +48 -0
  117. data/spec/cassettes/FedEx_calculators/with_US_origin_address/Spree_Calculator_Shipping_Fedex_StandardOvernight/1_2_3_1.yml +48 -0
  118. data/spec/cassettes/FedEx_calculators/with_US_origin_address/Spree_Calculator_Shipping_Fedex_TwoDay/1_2_4_1.yml +48 -0
  119. data/spec/cassettes/UPS_calculators/with_Canadian_origin_address/Spree_Calculator_Shipping_Ups_Express/1_1_1_1.yml +103 -0
  120. data/spec/cassettes/UPS_calculators/with_Canadian_origin_address/Spree_Calculator_Shipping_Ups_Saver/1_1_3_1.yml +103 -0
  121. data/spec/cassettes/UPS_calculators/with_Canadian_origin_address/Spree_Calculator_Shipping_Ups_Standard/1_1_4_1.yml +103 -0
  122. data/spec/cassettes/UPS_calculators/with_Canadian_origin_address/Spree_Calculator_Shipping_Ups_ThreeDaySelect/1_1_5_1.yml +103 -0
  123. data/spec/cassettes/UPS_calculators/with_Canadian_origin_address/Spree_Calculator_Shipping_Ups_WorldwideExpedited/1_1_2_1.yml +103 -0
  124. data/spec/cassettes/UPS_calculators/with_US_origin_address/Spree_Calculator_Shipping_Ups_Ground/1_2_1_1.yml +103 -0
  125. data/spec/cassettes/UPS_calculators/with_US_origin_address/Spree_Calculator_Shipping_Ups_NextDayAir/1_2_2_1.yml +103 -0
  126. data/spec/cassettes/UPS_calculators/with_US_origin_address/Spree_Calculator_Shipping_Ups_NextDayAirEarlyAm/1_2_3_1.yml +103 -0
  127. data/spec/cassettes/UPS_calculators/with_US_origin_address/Spree_Calculator_Shipping_Ups_NextDayAirSaver/1_2_4_1.yml +103 -0
  128. data/spec/cassettes/UPS_calculators/with_US_origin_address/Spree_Calculator_Shipping_Ups_SecondDayAir/1_2_5_1.yml +103 -0
  129. data/spec/controllers/admin/active_shipping_settings_controller_spec.rb +38 -0
  130. data/spec/controllers/admin/product_packages_controller_spec.rb +36 -0
  131. data/spec/factories/order_factory_override.rb +46 -0
  132. data/spec/factories/product_package_factory.rb +9 -0
  133. data/spec/factories/state_factory_override.rb +28 -0
  134. data/spec/features/checkout_spec.rb +37 -0
  135. data/spec/fixtures/normal_rates_request.xml +2 -0
  136. data/spec/integrations/calculators/fedex_spec.rb +56 -0
  137. data/spec/integrations/calculators/ups_spec.rb +56 -0
  138. data/spec/lib/spree/active_shipping/bogus_carrier.rb +18 -0
  139. data/spec/lib/spree/calculator/shipping/bogus_calculator.rb +17 -0
  140. data/spec/models/active_shipping_calculator_spec.rb +182 -0
  141. data/spec/models/carriers/usps_calculator_spec.rb +72 -0
  142. data/spec/models/package_builder_spec.rb +173 -0
  143. data/spec/spec.opts +6 -0
  144. data/spec/spec_helper.rb +80 -0
  145. data/spec/support/capybara.rb +24 -0
  146. data/spec/support/checkout_helper.rb +12 -0
  147. data/spec/support/feature_helper.rb +7 -0
  148. data/spec/support/package_helper.rb +9 -0
  149. data/spec/support/shared_contexts/checkout_setup.rb +12 -0
  150. data/spec/support/shared_contexts/package_setup.rb +34 -0
  151. data/spec/support/shared_contexts/shipping_carriers/fedex.rb +15 -0
  152. data/spec/support/shared_contexts/shipping_carriers/ups.rb +14 -0
  153. data/spec/support/shared_contexts/stock_location_setup.rb +26 -0
  154. data/spec/support/web_fixtures.rb +6 -0
  155. metadata +444 -0
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'UPS calculators', :vcr do
4
+ include_context 'UPS setup'
5
+ include_context 'package setup'
6
+
7
+ subject { described_class.new.compute_package(package) }
8
+
9
+ context 'with Canadian origin address' do
10
+ include_context 'Canada stock location'
11
+
12
+ describe Spree::Calculator::Shipping::Ups::Express do
13
+ it { is_expected.to eq(139.1) }
14
+ end
15
+
16
+ describe Spree::Calculator::Shipping::Ups::WorldwideExpedited do
17
+ it { is_expected.to eq(97.15) }
18
+ end
19
+
20
+ describe Spree::Calculator::Shipping::Ups::Saver do
21
+ it { is_expected.to eq(132.25) }
22
+ end
23
+
24
+ describe Spree::Calculator::Shipping::Ups::Standard do
25
+ it { is_expected.to eq(35.34) }
26
+ end
27
+
28
+ describe Spree::Calculator::Shipping::Ups::ThreeDaySelect do
29
+ it { is_expected.to eq(89.7) }
30
+ end
31
+ end
32
+
33
+ context 'with US origin address' do
34
+ include_context 'US stock location'
35
+
36
+ describe Spree::Calculator::Shipping::Ups::Ground do
37
+ it { is_expected.to eq(14.2) }
38
+ end
39
+
40
+ describe Spree::Calculator::Shipping::Ups::NextDayAir do
41
+ it { is_expected.to eq(79.47) }
42
+ end
43
+
44
+ describe Spree::Calculator::Shipping::Ups::NextDayAirEarlyAm do
45
+ it { is_expected.to eq(110.29) }
46
+ end
47
+
48
+ describe Spree::Calculator::Shipping::Ups::NextDayAirSaver do
49
+ it { is_expected.to eq(77.46) }
50
+ end
51
+
52
+ describe Spree::Calculator::Shipping::Ups::SecondDayAir do
53
+ it { is_expected.to eq(27.97) }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ module ActiveShipping
3
+ # Bogus carrier useful for testing. For some reasons the plugin version of this class does not work
4
+ # properly (it fails to return a RateResponse)
5
+ class BogusCarrier < ::ActiveShipping::Carrier
6
+ include RSpec::Mocks::ExampleMethods
7
+
8
+ def name
9
+ "BogusCarrier"
10
+ end
11
+
12
+ # We're stubbing the rates because actual RateEstimate are too elaborate
13
+ def find_rates(origin, destination, packages, options = {})
14
+ ::ActiveShipping::RateResponse.new(true, "success!", {}, :rates => [instance_double('rate', service_name: 'Bogus Calculator', price: 999)], :xml => "")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ module Calculator::Shipping
5
+ module ActiveShipping
6
+ class BogusCalculator < Spree::Calculator::Shipping::ActiveShipping::Base
7
+ def carrier
8
+ @carrier ||= Spree::ActiveShipping::BogusCarrier.new
9
+ end
10
+
11
+ def self.description
12
+ "Bogus Calculator"
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,182 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Calculator::Shipping do
4
+ # NOTE: All specs will use the bogus calculator
5
+ # (no login information needed)
6
+
7
+ let(:address) { FactoryGirl.create(:address) }
8
+ let(:variant_1) { FactoryGirl.create(:variant, weight: 1) }
9
+ let(:variant_2) { FactoryGirl.create(:variant, weight: 2) }
10
+ let!(:order) do
11
+ FactoryGirl.create(:order_with_line_items, ship_address: address, line_items_count: 2,
12
+ line_items_attributes: [{ quantity: 2, variant: variant_1 }, { quantity: 2, variant: variant_2 }])
13
+ end
14
+
15
+ let(:calculator) { Spree::Calculator::Shipping::ActiveShipping::BogusCalculator.new }
16
+ let(:package) { order.shipments.first.to_package }
17
+
18
+ before(:each) do
19
+ order.create_proposed_shipments
20
+ Spree::ActiveShipping::Config.set(units: 'imperial')
21
+ Spree::ActiveShipping::Config.set(unit_multiplier: 1)
22
+ Spree::ActiveShipping::Config.set(handling_fee: 0)
23
+ end
24
+
25
+ describe 'available' do
26
+ context 'when rates are available' do
27
+ it 'should return true' do
28
+ expect(calculator.available?(package)).to eq true
29
+ end
30
+
31
+ it 'should use zero as a valid weight for service' do
32
+ allow(calculator.carrier).to receive(:max_weight_for_country).and_return(0)
33
+ expect(calculator.available?(package)).to eq true
34
+ end
35
+ end
36
+
37
+ context 'when rates are not available' do
38
+ let(:invalid_response) do
39
+ ::ActiveShipping::RateResponse.new(true, 'success!', {}, rates: [], xml: '')
40
+ end
41
+
42
+ before do
43
+ allow(calculator.carrier).to receive(:find_rates).and_return(invalid_response)
44
+ end
45
+
46
+ it 'should return false' do
47
+ expect(calculator.available?(package)).to eq false
48
+ end
49
+ end
50
+
51
+ context 'when there is an error retrieving the rates' do
52
+ before do
53
+ allow(calculator.carrier).to receive(:find_rates).and_raise(::ActiveShipping::ResponseError)
54
+ end
55
+
56
+ it 'should return false' do
57
+ expect(calculator.available?(package)).to eq false
58
+ end
59
+ end
60
+ end
61
+
62
+ describe 'available?' do
63
+ # regression test for #164 and #171
64
+ it 'should not return rates if the weight requirements for the destination country are not met' do
65
+ # if max_weight_for_country is nil -> the carrier does not ship to that country
66
+ # if max_weight_for_country is 0 -> the carrier does not have weight restrictions to that country
67
+ allow(calculator).to receive(:max_weight_for_country).and_return(nil)
68
+ expect(calculator).to receive(:is_package_shippable?).and_raise(Spree::ShippingError)
69
+ expect(calculator.available?(package)).to eq false
70
+ end
71
+ end
72
+
73
+ describe 'compute' do
74
+ subject { calculator.compute(package) }
75
+
76
+ context 'when the cache is warm' do
77
+ it 'should check the cache first before finding rates' do
78
+ # Since the cache is cleared between the tests, cache.fetch will return a miss,
79
+ # but by passing a block { Hash.new }, the return value of the block will be
80
+ # written under the given cache key so we simulate a warm cache
81
+ Rails.cache.fetch(calculator.send(:cache_key, package)) { Hash.new }
82
+ expect(calculator.carrier).not_to receive(:find_rates)
83
+ subject
84
+ end
85
+ end
86
+
87
+ context 'when the cache is empty' do
88
+ before do
89
+ # We're stubbing the carrier method because we
90
+ # need to check that a specific instance of carrier
91
+ # is receiving or not the function call (otherwise test will
92
+ # pass but only because carrier we're watching and the carrier
93
+ # used by the calculator are different)
94
+ allow(calculator.carrier).to receive(:find_rates).and_call_original
95
+ end
96
+
97
+ it 'should call .find_rates' do
98
+ expect(calculator.carrier).to receive(:find_rates)
99
+ subject
100
+ end
101
+ end
102
+
103
+ context 'with valid response' do
104
+ it "should return rate based on calculator's service_name" do
105
+ expect(subject).to eq 9.99
106
+ end
107
+
108
+ it 'should include handling_fee when configured' do
109
+ Spree::ActiveShipping::Config.set(handling_fee: 100)
110
+ expect(subject).to eq 10.99
111
+ end
112
+
113
+ it 'should return nil if service_name is not found in rate_hash' do
114
+ allow(calculator.class).to receive(:description) { 'invalid service_name' }
115
+ expect(subject).to be_nil
116
+ end
117
+ end
118
+
119
+ context 'with invalid response' do
120
+ before do
121
+ allow(calculator.carrier).to receive(:find_rates).and_raise(::ActiveShipping::ResponseError)
122
+ end
123
+
124
+ it 'should raise a Spree::ShippingError' do
125
+ expect { subject }.to raise_exception(Spree::ShippingError)
126
+ end
127
+ end
128
+ end
129
+
130
+ describe 'service_name' do
131
+ it 'should return description when not defined' do
132
+ expect(calculator.class.service_name).to eq calculator.description
133
+ end
134
+ end
135
+
136
+ # We make an exception and tests this the private method because max_weight values
137
+ # are difficult to tests conclusively through the
138
+ describe 'get_max_weight' do
139
+ include_context 'US stock location'
140
+ include_context 'package setup'
141
+
142
+ context 'when the max_weight from the calculator is non-zero and max_weight_per_package is zero' do
143
+ before do
144
+ allow(calculator).to receive(:max_weight_for_country).and_return(1)
145
+ allow(calculator).to receive(:max_weight_per_package).and_return(0)
146
+ end
147
+
148
+ it 'uses the max_weight_for_country as a max_weight' do
149
+ expect(calculator.send(:get_max_weight, package)).to eq calculator.send(:max_weight_for_country)
150
+ end
151
+ end
152
+
153
+ context 'when the max_weight from the calculator is zero and max_weight_per_package is non-zero' do
154
+ before do
155
+ allow(calculator).to receive(:max_weight_for_country).and_return(0)
156
+ allow(calculator).to receive(:max_weight_per_package).and_return(1)
157
+ end
158
+
159
+ it 'uses the max_weight_per_package as a max_weight' do
160
+ expect(calculator.send(:get_max_weight, package)).to eq calculator.send(:max_weight_per_package)
161
+ end
162
+ end
163
+
164
+ context 'when the max_weight from the calculator is non-zero and max_weight_per_package is non-zero' do
165
+ before do
166
+ allow(calculator).to receive(:max_weight_per_package).and_return(SecureRandom.random_number(19) + 1)
167
+ allow(calculator).to receive(:max_weight_for_country).and_return(SecureRandom.random_number(19) + 1)
168
+ end
169
+
170
+ it 'uses the lesser one of the two values' do
171
+ min = [calculator.send(:max_weight_for_country), calculator.send(:max_weight_per_package)].min
172
+ expect(calculator.send(:get_max_weight, package)).to eq min
173
+ end
174
+ end
175
+
176
+ context 'when the max_weight is zero and max_weight_per_package is zero' do
177
+ it 'uses 0 as a max_eight' do
178
+ expect(calculator.send(:get_max_weight, package)).to be_zero
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Calculator::Shipping::Usps do
4
+ let(:address) { FactoryGirl.create(:address) }
5
+ let(:variant_1) { FactoryGirl.create(:variant, weight: 1) }
6
+ let(:variant_2) { FactoryGirl.create(:variant, weight: 2) }
7
+ let!(:order) do
8
+ FactoryGirl.create(:order_with_line_items, ship_address: address, line_items_count: 2,
9
+ line_items_attributes: [{ quantity: 2, variant: variant_1}, { quantity: 2, variant: variant_2 }] )
10
+ end
11
+
12
+ let(:carrier) { ActiveShipping::USPS.new(login: 'FAKEFAKEFAKE') }
13
+ let(:calculator) { Spree::Calculator::Shipping::Usps::ExpressMail.new }
14
+ let(:package) { order.shipments.first.to_package }
15
+
16
+ before(:each) do
17
+ order.create_proposed_shipments
18
+ Spree::ActiveShipping::Config.set(units: 'imperial')
19
+ Spree::ActiveShipping::Config.set(unit_multiplier: 1)
20
+ Spree::ActiveShipping::Config.set(handling_fee: 0)
21
+
22
+ stub_request(:get, %r{http:\/\/production.shippingapis.com\/ShippingAPI.dll.*})
23
+ .to_return(body: fixture(:normal_rates_request))
24
+
25
+ # Since the response can be cached, we explicitly clear cache
26
+ # so each test can be run from a clean slate
27
+ Rails.cache.delete(calculator.send(:cache_key, package))
28
+ end
29
+
30
+ describe 'compute' do
31
+ subject { calculator.compute(package) }
32
+
33
+ it 'should use the carrier supplied in the initializer' do
34
+ expect(subject).to eq 14.1
35
+ end
36
+
37
+ context 'with valid response' do
38
+ it "should return rate based on calculator's service_code" do
39
+ allow(calculator.class).to receive(:service_code) { 'dom:3' }
40
+ expect(subject).to eq 14.1
41
+ end
42
+
43
+ it 'should include handling_fee when configured' do
44
+ Spree::ActiveShipping::Config.set(handling_fee: 100)
45
+ allow(calculator.class).to receive(:service_code) { 'dom:3' }
46
+ expect(subject).to eq 15.1
47
+ end
48
+
49
+ it 'should return nil if service_code is not found in rate_hash' do
50
+ allow(calculator.class).to receive(:service_code) { 'invalid service_code' }
51
+ expect(subject).to be_nil
52
+ end
53
+ end
54
+
55
+ context 'with invalid response' do
56
+ before do
57
+ allow(calculator).to receive(:carrier).and_return(carrier)
58
+ allow(carrier).to receive(:find_rates).and_raise(::ActiveShipping::ResponseError)
59
+ end
60
+
61
+ it 'should raise a Spree::ShippingError' do
62
+ expect{ subject }.to raise_exception(Spree::ShippingError)
63
+ end
64
+ end
65
+ end
66
+
67
+ describe 'service_name' do
68
+ it 'should return description when not defined' do
69
+ expect(calculator.class.service_name).to eq calculator.description
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,173 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::PackageBuilder do
4
+ include_context 'Canada stock location'
5
+ include PackageHelper
6
+
7
+ let(:shipping_calculator) do
8
+ Spree::Calculator::Shipping::ActiveShipping::BogusCalculator.new
9
+ end
10
+
11
+ let(:package_builder) { Spree::PackageBuilder.new }
12
+
13
+ before do
14
+ # We disable the multiplier so it's simpler to test
15
+ allow(package_builder).to receive(:multiplier).and_return(1)
16
+ end
17
+
18
+ describe 'process' do
19
+ include_context 'package setup'
20
+
21
+ let(:max_weight) { 0 }
22
+
23
+ subject { package_builder.process(package, max_weight) }
24
+
25
+ it 'returns an array of ActiveShipping::Package' do
26
+ expect(subject.map(&:class).uniq).to match_array([ActiveShipping::Package])
27
+ end
28
+
29
+ it 'uses the unit multiplier in the calculations' do
30
+ allow(package_builder).to receive(:multiplier).and_return(2)
31
+ expect(subject.sum(&:weight)).to eq (package.weight * 2)
32
+ end
33
+
34
+ context 'when one product has a zero weight' do
35
+ let(:variant_1) { FactoryGirl.create(:variant, weight: 0) }
36
+
37
+ it 'ignores products with nil weight' do
38
+ expect(subject.sum(&:weight)).to eq (variant_2.weight * 2)
39
+ end
40
+ end
41
+
42
+ context 'when one product has a nil weight' do
43
+ let(:variant_1) { FactoryGirl.create(:variant, weight: nil) }
44
+ let(:variant_2) { FactoryGirl.create(:variant, weight: nil) }
45
+ let(:default_weight) { Spree::ActiveShipping::Config[:default_weight] }
46
+
47
+ it 'use the default_weight as a weight value' do
48
+ expect(subject.sum(&:weight)).to eq( default_weight * 4 )
49
+ end
50
+ end
51
+
52
+ context 'with an order containing only products with associated product_packages' do
53
+ let(:product_weight) { 20 }
54
+ let(:product_package1) { FactoryGirl.create(:product_package, weight: product_weight) }
55
+ let(:product_package2) { FactoryGirl.create(:product_package, weight: product_weight) }
56
+ let(:product_with_packages) do
57
+ build(
58
+ :variant,
59
+ weight: product_weight,
60
+ product: build(
61
+ :product,
62
+ product_packages: [product_package1, product_package2]
63
+ )
64
+ )
65
+ end
66
+
67
+ let(:package) do
68
+ build(:stock_package,
69
+ stock_location: stock_location,
70
+ contents: [build_content_items(product_with_packages, 2, order),
71
+ build_content_items(product_with_packages, 1, order)].flatten)
72
+ end
73
+
74
+ context 'when there is non-zero max_weight' do
75
+ context 'when the product_package weight exceeds the allowed max_weight' do
76
+ let(:max_weight) { product_weight - 1 }
77
+
78
+ it 'raise a Spree::Shipping error' do
79
+ expect { subject }.to raise_error(Spree::ShippingError)
80
+ end
81
+ end
82
+
83
+ context 'when the product_packages weight does not exceed the allowed max_weight' do
84
+ let(:max_weight) { 1000 }
85
+
86
+ it 'return a ActiveShipping::Package for each ProductPackage of each ContentItem' do
87
+ # Each individual item in the Order as a 1-1 association with a ContentItem
88
+ expected_size = package.contents.sum { |item| item.variant.product.product_packages.size }
89
+ expect(subject.size).to eq expected_size
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'when there is no max weight (0)' do
95
+ it 'return a ActiveShipping::Package for each ProductPackage of each ContentItem' do
96
+ # Each individual item in the Order as a 1-1 association with a ContentItem
97
+ expected_size = package.contents.sum { |item| item.variant.product.product_packages.size }
98
+ expect(subject.size).to eq expected_size
99
+ end
100
+ end
101
+ end
102
+
103
+ context 'with an order containing only products without any associated product_packages' do
104
+ include_context 'package setup'
105
+
106
+ context 'when there is non-zero max_weight' do
107
+ context 'and their combined weight is lower than the max_weight' do
108
+ let(:max_weight) { package.weight + 1 }
109
+
110
+ it 'will combine items into the same package' do
111
+ expect(subject.size).to eq 1
112
+ end
113
+ end
114
+
115
+ context 'and their combined weight is higher than the max_weight' do
116
+ let(:max_weight) { package.weight - 1 }
117
+
118
+ it 'will split items into multiple packages' do
119
+ expect(subject.size).to eq 2
120
+ end
121
+ end
122
+
123
+ context 'and the weight of a single item is higher than the max_weight' do
124
+ let(:max_weight) { variant_2.weight - 1 }
125
+ it 'will raise an Spree::ShippingError' do
126
+ expect { subject }.to raise_error(Spree::ShippingError)
127
+ end
128
+ end
129
+ end
130
+
131
+ context 'when there is no max_weight' do
132
+ it 'will combine all items into the same package' do
133
+ expect(subject.size).to eq 1
134
+ end
135
+ end
136
+ end
137
+
138
+ context 'with an order containing some products with product_packages and some products without' do
139
+ let(:product_weight) { 20 }
140
+ let(:product_package1) { FactoryGirl.create(:product_package, weight: product_weight) }
141
+ let(:product_package2) { FactoryGirl.create(:product_package, weight: product_weight) }
142
+
143
+ let(:product_no_packages) { FactoryGirl.create(:variant, weight: 5) }
144
+ let(:product_with_packages) do
145
+ build(
146
+ :variant,
147
+ weight: product_weight,
148
+ product: build(
149
+ :product,
150
+ product_packages: [product_package1, product_package2]
151
+ )
152
+ )
153
+ end
154
+
155
+ let(:package) do
156
+ build(:stock_package,
157
+ stock_location: stock_location,
158
+ contents: [build_content_items(product_with_packages, 1, order),
159
+ build_content_items(product_no_packages, 1, order),
160
+ build_content_items(product_no_packages, 1, order)].flatten)
161
+ end
162
+
163
+ let(:max_weight) { 1000 }
164
+
165
+ it 'products with product_packages will not be combined with product with no packages' do
166
+ active_shipping_packages = subject
167
+ # First package in the array is the "default package" who should
168
+ # only include product_with_no_packages x2
169
+ expect(active_shipping_packages[0].weight).to eq (product_no_packages.weight * 2)
170
+ end
171
+ end
172
+ end
173
+ end