mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
435 lines
15 KiB
Ruby
435 lines
15 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe Api::V0::ShipmentsController do
|
|
render_views
|
|
|
|
let!(:shipment) { create(:shipment) }
|
|
let(:attributes) { %w[id tracking number cost shipped_at order_id] }
|
|
let(:current_api_user) { build(:user) }
|
|
|
|
before do
|
|
allow(controller).to receive(:spree_current_user) { current_api_user }
|
|
end
|
|
|
|
context "as a non-admin" do
|
|
it "cannot make a shipment ready" do
|
|
api_put :ready, order_id: shipment.order.to_param, id: shipment.to_param
|
|
assert_unauthorized!
|
|
end
|
|
|
|
it "cannot make a shipment shipped" do
|
|
api_put :ship, order_id: shipment.order.to_param, id: shipment.to_param
|
|
assert_unauthorized!
|
|
end
|
|
end
|
|
|
|
context "as an admin" do
|
|
let(:current_api_user) { build(:admin_user) }
|
|
let!(:order) { shipment.order }
|
|
let(:order_ship_address) { create(:address) }
|
|
let!(:variant) { create(:variant) }
|
|
let(:params) do
|
|
{ quantity: 2,
|
|
variant_id: variant.to_param,
|
|
order_id: order.number,
|
|
format: :json }
|
|
end
|
|
let(:error_message) { "broken shipments creation" }
|
|
|
|
before do
|
|
order.update_attribute :ship_address_id, order_ship_address.id
|
|
order.update_attribute :distributor, variant.supplier
|
|
shipment.shipping_method.distributors << variant.supplier
|
|
end
|
|
|
|
context '#create' do
|
|
it 'creates a shipment if order does not have a shipment' do
|
|
order.shipment.destroy
|
|
order.reload
|
|
|
|
spree_post :create, params
|
|
|
|
expect_valid_response
|
|
expect(order.shipment.reload.inventory_units.size).to eq 2
|
|
expect(order.reload.line_items.first.variant.price).to eq(variant.price)
|
|
end
|
|
|
|
it 'updates and returns exiting shipment, if order already has a shipment' do
|
|
original_shipment_id = order.shipment.id
|
|
|
|
spree_post :create, params
|
|
|
|
expect(json_response["id"]).to eq(original_shipment_id)
|
|
expect_valid_response
|
|
expect(order.shipment.reload.inventory_units.size).to eq 2
|
|
expect(order.reload.line_items.first.variant.price).to eq(variant.price)
|
|
end
|
|
|
|
it 'updates existing shipment with variant override if an VO is sent', feature: :inventory do
|
|
hub = create(:distributor_enterprise)
|
|
order.update_attribute(:distributor, hub)
|
|
shipment.shipping_method.distributors << hub
|
|
variant_override = create(:variant_override, hub:, variant:)
|
|
|
|
spree_post :create, params
|
|
|
|
expect_valid_response
|
|
expect(order.shipment.reload.inventory_units.size).to eq 2
|
|
expect(order.reload.line_items.first.price).to eq(variant_override.price)
|
|
end
|
|
|
|
it 'returns error code when adding to order contents fails' do
|
|
make_order_contents_fail
|
|
|
|
spree_post :create, params
|
|
|
|
expect_error_response
|
|
end
|
|
|
|
it "applies any enterprise fees that are present" do
|
|
order_cycle = create(:simple_order_cycle,
|
|
coordinator: order.distributor,
|
|
coordinator_fees: [create(:enterprise_fee, amount: 20)],
|
|
distributors: [order.distributor],
|
|
variants: [variant])
|
|
order.update!(order_cycle_id: order_cycle.id)
|
|
spree_post :create, params
|
|
|
|
expect(order.line_item_adjustments.where(originator_type: "EnterpriseFee")).to be_present
|
|
end
|
|
end
|
|
|
|
it "can make a shipment ready" do
|
|
allow_any_instance_of(Spree::Order).to receive_messages(paid?: true, complete?: true)
|
|
api_put :ready, order_id: shipment.order.to_param, id: shipment.to_param
|
|
|
|
expect(json_response.keys).to include(*attributes)
|
|
expect(json_response["state"]).to eq("ready")
|
|
expect(shipment.reload.state).to eq("ready")
|
|
end
|
|
|
|
it "checks if shipment is ready already" do
|
|
allow_any_instance_of(Spree::Order).to receive_messages(paid?: true, complete?: true)
|
|
shipment.ready!
|
|
|
|
api_put :ready, order_id: shipment.order.to_param, id: shipment.to_param
|
|
|
|
expect(json_response.keys).to include(*attributes)
|
|
expect(json_response["state"]).to eq("ready")
|
|
expect(shipment.reload.state).to eq("ready")
|
|
end
|
|
|
|
it "cannot make a shipment ready if the order is unpaid" do
|
|
allow_any_instance_of(Spree::Order).to receive_messages(paid?: false)
|
|
api_put :ready, order_id: shipment.order.to_param, id: shipment.to_param
|
|
|
|
expect(json_response["error"]).to eq("Cannot ready shipment.")
|
|
expect(response).to have_http_status(:unprocessable_entity)
|
|
end
|
|
|
|
describe "#add and #remove" do
|
|
let(:order) { create :completed_order_with_totals }
|
|
let(:line_item) { order.line_items.first }
|
|
let(:existing_variant) { line_item.variant }
|
|
let(:new_variant) { create(:variant) }
|
|
let(:params) {
|
|
{
|
|
quantity: 2,
|
|
order_id: order.to_param,
|
|
id: order.shipments.first.to_param
|
|
}
|
|
}
|
|
|
|
before do
|
|
line_item.update!(quantity: 3)
|
|
end
|
|
|
|
context 'for completed shipments' do
|
|
it 'adds a variant to a shipment' do
|
|
expect {
|
|
api_put :add, params.merge(variant_id: new_variant.to_param)
|
|
expect(response).to have_http_status(:ok)
|
|
}.to change { inventory_units_for(new_variant).size }.by(2)
|
|
end
|
|
|
|
it 'adjusts stock when adding a variant' do
|
|
expect {
|
|
api_put :add, params.merge(variant_id: new_variant.to_param)
|
|
}.to change { new_variant.reload.on_hand }.by(-2)
|
|
end
|
|
|
|
it 'removes a variant from a shipment' do
|
|
expect {
|
|
api_put :remove, params.merge(variant_id: existing_variant.to_param)
|
|
expect(response).to have_http_status(:ok)
|
|
}.to change { inventory_units_for(existing_variant).size }.by(-2)
|
|
end
|
|
|
|
it 'adjusts stock when removing a variant' do
|
|
expect {
|
|
api_put :remove, params.merge(variant_id: existing_variant.to_param)
|
|
}.to change { existing_variant.reload.on_hand }.by(2)
|
|
end
|
|
|
|
it 'does not adjust stock when removing a variant' do
|
|
expect {
|
|
api_put :remove, params.merge(variant_id: existing_variant.to_param,
|
|
restock_item: 'false')
|
|
}.to change { existing_variant.reload.on_hand }.by(0)
|
|
end
|
|
end
|
|
|
|
context "for canceled orders" do
|
|
before do
|
|
expect(order.cancel).to eq true
|
|
end
|
|
|
|
it "doesn't adjust stock when adding a variant" do
|
|
expect {
|
|
api_put :add, params.merge(variant_id: existing_variant.to_param)
|
|
expect(response).to have_http_status(:unprocessable_entity)
|
|
}.not_to change { existing_variant.reload.on_hand }
|
|
end
|
|
|
|
it "doesn't adjust stock when removing a variant" do
|
|
expect {
|
|
api_put :remove, params.merge(variant_id: existing_variant.to_param)
|
|
expect(response).to have_http_status(:unprocessable_entity)
|
|
}.not_to change { existing_variant.reload.on_hand }
|
|
end
|
|
end
|
|
|
|
context "with shipping fees" do
|
|
let!(:distributor) { create(:distributor_enterprise) }
|
|
let(:fee_amount) { 10 }
|
|
let!(:shipping_method_with_fee) {
|
|
create(:shipping_method_with, :shipping_fee, distributors: [distributor],
|
|
shipping_fee: fee_amount)
|
|
}
|
|
let!(:order_cycle) { create(:order_cycle, distributors: [distributor]) }
|
|
let!(:order) {
|
|
create(:completed_order_with_totals, order_cycle:, distributor:)
|
|
}
|
|
let(:shipping_fee) { order.reload.shipment.adjustments.first }
|
|
|
|
before do
|
|
order.shipments.first.shipping_methods = [shipping_method_with_fee]
|
|
order.select_shipping_method(shipping_method_with_fee.id)
|
|
order.update_order!
|
|
end
|
|
|
|
context "adding item to a shipment" do
|
|
it "updates the shipping fee" do
|
|
expect {
|
|
api_put :add, params.merge(variant_id: new_variant.to_param)
|
|
}.to change { order.reload.shipment.adjustments.first.amount }.by(20)
|
|
end
|
|
end
|
|
|
|
context "removing item from a shipment" do
|
|
it "updates the shipping fee" do
|
|
expect {
|
|
api_put :remove, params.merge(variant_id: existing_variant.to_param)
|
|
}.to change { order.reload.shipment.adjustments.first.amount }.by(-20)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#update" do
|
|
let!(:distributor) { create(:distributor_enterprise) }
|
|
let!(:shipping_method1) {
|
|
create(:shipping_method_with, :flat_rate, distributors: [distributor], amount: 10)
|
|
}
|
|
let!(:shipping_method2) {
|
|
create(:shipping_method_with, :flat_rate, distributors: [distributor], amount: 20)
|
|
}
|
|
let!(:order_cycle) { create(:order_cycle, distributors: [distributor]) }
|
|
let!(:order) {
|
|
create(:completed_order_with_totals, order_cycle:, distributor:)
|
|
}
|
|
let(:new_shipping_rate) {
|
|
order.shipment.shipping_rates.select{ |sr| sr.shipping_method == shipping_method2 }.first
|
|
}
|
|
let(:params) {
|
|
{
|
|
id: order.shipment.number,
|
|
order_id: order.number,
|
|
shipment: {
|
|
selected_shipping_rate_id: new_shipping_rate.id
|
|
}
|
|
}
|
|
}
|
|
|
|
before do
|
|
order.shipments.first.shipping_methods = [shipping_method1, shipping_method2]
|
|
order.shipments.each(&:refresh_rates)
|
|
order.select_shipping_method(shipping_method1.id)
|
|
order.update_order!
|
|
order.update_columns(
|
|
payment_total: 60,
|
|
payment_state: "paid"
|
|
)
|
|
end
|
|
|
|
context "when an order has multiple shipping methods available which could be chosen" do
|
|
context "changing the selected shipping method" do
|
|
it "updates the order's totals and states" do
|
|
expect(order.shipment.shipping_method).to eq shipping_method1
|
|
expect(order.shipment.cost).to eq 10
|
|
expect(order.total).to eq 60 # item total is 50, shipping cost is 10
|
|
expect(order.payment_state).to eq "paid" # order is fully paid for
|
|
|
|
api_put :update, params
|
|
expect(response).to have_http_status :ok
|
|
|
|
order.reload
|
|
|
|
expect(order.shipment.shipping_method).to eq shipping_method2
|
|
expect(order.shipment.cost).to eq 20
|
|
expect(order.total).to eq 70 # item total is 50, shipping cost is 20
|
|
expect(order.payment_state).to eq "balance_due" # total changed, payment is due
|
|
end
|
|
|
|
it "updates closed adjustments" do
|
|
expect {
|
|
api_put :update, params
|
|
expect(response).to have_http_status :ok
|
|
}.to change { order.reload.shipment.fee_adjustment.amount }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#ship" do
|
|
before do
|
|
allow_any_instance_of(Spree::Order).to receive_messages(paid?: true, complete?: true)
|
|
# For the shipment notification email
|
|
Spree::Config[:mails_from] = "ofn@example.com"
|
|
|
|
shipment.update!(shipment.order)
|
|
expect(shipment.state).to eq("ready")
|
|
allow_any_instance_of(Spree::ShippingRate).to receive_messages(cost: 5)
|
|
end
|
|
|
|
it "can transition a shipment from ready to ship" do
|
|
shipment.reload
|
|
api_put :ship, order_id: shipment.order.to_param,
|
|
id: shipment.to_param,
|
|
shipment: { tracking: "123123" }
|
|
|
|
expect(json_response.keys).to include(*attributes)
|
|
expect(json_response["state"]).to eq("shipped")
|
|
end
|
|
end
|
|
|
|
context 'for a completed order with shipment' do
|
|
let(:order) { create :completed_order_with_totals }
|
|
|
|
before { params[:id] = order.shipments.first.to_param }
|
|
|
|
context '#add' do
|
|
it 'adds a variant to the shipment' do
|
|
spree_put :add, params
|
|
|
|
expect_valid_response
|
|
expect(inventory_units_for(variant).size).to eq 2
|
|
end
|
|
|
|
it 'returns error code when adding to order contents fails' do
|
|
make_order_contents_fail
|
|
|
|
spree_put :add, params
|
|
|
|
expect_error_response
|
|
end
|
|
|
|
it 'adds a variant override to the shipment', feature: :inventory do
|
|
hub = create(:distributor_enterprise)
|
|
order.update_attribute(:distributor, hub)
|
|
variant_override = create(:variant_override, hub:, variant:)
|
|
|
|
spree_put :add, params
|
|
|
|
expect_valid_response
|
|
expect(inventory_units_for(variant).size).to eq 2
|
|
expect(order.reload.line_items.last.price).to eq(variant_override.price)
|
|
end
|
|
|
|
context "when line items have fees" do
|
|
let(:fee_order) {
|
|
instance_double(Spree::Order, number: "123", distributor: variant.supplier)
|
|
}
|
|
let(:contents) { instance_double(Spree::OrderContents) }
|
|
let(:fee_order_shipment) {
|
|
instance_double(Spree::Shipment)
|
|
}
|
|
|
|
before do
|
|
allow(Spree::Order).to receive(:find_by!) { fee_order }
|
|
allow(controller).to receive(:refuse_changing_cancelled_orders)
|
|
allow(fee_order).to receive(:contents) { contents }
|
|
allow(contents).to receive_messages(add: {}, remove: {})
|
|
allow(fee_order).to receive_message_chain(:shipments, :find_by!) { fee_order_shipment }
|
|
allow(fee_order_shipment).to receive_messages(update: nil, reload: nil, persisted?: nil)
|
|
allow(fee_order).to receive(:recreate_all_fees!)
|
|
end
|
|
|
|
it "recalculates fees for the line item" do
|
|
params[:order_id] = fee_order.number
|
|
spree_put :add, params
|
|
expect(fee_order).to have_received(:recreate_all_fees!)
|
|
end
|
|
|
|
it "recalculates fees for the line item when qty is decreased" do
|
|
params[:order_id] = fee_order.number
|
|
spree_put :remove, params
|
|
expect(fee_order).to have_received(:recreate_all_fees!)
|
|
end
|
|
end
|
|
end
|
|
|
|
context '#remove' do
|
|
before do
|
|
params[:variant_id] = order.line_items.first.variant.to_param
|
|
params[:quantity] = 1
|
|
end
|
|
|
|
it 'removes a variant from the shipment' do
|
|
spree_put :remove, params
|
|
|
|
expect_valid_response
|
|
expect(inventory_units_for(variant).size).to eq 0
|
|
end
|
|
|
|
it 'returns error code when removing from order contents fails' do
|
|
make_order_contents_fail
|
|
|
|
spree_put :remove, params
|
|
|
|
expect_error_response
|
|
end
|
|
end
|
|
end
|
|
|
|
def inventory_units_for(variant)
|
|
order.shipment.reload.inventory_units.select { |unit| unit['variant_id'] == variant.id }
|
|
end
|
|
|
|
def expect_valid_response
|
|
expect(response).to have_http_status :ok
|
|
expect(json_response.keys).to include(*attributes)
|
|
end
|
|
|
|
def make_order_contents_fail
|
|
expect(Spree::Order).to receive(:find_by!).with({ number: order.number }) { order }
|
|
expect(order).to receive(:contents) { raise error_message }
|
|
end
|
|
|
|
def expect_error_response
|
|
expect(response).to have_http_status :unprocessable_entity
|
|
expect(json_response["exception"]).to eq error_message
|
|
end
|
|
end
|
|
end
|