mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-04-02 06:51:40 +00:00
228 lines
8.4 KiB
Ruby
228 lines
8.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe SubscriptionPlacementJob do
|
|
let(:job) { SubscriptionPlacementJob.new }
|
|
let(:summarizer) { OrderManagement::Subscriptions::Summarizer.new }
|
|
|
|
describe "finding proxy_orders that are ready to be placed" do
|
|
let(:shop) { create(:distributor_enterprise) }
|
|
let(:order_cycle1) {
|
|
create(:simple_order_cycle, coordinator: shop, orders_open_at: 1.minute.ago,
|
|
orders_close_at: 10.minutes.from_now)
|
|
}
|
|
let(:order_cycle2) {
|
|
create(:simple_order_cycle, coordinator: shop, orders_open_at: 10.minutes.ago,
|
|
orders_close_at: 1.minute.ago)
|
|
}
|
|
let(:schedule) { create(:schedule, order_cycles: [order_cycle1, order_cycle2]) }
|
|
let(:subscription) { create(:subscription, shop:, schedule:) }
|
|
let!(:proxy_order) {
|
|
create(:proxy_order, subscription:, order_cycle: order_cycle1)
|
|
} # OK
|
|
|
|
it "ignores proxy orders where the OC has closed" do
|
|
expect(job.__send__(:proxy_orders)).to include proxy_order
|
|
proxy_order.update!(order_cycle_id: order_cycle2.id)
|
|
expect(job.__send__(:proxy_orders)).not_to include proxy_order
|
|
end
|
|
|
|
it "ignores proxy orders for paused or cancelled subscriptions" do
|
|
expect(job.__send__(:proxy_orders)).to include proxy_order
|
|
subscription.update!(paused_at: 1.minute.ago)
|
|
expect(job.__send__(:proxy_orders)).not_to include proxy_order
|
|
subscription.update!(paused_at: nil)
|
|
expect(job.__send__(:proxy_orders)).to include proxy_order
|
|
subscription.update!(canceled_at: 1.minute.ago)
|
|
expect(job.__send__(:proxy_orders)).not_to include proxy_order
|
|
end
|
|
|
|
it "ignores proxy orders that have been marked as cancelled or placed" do
|
|
expect(job.__send__(:proxy_orders)).to include proxy_order
|
|
proxy_order.update!(canceled_at: 5.minutes.ago)
|
|
expect(job.__send__(:proxy_orders)).not_to include proxy_order
|
|
proxy_order.update!(canceled_at: nil)
|
|
expect(job.__send__(:proxy_orders)).to include proxy_order
|
|
proxy_order.update!(placed_at: 5.minutes.ago)
|
|
expect(job.__send__(:proxy_orders)).not_to include proxy_order
|
|
end
|
|
end
|
|
|
|
describe "performing the job" do
|
|
context "when unplaced proxy_orders exist" do
|
|
let!(:subscription) { create(:subscription, with_items: true) }
|
|
let(:order) { build(:order, distributor: shop, line_items: [build(:line_item)]) }
|
|
let(:shop) { order_cycle.coordinator }
|
|
let(:order_cycle) { create(:simple_order_cycle) }
|
|
let(:exchange) {
|
|
create(:exchange, order_cycle:, sender: shop, receiver: shop, incoming: false)
|
|
}
|
|
let!(:proxy_order) {
|
|
create(:proxy_order, subscription:, order_cycle:, order:)
|
|
}
|
|
|
|
before do
|
|
allow(job).to receive(:proxy_orders) { ProxyOrder.where(id: proxy_order.id) }
|
|
end
|
|
|
|
it "processes placeable proxy_orders" do
|
|
service = PlaceProxyOrder.new(proxy_order, summarizer, JobLogger.logger, CapQuantity.new)
|
|
|
|
allow(PlaceProxyOrder).to receive(:new) { service }
|
|
allow(service).to receive(:call)
|
|
|
|
job.perform
|
|
|
|
expect(service).to have_received(:call)
|
|
end
|
|
|
|
it "records exceptions" do
|
|
exchange.variants << order.line_items.first.variant
|
|
|
|
summarizer = TestSummarizer.new
|
|
allow(OrderManagement::Subscriptions::Summarizer).to receive(:new).and_return(summarizer)
|
|
|
|
job.perform
|
|
|
|
expect(summarizer.recorded_issues[order.id])
|
|
.to eq("Errors: Cannot transition state via :next from :address " \
|
|
"(Reason(s): Items cannot be shipped)")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "processing a subscription order" do
|
|
let!(:shipping_method_created_earlier) { create(:shipping_method, distributors: [shop]) }
|
|
let!(:shipping_method) { create(:shipping_method, distributors: [shop]) }
|
|
let!(:shipping_method_created_later) { create(:shipping_method, distributors: [shop]) }
|
|
|
|
let(:shop) { create(:enterprise) }
|
|
let(:subscription) { create(:subscription, shop:, with_items: true) }
|
|
let(:proxy_order) { create(:proxy_order, subscription:) }
|
|
let(:oc) { proxy_order.order_cycle }
|
|
let(:ex) { oc.exchanges.outgoing.find_by(sender_id: shop.id, receiver_id: shop.id) }
|
|
let(:fee) { create(:enterprise_fee, enterprise: shop, fee_type: 'sales', amount: 10) }
|
|
let!(:exchange_fee) { ExchangeFee.create!(exchange: ex, enterprise_fee: fee) }
|
|
|
|
before do
|
|
expect_any_instance_of(Spree::Payment).not_to receive(:process!)
|
|
allow_any_instance_of(PlaceProxyOrder).to receive(:send_placement_email)
|
|
allow_any_instance_of(PlaceProxyOrder).to receive(:send_empty_email)
|
|
end
|
|
|
|
context "when the order is not already complete" do
|
|
context "when no stock items are available after capping stock" do
|
|
let(:service) do
|
|
PlaceProxyOrder.new(proxy_order, summarizer, JobLogger.logger, store_updater)
|
|
end
|
|
let(:store_updater) { CapQuantity.new }
|
|
|
|
before do
|
|
fake_relation = instance_double(ActiveRecord::Relation, select: -123)
|
|
allow(store_updater).to receive(:available_variants_for).and_return(fake_relation)
|
|
end
|
|
|
|
it "does not place the order, clears all adjustments, and sends an empty_order email" do
|
|
allow(service).to receive(:send_placement_email)
|
|
allow(service).to receive(:send_empty_email)
|
|
|
|
service.call
|
|
|
|
expect(proxy_order.order.reload.completed_at).to be_nil
|
|
expect(proxy_order.order.all_adjustments).to be_empty
|
|
expect(proxy_order.order.total).to eq 0
|
|
expect(proxy_order.order.adjustment_total).to eq 0
|
|
|
|
expect(service).not_to have_received(:send_placement_email)
|
|
expect(service).to have_received(:send_empty_email)
|
|
end
|
|
end
|
|
|
|
context "when at least one stock item is available after capping stock" do
|
|
let(:service) do
|
|
PlaceProxyOrder.new(proxy_order, summarizer, JobLogger.logger, CapQuantity.new)
|
|
end
|
|
|
|
before do
|
|
allow(service).to receive(:send_placement_email)
|
|
end
|
|
|
|
it "processes the order to completion, but does not process the payment" do
|
|
freeze_time do
|
|
service.call
|
|
proxy_order.order.reload.completed_at
|
|
|
|
expect(proxy_order.order.completed_at).to eq(Time.zone.now)
|
|
expect(proxy_order.order.payments.first.state).to eq "checkout"
|
|
end
|
|
end
|
|
|
|
it "does not enqueue confirmation emails" do
|
|
expect{ service.call }
|
|
.not_to have_enqueued_mail(Spree::OrderMailer, :confirm_email_for_customer)
|
|
|
|
expect(service).to have_received(:send_placement_email).once
|
|
end
|
|
|
|
context "when progression of the order fails" do
|
|
before { allow(service).to receive(:move_to_completion).and_raise(StandardError) }
|
|
|
|
it "records an error and does not attempt to send an email" do
|
|
expect(service).not_to receive(:send_placement_email)
|
|
expect(summarizer).to receive(:record_and_log_error).once
|
|
service.call
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "parallisation", concurrency: true do
|
|
let(:shop) { create(:distributor_enterprise) }
|
|
let(:order_cycle) {
|
|
create(
|
|
:simple_order_cycle,
|
|
coordinator: shop,
|
|
orders_open_at: 1.minute.ago,
|
|
orders_close_at: 10.minutes.from_now
|
|
)
|
|
}
|
|
let(:schedule) { create(:schedule, order_cycles: [order_cycle]) }
|
|
let(:subscription) { create(:subscription, shop:, schedule:) }
|
|
let!(:proxy_order) {
|
|
create(:proxy_order, subscription:, order_cycle:)
|
|
}
|
|
let(:breakpoint) { Mutex.new }
|
|
|
|
it "doesn't place duplicate orders" do
|
|
# Pause jobs when placing proxy order:
|
|
breakpoint.lock
|
|
allow(PlaceProxyOrder).to(
|
|
receive(:new).and_wrap_original do |method, *args|
|
|
breakpoint.synchronize { nil }
|
|
method.call(*args)
|
|
end
|
|
)
|
|
|
|
expect {
|
|
# Start two jobs in parallel:
|
|
threads = [
|
|
Thread.new { SubscriptionPlacementJob.new.perform },
|
|
Thread.new { SubscriptionPlacementJob.new.perform },
|
|
]
|
|
|
|
# Wait for both to jobs to pause.
|
|
# This can reveal a race condition.
|
|
sleep 1
|
|
|
|
# Resume and complete both jobs:
|
|
breakpoint.unlock
|
|
threads.each(&:join)
|
|
}.to change {
|
|
Spree::Order.count
|
|
}.by(1)
|
|
end
|
|
end
|
|
end
|