mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-02 21:57:17 +00:00
The previous version wasn't actually testing the sorting option. There was only one supplier and the items got sorted the same way in both cases.
1353 lines
42 KiB
Ruby
1353 lines
42 KiB
Ruby
# frozen_string_literal: false
|
|
|
|
require 'spec_helper'
|
|
|
|
describe Spree::Order do
|
|
include OpenFoodNetwork::EmailHelper
|
|
|
|
let(:user) { build(:user, email: "spree@example.com") }
|
|
let(:order) { build(:order, user: user) }
|
|
|
|
context "#products" do
|
|
let(:order) { create(:order_with_line_items) }
|
|
|
|
it "should return ordered products" do
|
|
expect(order.products.first).to eq order.line_items.first.product
|
|
end
|
|
|
|
it "contains?" do
|
|
expect(order.contains?(order.line_items.first.variant)).to be_truthy
|
|
end
|
|
|
|
it "can find a line item matching a given variant" do
|
|
expect(order.find_line_item_by_variant(order.line_items.third.variant)).to_not be_nil
|
|
expect(order.find_line_item_by_variant(build(:variant))).to be_nil
|
|
end
|
|
end
|
|
|
|
context "#generate_order_number" do
|
|
it "should generate a random string" do
|
|
expect(order.generate_order_number.is_a?(String)).to be_truthy
|
|
expect(!order.generate_order_number.to_s.empty?).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "#associate_user!" do
|
|
it "should associate a user with a persisted order" do
|
|
order = create(:order_with_line_items, created_by: nil)
|
|
user = create(:user)
|
|
|
|
order.user = nil
|
|
order.email = nil
|
|
order.associate_user!(user)
|
|
expect(order.user).to eq user
|
|
expect(order.email).to eq user.email
|
|
expect(order.created_by).to eq user
|
|
|
|
# verify that the changes we made were persisted
|
|
order.reload
|
|
expect(order.user).to eq user
|
|
expect(order.email).to eq user.email
|
|
expect(order.created_by).to eq user
|
|
end
|
|
|
|
it "should not overwrite the created_by if it already is set" do
|
|
creator = create(:user)
|
|
order = create(:order_with_line_items, created_by: creator)
|
|
user = create(:user)
|
|
|
|
order.user = nil
|
|
order.email = nil
|
|
order.associate_user!(user)
|
|
expect(order.user).to eq user
|
|
expect(order.email).to eq user.email
|
|
expect(order.created_by).to eq creator
|
|
|
|
# verify that the changes we made were persisted
|
|
order.reload
|
|
expect(order.user).to eq user
|
|
expect(order.email).to eq user.email
|
|
expect(order.created_by).to eq creator
|
|
end
|
|
|
|
it "should associate a user with a non-persisted order" do
|
|
order = Spree::Order.new
|
|
|
|
expect do
|
|
order.associate_user!(user)
|
|
end.to change { [order.user, order.email] }.from([nil, nil]).to([user, user.email])
|
|
end
|
|
|
|
it "should not persist an invalid address" do
|
|
address = Spree::Address.new
|
|
order.user = nil
|
|
order.email = nil
|
|
order.ship_address = address
|
|
expect do
|
|
order.associate_user!(user)
|
|
end.not_to change { address.persisted? }.from(false)
|
|
end
|
|
end
|
|
|
|
context "#create" do
|
|
it "should assign an order number" do
|
|
order = Spree::Order.create
|
|
expect(order.number).to_not be_nil
|
|
end
|
|
end
|
|
|
|
context "#can_ship?" do
|
|
let(:order) { Spree::Order.create }
|
|
|
|
it "should be true for order in the 'complete' state" do
|
|
allow(order).to receive_messages(complete?: true)
|
|
expect(order.can_ship?).to be_truthy
|
|
end
|
|
|
|
it "should be true for order in the 'resumed' state" do
|
|
allow(order).to receive_messages(resumed?: true)
|
|
expect(order.can_ship?).to be_truthy
|
|
end
|
|
|
|
it "should be true for an order in the 'awaiting return' state" do
|
|
allow(order).to receive_messages(awaiting_return?: true)
|
|
expect(order.can_ship?).to be_truthy
|
|
end
|
|
|
|
it "should be true for an order in the 'returned' state" do
|
|
allow(order).to receive_messages(returned?: true)
|
|
expect(order.can_ship?).to be_truthy
|
|
end
|
|
|
|
it "should be false if the order is neither in the 'complete' nor 'resumed' state" do
|
|
allow(order).to receive_messages(resumed?: false, complete?: false)
|
|
expect(order.can_ship?).to be_falsy
|
|
end
|
|
end
|
|
|
|
context "checking if order is paid" do
|
|
context "payment_state is paid" do
|
|
before { allow(order).to receive_messages payment_state: 'paid' }
|
|
it { expect(order).to be_paid }
|
|
end
|
|
|
|
context "payment_state is credit_owned" do
|
|
before { allow(order).to receive_messages payment_state: 'credit_owed' }
|
|
it { expect(order).to be_paid }
|
|
end
|
|
end
|
|
|
|
context "#finalize!" do
|
|
let(:order) { Spree::Order.create }
|
|
it "should set completed_at" do
|
|
expect(order).to receive(:touch).with(:completed_at)
|
|
order.finalize!
|
|
end
|
|
|
|
it "should sell inventory units" do
|
|
order.shipments.each do |shipment|
|
|
expect(shipment).to receive(:update!)
|
|
expect(shipment).to receive(:finalize!)
|
|
end
|
|
order.finalize!
|
|
end
|
|
|
|
it "should decrease the stock for each variant in the shipment" do
|
|
order.shipments.each do |shipment|
|
|
expect(shipment.stock_location).to receive(:decrease_stock_for_variant)
|
|
end
|
|
order.finalize!
|
|
end
|
|
|
|
it "should change the shipment state to ready if order is paid" do
|
|
Spree::Shipment.create(order: order)
|
|
order.shipments.reload
|
|
|
|
allow(order).to receive_messages(paid?: true, complete?: true)
|
|
order.finalize!
|
|
order.reload # reload so we're sure the changes are persisted
|
|
expect(order.shipment_state).to eq 'ready'
|
|
end
|
|
|
|
it "sends confirmation emails to both the user and the shop owner" do
|
|
mailer = double(:mailer, deliver_later: true)
|
|
|
|
expect(Spree::OrderMailer).to receive(:confirm_email_for_customer).and_return(mailer)
|
|
expect(Spree::OrderMailer).to receive(:confirm_email_for_shop).and_return(mailer)
|
|
|
|
order.finalize!
|
|
end
|
|
|
|
it "should freeze all adjustments" do
|
|
allow(Spree::OrderMailer).to receive_message_chain :confirm_email, :deliver_later
|
|
adjustments = double
|
|
allow(order).to receive_messages all_adjustments: adjustments
|
|
expect(adjustments).to receive(:update_all).with(state: 'closed')
|
|
order.finalize!
|
|
end
|
|
|
|
it "should log state event" do
|
|
expect(order.state_changes).to receive(:create).exactly(3).times # order, shipment & payment state changes
|
|
order.finalize!
|
|
end
|
|
|
|
it 'calls updater#before_save' do
|
|
expect(order.updater).to receive(:before_save_hook)
|
|
order.finalize!
|
|
end
|
|
end
|
|
|
|
context "#process_payments!" do
|
|
let(:payment) { build(:payment) }
|
|
before { allow(order).to receive_messages pending_payments: [payment], total: 10 }
|
|
|
|
it "should return false if no pending_payments available" do
|
|
allow(order).to receive_messages pending_payments: []
|
|
expect(order.process_payments!).to be_falsy
|
|
end
|
|
|
|
context "when the processing is sucessful" do
|
|
it "should process the payments" do
|
|
expect(payment).to receive(:process!)
|
|
expect(order.process_payments!).to be_truthy
|
|
end
|
|
|
|
it "stores the payment total on the order" do
|
|
allow(payment).to receive(:process!)
|
|
allow(payment).to receive(:completed?).and_return(true)
|
|
|
|
order.process_payments!
|
|
|
|
expect(order.payment_total).to eq(payment.amount)
|
|
end
|
|
end
|
|
|
|
context "when a payment raises a GatewayError" do
|
|
before { expect(payment).to receive(:process!).and_raise(Spree::Core::GatewayError) }
|
|
|
|
it "should return true when configured to allow checkout on gateway failures" do
|
|
Spree::Config.set allow_checkout_on_gateway_error: true
|
|
expect(order.process_payments!).to be_truthy
|
|
end
|
|
|
|
it "should return false when not configured to allow checkout on gateway failures" do
|
|
Spree::Config.set allow_checkout_on_gateway_error: false
|
|
expect(order.process_payments!).to be_falsy
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#completed?" do
|
|
it "should indicate if order is completed" do
|
|
order.completed_at = nil
|
|
expect(order.completed?).to be_falsy
|
|
|
|
order.completed_at = Time.zone.now
|
|
expect(order.completed?).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "#allow_checkout?" do
|
|
it "should be true if there are line_items in the order" do
|
|
allow(order).to receive_message_chain(:line_items, count: 1)
|
|
expect(order.checkout_allowed?).to be_truthy
|
|
end
|
|
it "should be false if there are no line_items in the order" do
|
|
allow(order).to receive_message_chain(:line_items, count: 0)
|
|
expect(order.checkout_allowed?).to be_falsy
|
|
end
|
|
end
|
|
|
|
context "#amount" do
|
|
before do
|
|
@order = create(:order, user: user)
|
|
@order.line_items = [create(:line_item, price: 1.0, quantity: 2),
|
|
create(:line_item, price: 1.0, quantity: 1)]
|
|
end
|
|
it "should return the correct lum sum of items" do
|
|
expect(@order.amount).to eq 3.0
|
|
end
|
|
end
|
|
|
|
context "#can_cancel?" do
|
|
it "should be false for completed order in the canceled state" do
|
|
order.state = 'canceled'
|
|
order.shipment_state = 'ready'
|
|
order.completed_at = Time.zone.now
|
|
expect(order.can_cancel?).to be_falsy
|
|
end
|
|
|
|
it "should be true for completed order with no shipment" do
|
|
order.state = 'complete'
|
|
order.shipment_state = nil
|
|
order.completed_at = Time.zone.now
|
|
expect(order.can_cancel?).to be_truthy
|
|
end
|
|
|
|
context "with a soft-deleted product" do
|
|
let(:order) { create(:completed_order_with_totals) }
|
|
|
|
it "should cancel the order without error" do
|
|
order.line_items.first.variant.product.tap(&:destroy)
|
|
order.cancel!
|
|
expect(Spree::Order.by_state(:canceled)).to include order
|
|
end
|
|
end
|
|
end
|
|
|
|
context "insufficient_stock_lines" do
|
|
let(:line_item) { build(:line_item) }
|
|
|
|
before do
|
|
allow(order).to receive_messages(line_items: [line_item])
|
|
allow(line_item).to receive(:insufficient_stock?) { true }
|
|
end
|
|
|
|
it "should return line_item that has insufficient stock on hand" do
|
|
expect(order.insufficient_stock_lines.size).to eq 1
|
|
expect(order.insufficient_stock_lines.include?(line_item)).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "empty!" do
|
|
it "should clear out all line items and adjustments" do
|
|
order = build(:order)
|
|
allow(order).to receive_messages(line_items: line_items = [])
|
|
allow(order).to receive_messages(adjustments: adjustments = [])
|
|
expect(order.line_items).to receive(:destroy_all)
|
|
expect(order.adjustments).to receive(:destroy_all)
|
|
|
|
order.empty!
|
|
end
|
|
end
|
|
|
|
context "#display_outstanding_balance" do
|
|
it "returns the value as a spree money" do
|
|
allow(order).to receive(:new_outstanding_balance) { 10.55 }
|
|
expect(order.display_outstanding_balance).to eq Spree::Money.new(10.55)
|
|
end
|
|
end
|
|
|
|
context "#display_item_total" do
|
|
it "returns the value as a spree money" do
|
|
allow(order).to receive(:item_total) { 10.55 }
|
|
expect(order.display_item_total).to eq Spree::Money.new(10.55)
|
|
end
|
|
end
|
|
|
|
context "#display_adjustment_total" do
|
|
it "returns the value as a spree money" do
|
|
order.adjustment_total = 10.55
|
|
expect(order.display_adjustment_total).to eq Spree::Money.new(10.55)
|
|
end
|
|
end
|
|
|
|
context "#display_total" do
|
|
it "returns the value as a spree money" do
|
|
order.total = 10.55
|
|
expect(order.display_total).to eq Spree::Money.new(10.55)
|
|
end
|
|
end
|
|
|
|
context "#currency" do
|
|
context "when object currency is ABC" do
|
|
before { order.currency = "ABC" }
|
|
|
|
it "returns the currency from the object" do
|
|
expect(order.currency).to eq "ABC"
|
|
end
|
|
end
|
|
|
|
context "when object currency is nil" do
|
|
before { order.currency = nil }
|
|
|
|
it "returns the globally configured currency" do
|
|
expect(order.currency).to eq Spree::Config[:currency]
|
|
end
|
|
end
|
|
end
|
|
|
|
# Regression test for Spree #2191
|
|
context "when an order has an adjustment that zeroes the total, but another adjustment for shipping that raises it above zero" do
|
|
let!(:persisted_order) { create(:order) }
|
|
let!(:line_item) { create(:line_item) }
|
|
let!(:shipping_method) do
|
|
sm = create(:shipping_method)
|
|
sm.calculator.preferred_amount = 10
|
|
sm.save
|
|
sm
|
|
end
|
|
|
|
before do
|
|
persisted_order.line_items << line_item
|
|
persisted_order.adjustments.create(amount: -line_item.amount, label: "Promotion")
|
|
persisted_order.state = 'delivery'
|
|
persisted_order.save # To ensure new state_change event
|
|
end
|
|
|
|
it "transitions from delivery to payment" do
|
|
allow(persisted_order).to receive_messages(payment_required?: true)
|
|
persisted_order.next!
|
|
expect(persisted_order.state).to eq "payment"
|
|
end
|
|
end
|
|
|
|
context "payment required?" do
|
|
let(:order) { Spree::Order.new }
|
|
|
|
context "total is zero" do
|
|
it { expect(order.payment_required?).to be_falsy }
|
|
end
|
|
|
|
context "total > zero" do
|
|
before { allow(order).to receive_messages(total: 1) }
|
|
it { expect(order.payment_required?).to be_truthy }
|
|
end
|
|
end
|
|
|
|
describe ".tax_address" do
|
|
before { Spree::Config[:tax_using_ship_address] = tax_using_ship_address }
|
|
subject { order.tax_address }
|
|
|
|
context "when tax_using_ship_address is true" do
|
|
let(:tax_using_ship_address) { true }
|
|
|
|
it 'returns ship_address' do
|
|
expect(subject).to eq order.ship_address
|
|
end
|
|
end
|
|
|
|
context "when tax_using_ship_address is not true" do
|
|
let(:tax_using_ship_address) { false }
|
|
|
|
it "returns bill_address" do
|
|
expect(subject).to eq order.bill_address
|
|
end
|
|
end
|
|
end
|
|
|
|
context '#updater' do
|
|
it 'returns an OrderManagement::Order::Updater' do
|
|
expect(order.updater.class).to eq OrderManagement::Order::Updater
|
|
end
|
|
end
|
|
|
|
describe "email validation" do
|
|
let(:order) { build(:order) }
|
|
|
|
it "has errors if email is blank" do
|
|
allow(order).to receive_messages(require_email: true)
|
|
order.email = ""
|
|
|
|
order.valid?
|
|
expect(order.errors[:email]).to eq ["can't be blank", "is invalid"]
|
|
end
|
|
|
|
it "has errors if email is invalid" do
|
|
allow(order).to receive_messages(require_email: true)
|
|
order.email = "invalid_email"
|
|
|
|
order.valid?
|
|
expect(order.errors[:email]).to eq ["is invalid"]
|
|
end
|
|
|
|
it "has errors if email has invalid domain" do
|
|
allow(order).to receive_messages(require_email: true)
|
|
order.email = "single_letter_tld@domain.z"
|
|
|
|
order.valid?
|
|
expect(order.errors[:email]).to eq ["is invalid"]
|
|
end
|
|
|
|
it "is valid if email is valid" do
|
|
allow(order).to receive_messages(require_email: true)
|
|
order.email = "a@b.ca"
|
|
|
|
order.valid?
|
|
expect(order.errors[:email]).to eq []
|
|
end
|
|
end
|
|
|
|
describe "applying enterprise fees" do
|
|
subject { create(:order) }
|
|
let(:fee_handler) { ::OrderFeesHandler.new(subject) }
|
|
|
|
before do
|
|
allow(subject).to receive(:fee_handler) { fee_handler }
|
|
allow(subject).to receive(:update_order!)
|
|
end
|
|
|
|
it "clears all enterprise fee adjustments on the order" do
|
|
expect(EnterpriseFee).to receive(:clear_all_adjustments).with(subject)
|
|
subject.recreate_all_fees!
|
|
end
|
|
|
|
it "creates line item and order fee adjustments via OrderFeesHandler" do
|
|
expect(fee_handler).to receive(:create_line_item_fees!)
|
|
expect(fee_handler).to receive(:create_order_fees!)
|
|
subject.recreate_all_fees!
|
|
end
|
|
|
|
it "skips order cycle per-order adjustments for orders that don't have an order cycle" do
|
|
allow(EnterpriseFee).to receive(:clear_all_adjustments)
|
|
|
|
allow(subject).to receive(:order_cycle) { nil }
|
|
|
|
subject.recreate_all_fees!
|
|
end
|
|
|
|
it "ensures the correct adjustment(s) are created for order cycles" do
|
|
allow(EnterpriseFee).to receive(:clear_all_adjustments)
|
|
line_item = create(:line_item, order: subject)
|
|
allow(fee_handler).to receive(:provided_by_order_cycle?) { true }
|
|
|
|
order_cycle = double(:order_cycle)
|
|
expect_any_instance_of(OpenFoodNetwork::EnterpriseFeeCalculator).
|
|
to receive(:create_line_item_adjustments_for).
|
|
with(line_item)
|
|
allow_any_instance_of(OpenFoodNetwork::EnterpriseFeeCalculator).to receive(:create_order_adjustments_for)
|
|
allow(subject).to receive(:order_cycle) { order_cycle }
|
|
|
|
subject.recreate_all_fees!
|
|
end
|
|
|
|
it "ensures the correct per-order adjustment(s) are created for order cycles" do
|
|
allow(EnterpriseFee).to receive(:clear_all_adjustments)
|
|
|
|
order_cycle = double(:order_cycle)
|
|
expect_any_instance_of(OpenFoodNetwork::EnterpriseFeeCalculator).
|
|
to receive(:create_order_adjustments_for).
|
|
with(subject)
|
|
|
|
allow(subject).to receive(:order_cycle) { order_cycle }
|
|
|
|
subject.recreate_all_fees!
|
|
end
|
|
end
|
|
|
|
describe "getting the admin and handling charge" do
|
|
let(:o) { create(:order) }
|
|
let(:li) { create(:line_item, order: o) }
|
|
|
|
it "returns the sum of eligible enterprise fee adjustments" do
|
|
ef = create(:enterprise_fee, calculator: Calculator::FlatRate.new )
|
|
ef.calculator.set_preference :amount, 123.45
|
|
a = ef.create_adjustment("adjustment", o, true)
|
|
|
|
expect(o.admin_and_handling_total).to eq(123.45)
|
|
end
|
|
|
|
it "does not include ineligible adjustments" do
|
|
ef = create(:enterprise_fee, calculator: Calculator::FlatRate.new )
|
|
ef.calculator.set_preference :amount, 123.45
|
|
a = ef.create_adjustment("adjustment", o, true)
|
|
|
|
a.update_column :eligible, false
|
|
|
|
expect(o.admin_and_handling_total).to eq(0)
|
|
end
|
|
|
|
it "does not include adjustments that do not originate from enterprise fees" do
|
|
sm = create(:shipping_method, calculator: Calculator::FlatRate.new )
|
|
sm.calculator.set_preference :amount, 123.45
|
|
sm.create_adjustment("adjustment", o, true)
|
|
|
|
expect(o.admin_and_handling_total).to eq(0)
|
|
end
|
|
|
|
it "does not include adjustments whose source is a line item" do
|
|
ef = create(:enterprise_fee, calculator: Calculator::PerItem.new )
|
|
ef.calculator.set_preference :amount, 123.45
|
|
ef.create_adjustment("adjustment", li, true)
|
|
|
|
expect(o.admin_and_handling_total).to eq(0)
|
|
end
|
|
end
|
|
|
|
describe "an order without shipping method" do
|
|
let(:order) { create(:order) }
|
|
|
|
it "cannot be shipped" do
|
|
expect(order.ready_to_ship?).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe "an unpaid order with a shipment" do
|
|
let(:order) { create(:order_with_totals, shipments: [create(:shipment)]) }
|
|
|
|
before do
|
|
order.reload
|
|
order.state = 'complete'
|
|
order.shipment.update!(order)
|
|
end
|
|
|
|
it "cannot be shipped" do
|
|
expect(order.ready_to_ship?).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe "a paid order without a shipment" do
|
|
let(:order) { create(:order) }
|
|
|
|
before do
|
|
order.payment_state = 'paid'
|
|
order.state = 'complete'
|
|
end
|
|
|
|
it "cannot be shipped" do
|
|
expect(order.ready_to_ship?).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe "a paid order with a shipment" do
|
|
let(:order) { create(:order_with_line_items) }
|
|
|
|
before do
|
|
order.payment_state = 'paid'
|
|
order.state = 'complete'
|
|
order.shipment.update!(order)
|
|
end
|
|
|
|
it "can be shipped" do
|
|
expect(order.ready_to_ship?).to eq(true)
|
|
end
|
|
end
|
|
|
|
describe "getting the shipping tax" do
|
|
let(:order) { create(:order) }
|
|
let(:shipping_tax_rate) {
|
|
create(:tax_rate, amount: 0.25, included_in_price: true, zone: create(:zone_with_member))
|
|
}
|
|
let(:shipping_tax_category) { create(:tax_category, tax_rates: [shipping_tax_rate]) }
|
|
let!(:shipping_method) {
|
|
create(:shipping_method_with, :flat_rate, tax_category: shipping_tax_category)
|
|
}
|
|
|
|
context "with a taxed shipment" do
|
|
let!(:shipment) {
|
|
create(:shipment_with, :shipping_method, shipping_method: shipping_method, order: order)
|
|
}
|
|
|
|
before do
|
|
allow(order).to receive(:tax_zone) { shipping_tax_rate.zone }
|
|
order.reload
|
|
order.create_tax_charge!
|
|
end
|
|
|
|
it "returns the shipping tax" do
|
|
expect(order.shipping_tax).to eq(10)
|
|
end
|
|
end
|
|
|
|
context 'when the order has not been shipped' do
|
|
it "returns zero when the order has not been shipped" do
|
|
expect(order.shipping_tax).to eq(0)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#enterprise_fee_tax" do
|
|
let!(:order) { create(:order) }
|
|
let(:enterprise_fee) { create(:enterprise_fee) }
|
|
let!(:fee_adjustment) {
|
|
create(:adjustment, adjustable: order, originator: enterprise_fee,
|
|
amount: 100, order: order, state: "closed")
|
|
}
|
|
let!(:fee_tax1) {
|
|
create(:adjustment, adjustable: fee_adjustment, originator_type: "Spree::TaxRate",
|
|
amount: 12.3, order: order, state: "closed")
|
|
}
|
|
let!(:fee_tax2) {
|
|
create(:adjustment, adjustable: fee_adjustment, originator_type: "Spree::TaxRate",
|
|
amount: 4.5, order: order, state: "closed")
|
|
}
|
|
let!(:admin_adjustment) {
|
|
create(:adjustment, adjustable: order, originator: nil,
|
|
amount: 6.7, order: order, state: "closed")
|
|
}
|
|
|
|
it "returns a sum of all taxes on enterprise fees" do
|
|
expect(order.reload.enterprise_fee_tax).to eq(16.8)
|
|
end
|
|
end
|
|
|
|
describe "getting the total tax" do
|
|
let(:shipping_tax_rate) { create(:tax_rate, amount: 0.25) }
|
|
let(:fee_tax_rate) { create(:tax_rate, amount: 0.10) }
|
|
let(:order) { create(:order) }
|
|
let(:shipping_method) { create(:shipping_method_with, :flat_rate) }
|
|
let!(:shipment) do
|
|
create(:shipment_with, :shipping_method, shipping_method: shipping_method, order: order)
|
|
end
|
|
let(:enterprise_fee) { create(:enterprise_fee) }
|
|
let!(:fee) {
|
|
create(:adjustment, adjustable: order, originator: enterprise_fee, label: "EF", amount: 20,
|
|
order: order)
|
|
}
|
|
let!(:fee_tax) {
|
|
create(:adjustment, adjustable: fee, originator: fee_tax_rate,
|
|
amount: 2, order: order, state: "closed")
|
|
}
|
|
let!(:shipping_tax) {
|
|
create(:adjustment, adjustable: shipment, originator: shipping_tax_rate,
|
|
amount: 10, order: order, state: "closed")
|
|
}
|
|
|
|
before do
|
|
order.update_order!
|
|
end
|
|
|
|
it "returns a sum of all tax on the order" do
|
|
# 12 = 2 (of the enterprise fee adjustment) + 10 (of the shipment adjustment)
|
|
expect(order.total_tax).to eq(12)
|
|
end
|
|
end
|
|
|
|
describe "setting the distributor" do
|
|
it "sets the distributor when no order cycle is set" do
|
|
d = create(:distributor_enterprise)
|
|
subject.set_distributor! d
|
|
expect(subject.distributor).to eq(d)
|
|
end
|
|
|
|
it "keeps the order cycle when it is available at the new distributor" do
|
|
d = create(:distributor_enterprise)
|
|
oc = create(:simple_order_cycle)
|
|
create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d, incoming: false)
|
|
|
|
subject.order_cycle = oc
|
|
subject.set_distributor! d
|
|
|
|
expect(subject.distributor).to eq(d)
|
|
expect(subject.order_cycle).to eq(oc)
|
|
end
|
|
|
|
it "clears the order cycle if it is not available at that distributor" do
|
|
d = create(:distributor_enterprise)
|
|
oc = create(:simple_order_cycle)
|
|
|
|
subject.order_cycle = oc
|
|
subject.set_distributor! d
|
|
|
|
expect(subject.distributor).to eq(d)
|
|
expect(subject.order_cycle).to be_nil
|
|
end
|
|
|
|
it "clears the distributor when setting to nil" do
|
|
d = create(:distributor_enterprise)
|
|
subject.set_distributor! d
|
|
subject.set_distributor! nil
|
|
|
|
expect(subject.distributor).to be_nil
|
|
end
|
|
end
|
|
|
|
describe "emptying the order" do
|
|
it "removes shipments" do
|
|
subject.shipments << create(:shipment)
|
|
subject.save!
|
|
subject.empty!
|
|
expect(subject.shipments).to be_empty
|
|
end
|
|
|
|
it "removes payments" do
|
|
subject.payments << create(:payment)
|
|
subject.save!
|
|
subject.empty!
|
|
expect(subject.payments).to be_empty
|
|
end
|
|
end
|
|
|
|
describe "setting the order cycle" do
|
|
let(:oc) { create(:simple_order_cycle) }
|
|
|
|
it "empties the cart when changing the order cycle" do
|
|
expect(subject).to receive(:empty!)
|
|
subject.set_order_cycle! oc
|
|
end
|
|
|
|
it "doesn't empty the cart if the order cycle is not different" do
|
|
expect(subject).not_to receive(:empty!)
|
|
subject.set_order_cycle! subject.order_cycle
|
|
end
|
|
|
|
it "sets the order cycle when no distributor is set" do
|
|
subject.set_order_cycle! oc
|
|
expect(subject.order_cycle).to eq(oc)
|
|
end
|
|
|
|
it "keeps the distributor when it is available in the new order cycle" do
|
|
d = create(:distributor_enterprise)
|
|
create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d, incoming: false)
|
|
|
|
subject.distributor = d
|
|
subject.set_order_cycle! oc
|
|
|
|
expect(subject.order_cycle).to eq(oc)
|
|
expect(subject.distributor).to eq(d)
|
|
end
|
|
|
|
it "clears the distributor if it is not available at that order cycle" do
|
|
d = create(:distributor_enterprise)
|
|
|
|
subject.distributor = d
|
|
subject.set_order_cycle! oc
|
|
|
|
expect(subject.order_cycle).to eq(oc)
|
|
expect(subject.distributor).to be_nil
|
|
end
|
|
|
|
it "clears the order cycle when setting to nil" do
|
|
d = create(:distributor_enterprise)
|
|
subject.set_order_cycle! oc
|
|
subject.distributor = d
|
|
|
|
subject.set_order_cycle! nil
|
|
|
|
expect(subject.order_cycle).to be_nil
|
|
expect(subject.distributor).to eq(d)
|
|
end
|
|
end
|
|
|
|
context "change distributor and order cycle" do
|
|
let(:variant1) { create(:product).variants.first }
|
|
let(:variant2) { create(:product).variants.first }
|
|
let(:distributor) { create(:enterprise) }
|
|
|
|
before do
|
|
subject.order_cycle = create(:simple_order_cycle, distributors: [distributor],
|
|
variants: [variant1, variant2])
|
|
subject.distributor = distributor
|
|
|
|
line_item1 = create(:line_item, order: subject, variant: variant1)
|
|
line_item2 = create(:line_item, order: subject, variant: variant2)
|
|
subject.reload
|
|
subject.line_items = [line_item1, line_item2]
|
|
end
|
|
|
|
it "allows the change when all variants in the order are provided by the new distributor in the new order cycle" do
|
|
new_distributor = create(:enterprise)
|
|
new_order_cycle = create(:simple_order_cycle, distributors: [new_distributor],
|
|
variants: [variant1, variant2])
|
|
|
|
subject.distributor = new_distributor
|
|
expect(subject).not_to be_valid
|
|
subject.order_cycle = new_order_cycle
|
|
expect(subject).to be_valid
|
|
end
|
|
|
|
it "does not allow the change when not all variants in the order are provided by the new distributor" do
|
|
new_distributor = create(:enterprise)
|
|
create(:simple_order_cycle, distributors: [new_distributor], variants: [variant1])
|
|
|
|
subject.distributor = new_distributor
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors.messages).to eq(base: ["Distributor or order cycle cannot supply the products in your cart"])
|
|
end
|
|
end
|
|
|
|
describe "scopes" do
|
|
describe "not_state" do
|
|
before do
|
|
setup_email
|
|
end
|
|
|
|
it "finds only orders not in specified state" do
|
|
o = FactoryBot.create(:completed_order_with_totals,
|
|
distributor: create(:distributor_enterprise))
|
|
o.cancel!
|
|
expect(Spree::Order.not_state(:canceled)).not_to include o
|
|
end
|
|
end
|
|
|
|
describe "not_empty" do
|
|
let!(:order_with_line_items) { create(:order_with_line_items, line_items_count: 1) }
|
|
let!(:order_without_line_items) { create(:order) }
|
|
|
|
it "returns only orders which have line items" do
|
|
expect(Spree::Order.not_empty).to include order_with_line_items
|
|
expect(Spree::Order.not_empty).to_not include order_without_line_items
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "sending confirmation emails" do
|
|
let!(:distributor) { create(:distributor_enterprise) }
|
|
let!(:order) { create(:order, distributor: distributor) }
|
|
|
|
it "sends confirmation emails" do
|
|
mailer = double(:mailer, deliver_later: true)
|
|
expect(Spree::OrderMailer).to receive(:confirm_email_for_customer).and_return(mailer)
|
|
expect(Spree::OrderMailer).to receive(:confirm_email_for_shop).and_return(mailer)
|
|
|
|
order.deliver_order_confirmation_email
|
|
end
|
|
|
|
it "does not send confirmation emails when the order belongs to a subscription" do
|
|
create(:proxy_order, order: order)
|
|
|
|
expect(Spree::OrderMailer).not_to receive(:confirm_email_for_customer)
|
|
expect(Spree::OrderMailer).not_to receive(:confirm_email_for_shop)
|
|
|
|
order.deliver_order_confirmation_email
|
|
end
|
|
end
|
|
|
|
describe "#require_customer?" do
|
|
context "when the record is new" do
|
|
let(:order) { build(:order, state: state) }
|
|
|
|
context "and the state is not cart" do
|
|
let(:state) { "complete" }
|
|
|
|
it "returns false" do
|
|
expect(order.send(:require_customer?)).to eq(false)
|
|
end
|
|
end
|
|
|
|
context "and the state is cart" do
|
|
let(:state) { "cart" }
|
|
|
|
it "returns false" do
|
|
expect(order.send(:require_customer?)).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the record is not new" do
|
|
let(:order) { create(:order, state: state) }
|
|
|
|
context "and the state is not cart" do
|
|
let(:state) { "complete" }
|
|
|
|
it "returns true" do
|
|
expect(order.send(:require_customer?)).to eq(true)
|
|
end
|
|
end
|
|
|
|
context "and the state is cart" do
|
|
let(:state) { "cart" }
|
|
|
|
it "returns false" do
|
|
expect(order.send(:require_customer?)).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "associating a customer" do
|
|
let(:distributor) { create(:distributor_enterprise) }
|
|
|
|
context "when creating an order" do
|
|
it "does not create a customer" do
|
|
expect {
|
|
create(:order, distributor: distributor)
|
|
}.to_not change {
|
|
Customer.count
|
|
}
|
|
end
|
|
|
|
it "associates an existing customer" do
|
|
customer = create(
|
|
:customer,
|
|
user: user,
|
|
email: user.email,
|
|
enterprise: distributor
|
|
)
|
|
order = create(:order, user: user, distributor: distributor)
|
|
|
|
expect(order.customer).to eq customer
|
|
end
|
|
end
|
|
|
|
context "when updating the order" do
|
|
before do
|
|
order.update!(distributor: distributor)
|
|
end
|
|
|
|
it "associates an existing customer" do
|
|
customer = create(
|
|
:customer,
|
|
user: user,
|
|
email: user.email,
|
|
enterprise: distributor
|
|
)
|
|
|
|
order.update!(state: "complete")
|
|
|
|
expect(order.customer).to eq customer
|
|
end
|
|
|
|
it "doesn't create a customer before needed" do
|
|
expect(order.customer).to eq nil
|
|
end
|
|
|
|
it "creates a customer" do
|
|
expect {
|
|
order.update!(state: "complete")
|
|
}.to change {
|
|
Customer.count
|
|
}.by(1)
|
|
|
|
expect(order.customer).to be_present
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "when a guest order is placed with a registered email" do
|
|
let(:order) { create(:order_with_totals_and_distribution, user: user) }
|
|
let(:payment_method) { create(:payment_method, distributors: [order.distributor]) }
|
|
let(:shipping_method) { create(:shipping_method, distributors: [order.distributor]) }
|
|
let(:user) { create(:user, email: 'registered@email.com') }
|
|
|
|
before do
|
|
order.bill_address = create(:address)
|
|
order.ship_address = create(:address)
|
|
order.email = user.email
|
|
order.user = nil
|
|
order.state = 'cart'
|
|
end
|
|
|
|
it "returns a validation error" do
|
|
expect{ order.next }.to change(order.errors, :count).from(0).to(1)
|
|
expect(order.errors.messages[:email]).to eq [I18n.t('devise.failure.already_registered')]
|
|
expect(order.state).to eq 'cart'
|
|
end
|
|
end
|
|
|
|
describe "a completed order with shipping and transaction fees" do
|
|
let(:distributor) { create(:distributor_enterprise_with_tax) }
|
|
let(:zone) { create(:zone_with_member) }
|
|
let(:shipping_tax_rate) { create(:tax_rate, amount: 0.25, included_in_price: true, zone: zone) }
|
|
let(:shipping_tax_category) { create(:tax_category, tax_rates: [shipping_tax_rate]) }
|
|
let(:order) {
|
|
create(:completed_order_with_fees, distributor: distributor, shipping_fee: shipping_fee,
|
|
payment_fee: payment_fee,
|
|
shipping_tax_category: shipping_tax_category)
|
|
}
|
|
let(:shipping_fee) { 3 }
|
|
let(:payment_fee) { 5 }
|
|
let(:item_num) { order.line_items.length }
|
|
let(:expected_fees) { item_num * (shipping_fee + payment_fee) }
|
|
|
|
before do
|
|
order.reload
|
|
order.create_tax_charge!
|
|
|
|
# Sanity check the fees
|
|
expect(order.all_adjustments.length).to eq 3
|
|
expect(order.shipment_adjustments.length).to eq 2
|
|
expect(item_num).to eq 2
|
|
expect(order.adjustment_total).to eq expected_fees
|
|
expect(order.shipment.adjustments.tax.inclusive.sum(:amount)).to eq 1.2
|
|
expect(order.shipment.included_tax_total).to eq 1.2
|
|
end
|
|
|
|
context "removing line_items" do
|
|
it "updates shipping and transaction fees" do
|
|
order.line_items.first.update_attribute(:quantity, 0)
|
|
order.save
|
|
|
|
expect(order.adjustment_total).to eq expected_fees - shipping_fee - payment_fee
|
|
expect(order.shipment.adjustments.tax.inclusive.sum(:amount)).to eq 0.6
|
|
expect(order.shipment.included_tax_total).to eq 0.6
|
|
end
|
|
|
|
context "when finalized fee adjustments exist on the order" do
|
|
before do
|
|
order.all_adjustments.each(&:finalize!)
|
|
order.reload
|
|
end
|
|
|
|
it "does not attempt to update such adjustments" do
|
|
order.update(line_items_attributes: [{ id: order.line_items.first.id, quantity: 0 }])
|
|
|
|
# Check if fees got updated
|
|
order.reload
|
|
|
|
expect(order.adjustment_total).to eq expected_fees
|
|
expect(order.shipment.adjustments.tax.inclusive.sum(:amount)).to eq 1.2
|
|
expect(order.shipment.included_tax_total).to eq 1.2
|
|
end
|
|
end
|
|
end
|
|
|
|
context "changing the shipping method to one without fees" do
|
|
let(:shipping_method) {
|
|
create(:shipping_method, calculator: Calculator::FlatRate.new(preferred_amount: 0))
|
|
}
|
|
|
|
it "updates shipping fees" do
|
|
order.shipments = [create(:shipment_with, :shipping_method,
|
|
shipping_method: shipping_method)]
|
|
order.save
|
|
|
|
expect(order.adjustment_total).to eq expected_fees - (item_num * shipping_fee)
|
|
expect(order.shipment.adjustments.tax.inclusive.sum(:amount)).to eq 0
|
|
expect(order.shipment.included_tax_total).to eq 0
|
|
end
|
|
end
|
|
|
|
context "changing the payment method to one without fees" do
|
|
let(:payment_method) {
|
|
create(:payment_method, calculator: Calculator::FlatRate.new(preferred_amount: 0))
|
|
}
|
|
|
|
it "removes transaction fees" do
|
|
# Change the payment method
|
|
order.payments.first.update(payment_method_id: payment_method.id)
|
|
order.save
|
|
|
|
# Check if fees got updated
|
|
order.reload
|
|
expect(order.adjustment_total).to eq expected_fees - (item_num * payment_fee)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "retrieving previously ordered items" do
|
|
let(:distributor) { create(:distributor_enterprise) }
|
|
let(:order_cycle) { create(:simple_order_cycle) }
|
|
let!(:order) { create(:order, distributor: distributor, order_cycle: order_cycle) }
|
|
|
|
it "returns no items if nothing has been ordered" do
|
|
expect(order.finalised_line_items).to eq []
|
|
end
|
|
|
|
context "when no order has been finalised in this order cycle" do
|
|
let(:product) { create(:product) }
|
|
|
|
before do
|
|
order.contents.update_or_create(product.variants.first, { quantity: 1, max_quantity: 3 })
|
|
end
|
|
|
|
it "returns no items even though the cart contains items" do
|
|
expect(order.finalised_line_items).to eq []
|
|
end
|
|
end
|
|
|
|
context "when an order has been finalised in this order cycle" do
|
|
let!(:prev_order) {
|
|
create(:completed_order_with_totals, distributor: distributor, order_cycle: order_cycle,
|
|
user: order.user)
|
|
}
|
|
let!(:prev_order2) {
|
|
create(:completed_order_with_totals, distributor: distributor, order_cycle: order_cycle,
|
|
user: order.user)
|
|
}
|
|
let(:product) { create(:product) }
|
|
|
|
before do
|
|
prev_order.contents.update_or_create(product.variants.first,
|
|
{ quantity: 1, max_quantity: 3 })
|
|
prev_order2.reload # to get the right response from line_items
|
|
end
|
|
|
|
it "returns previous items" do
|
|
expect(order.finalised_line_items.length).to eq 11
|
|
expect(order.finalised_line_items).to match_array(prev_order.line_items + prev_order2.line_items)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "determining checkout steps for an order" do
|
|
let!(:enterprise) { create(:enterprise) }
|
|
let!(:order) { create(:order, distributor: enterprise) }
|
|
let!(:payment_method) {
|
|
create(:stripe_sca_payment_method, distributor_ids: [enterprise.id])
|
|
}
|
|
let!(:payment) { create(:payment, order: order, payment_method: payment_method) }
|
|
|
|
it "does not include the :confirm step" do
|
|
expect(order.checkout_steps).to_not include "confirm"
|
|
end
|
|
end
|
|
|
|
describe "payments" do
|
|
let(:payment_method) { create(:payment_method) }
|
|
let(:shipping_method) { create(:shipping_method) }
|
|
let(:order) { create(:order_with_totals_and_distribution) }
|
|
|
|
before { order.update_totals }
|
|
|
|
context "when the order is not a subscription" do
|
|
it "it requires a payment" do
|
|
expect(order.payment_required?).to be true
|
|
end
|
|
|
|
it "advances to payment state" do
|
|
advance_to_delivery_state(order)
|
|
|
|
expect { order.next! }.to change { order.state }.from("delivery").to("payment")
|
|
end
|
|
|
|
# Regression test for https://github.com/openfoodfoundation/openfoodnetwork/issues/3924
|
|
it "advances to complete state without error" do
|
|
advance_to_delivery_state(order)
|
|
order.next!
|
|
order.payments << create(:payment, order: order)
|
|
|
|
expect { order.next! }.to change { order.state }.from("payment").to("complete")
|
|
end
|
|
end
|
|
|
|
context "when the order is a subscription" do
|
|
let!(:proxy_order) { create(:proxy_order, order: order) }
|
|
let!(:order_cycle) { proxy_order.order_cycle }
|
|
|
|
context "and order_cycle has no order_close_at set" do
|
|
before { order.order_cycle.update(orders_close_at: nil) }
|
|
|
|
it "requires a payment" do
|
|
expect(order.payment_required?).to be true
|
|
end
|
|
end
|
|
|
|
context "and the order_cycle has closed" do
|
|
before { order.order_cycle.update(orders_close_at: 5.minutes.ago) }
|
|
|
|
it "returns the payments on the order" do
|
|
expect(order.payment_required?).to be true
|
|
end
|
|
end
|
|
|
|
context "and the order_cycle has not yet closed" do
|
|
before { order.order_cycle.update(orders_close_at: 5.minutes.from_now) }
|
|
|
|
it "returns an empty array" do
|
|
expect(order.payment_required?).to be false
|
|
end
|
|
|
|
it "skips the payment state" do
|
|
advance_to_delivery_state(order)
|
|
|
|
expect { order.next! }.to change { order.state }.from("delivery").to("complete")
|
|
end
|
|
end
|
|
end
|
|
|
|
def advance_to_delivery_state(order)
|
|
# advance to address state
|
|
order.ship_address = create(:address)
|
|
order.next!
|
|
expect(order.state).to eq "address"
|
|
|
|
# advance to delivery state
|
|
order.next!
|
|
expect(order.state).to eq "delivery"
|
|
end
|
|
end
|
|
|
|
describe '#restart_checkout!' do
|
|
context 'when the order is complete' do
|
|
let(:order) do
|
|
build_stubbed(
|
|
:order,
|
|
completed_at: Time.zone.now,
|
|
line_items: [build_stubbed(:line_item)]
|
|
)
|
|
end
|
|
|
|
it 'raises' do
|
|
expect { order.restart_checkout! }
|
|
.to raise_error(StateMachines::InvalidTransition)
|
|
end
|
|
end
|
|
|
|
context 'when the order is not complete' do
|
|
let(:order) do
|
|
build(:order, completed_at: nil, line_items: [build(:line_item)])
|
|
end
|
|
|
|
it 'transitions to :cart state' do
|
|
order.restart_checkout!
|
|
expect(order.state).to eq('cart')
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#ensure_updated_shipments" do
|
|
before { Spree::Shipment.create!(order: order) }
|
|
|
|
context "when the order is not completed" do
|
|
it "destroys current shipments" do
|
|
order.ensure_updated_shipments
|
|
expect(order.shipments).to be_empty
|
|
end
|
|
|
|
it "puts order back in address state" do
|
|
order.ensure_updated_shipments
|
|
expect(order.state).to eq "address"
|
|
end
|
|
end
|
|
|
|
context "when the order is completed" do
|
|
before do
|
|
allow(order).to receive(:completed?) { true }
|
|
end
|
|
|
|
it "does not change the shipments" do
|
|
expect {
|
|
order.ensure_updated_shipments
|
|
}.not_to change { order.shipments }
|
|
|
|
expect {
|
|
order.ensure_updated_shipments
|
|
}.not_to change { order.state }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#sort_line_items" do
|
|
let(:aaron) { create(:supplier_enterprise, name: "Aaron the farmer") }
|
|
let(:zed) { create(:supplier_enterprise, name: "Zed the farmer") }
|
|
|
|
let(:aaron_apple) { create(:product, name: "Apple", supplier: aaron) }
|
|
let(:aaron_banana) { create(:product, name: "Banana", supplier: aaron) }
|
|
let(:zed_apple) { create(:product, name: "Apple", supplier: zed) }
|
|
let(:zed_banana) { create(:product, name: "Banana", supplier: zed) }
|
|
|
|
let(:distributor) { create(:distributor_enterprise) }
|
|
let(:order) do
|
|
create(:order, distributor: distributor).tap do |order|
|
|
order.line_items << build(:line_item, variant: aaron_apple.variants.first)
|
|
order.line_items << build(:line_item, variant: zed_banana.variants.first)
|
|
order.line_items << build(:line_item, variant: zed_apple.variants.first)
|
|
order.line_items << build(:line_item, variant: aaron_banana.variants.first)
|
|
end
|
|
end
|
|
|
|
let(:line_item_names) do
|
|
order.sorted_line_items.map do |item|
|
|
"#{item.product.name} - #{item.supplier.name}"
|
|
end
|
|
end
|
|
|
|
context "when the distributor has preferred_invoice_order_by_supplier set to true" do
|
|
it "sorts the line items by supplier" do
|
|
distributor.update_attribute(:preferred_invoice_order_by_supplier, true)
|
|
|
|
expect(line_item_names).to eq [
|
|
"Apple - Aaron the farmer",
|
|
"Banana - Aaron the farmer",
|
|
"Apple - Zed the farmer",
|
|
"Banana - Zed the farmer",
|
|
]
|
|
end
|
|
end
|
|
|
|
context "when the distributor has preferred_invoice_order_by_supplier set to false" do
|
|
it "sorts the line items by product" do
|
|
distributor.update_attribute(:preferred_invoice_order_by_supplier, false)
|
|
|
|
expect(line_item_names).to eq [
|
|
"Apple - Aaron the farmer",
|
|
"Apple - Zed the farmer",
|
|
"Banana - Zed the farmer",
|
|
"Banana - Aaron the farmer",
|
|
]
|
|
end
|
|
end
|
|
end
|
|
end
|