-
-
Notifications
You must be signed in to change notification settings - Fork 730
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Place backorders for linked products via DFC integration #12856
Merged
Merged
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
260e4f7
Create BackorderJob to place wholesale orders
mkllnk 98966f6
Place backorders for DFC products
mkllnk 439f0ca
Raise errors on DFC requests
mkllnk 6c6927a
Add SaleSession with correct OrderCycle times
mkllnk 827e37c
Start moving backorder logic to service
mkllnk caa6d28
Find and update existing open order
mkllnk a7a3889
Add needed quantities to existing line items
mkllnk f839452
Complete an open order
mkllnk c7fa3ff
Simplify order update logic
mkllnk 3e0eb87
Simplify service with ivar
mkllnk 7b286ea
Complete test for FDC Orders API
mkllnk 3849db7
Simplify order update call
mkllnk 3ec53a7
Parse updated order result
mkllnk c0ae2ed
Complete order 4 hours after order cycle closed
mkllnk 8f4f873
Move offer finding into separate class
mkllnk 14c32c0
Reduce complexity
mkllnk efe2b72
Find wholesale offer for retail variant
mkllnk 95bc0cc
Reduce complexity of BackorderJob
mkllnk c948efd
Add structure to adjust final quantities
mkllnk 95e620a
Add lookup of variants by semantic id
mkllnk 283db8f
Adjust quantities of backorder before completion
mkllnk 5ef85ae
Handle backorder cancellations
mkllnk 2eec4c7
Apply 4 hour completion delay only to one enterprise
mkllnk 4303f0e
Build API URLs to work with any FDC Shopify shop
mkllnk fb96f8f
Fall back to given product w/o wholesale variant
mkllnk 070b93c
Fall back to givin product id w/o retail variant
mkllnk 7f62b49
Move catalog loading to where it's needed
mkllnk 66f0802
Import DFC product images
mkllnk 9f43244
Import on-demand stock setting in DFC import
mkllnk 2465780
Import prices and stock levels from DFC catalog
mkllnk eece738
Restore concurrency spec for the checkout
mkllnk 61fec65
Abstract OrderLocker for re-use
mkllnk e31e45b
Place backorders in the background
mkllnk 49fd1dc
Report backorder errors instead of failing checkout
mkllnk 495634b
Send error notification to owner
mkllnk 989a6d5
Notify user of failed backorder completion
mkllnk 51b3770
Keep failed backorder job in dead set
mkllnk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'spec_helper' | ||
|
||
# This is the first example of testing concurrency in the Open Food Network. | ||
# If we want to do this more often, we should look at: | ||
# | ||
# https://github.com/forkbreak/fork_break | ||
# | ||
# The concurrency flag enables multiple threads to see the same database | ||
# without isolated transactions. | ||
RSpec.describe "Concurrent checkouts", concurrency: true do | ||
include AuthenticationHelper | ||
include ShopWorkflow | ||
|
||
let(:order_cycle) { create(:order_cycle) } | ||
let(:distributor) { order_cycle.distributors.first } | ||
let(:order) { create(:order, order_cycle:, distributor:) } | ||
let(:address) { create(:address) } | ||
let(:payment_method) { create(:payment_method, distributors: [distributor]) } | ||
let(:breakpoint) { Mutex.new } | ||
|
||
let(:address_params) { address.attributes.except("id") } | ||
let(:order_params) { | ||
{ | ||
"payments_attributes" => [ | ||
{ | ||
"payment_method_id" => payment_method.id, | ||
"amount" => order.total | ||
} | ||
], | ||
"bill_address_attributes" => address_params, | ||
"ship_address_attributes" => address_params, | ||
} | ||
} | ||
let(:path) { checkout_update_path(:summary) } | ||
let(:params) { { format: :json } } | ||
|
||
before do | ||
# Create a valid order ready for checkout: | ||
create(:shipping_method, distributors: [distributor]) | ||
variant = order_cycle.variants_distributed_by(distributor).first | ||
order.line_items << create(:line_item, variant:) | ||
|
||
# Transition cart to confirmation state: | ||
order.update(order_params) | ||
order.next # => address | ||
order.next # => delivery | ||
order.next # => payment | ||
order.next # => confirmation | ||
|
||
set_order(order) | ||
login_as(order.user) | ||
end | ||
|
||
it "handles two concurrent orders successfully" do | ||
breakpoint.lock | ||
breakpoint_reached_counter = 0 | ||
|
||
# Set a breakpoint after loading the order and before advancing the order's | ||
# state and making payments. If two requests reach this breakpoint at the | ||
# same time, they are in a race condition and bad things can happen. | ||
# Examples are processing payments twice or selling more than we have. | ||
allow_any_instance_of(CheckoutController). | ||
to receive(:advance_order_state). | ||
and_wrap_original do |method, *args| | ||
breakpoint_reached_counter += 1 | ||
breakpoint.synchronize do | ||
# Wait here until the breakpoint is unlocked. | ||
# Hopefully only one thread gets here in that time. | ||
# The second thread is told by the controller to wait before | ||
# loading the order. | ||
end | ||
method.call(*args) | ||
end | ||
|
||
# Starting two checkout threads. The controller code will determine if | ||
# these two threads are synchronised correctly or run into a race condition. | ||
# | ||
# 1. If the controller synchronises correctly: | ||
# The first thread locks required resources and then waits at the | ||
# breakpoint. The second thread waits for the first one. | ||
# 2. If the controller fails to prevent the race condition: | ||
# Both threads load required resources and wait at the breakpoint to do | ||
# the same checkout action. | ||
threads = [ | ||
Thread.new { put(path, params:) }, | ||
Thread.new { put(path, params:) }, | ||
] | ||
|
||
# Wait for the first thread to reach the breakpoint: | ||
Timeout.timeout(1) do | ||
sleep 0.1 while breakpoint_reached_counter < 1 | ||
end | ||
|
||
# Give the second thread a chance to reach the breakpoint, too. | ||
# But we hope that it waits for the first thread earlier and doesn't | ||
# reach the breakpoint yet. | ||
sleep 1 | ||
expect(breakpoint_reached_counter).to eq 1 | ||
|
||
# Let the requests continue and finish. | ||
breakpoint.unlock | ||
threads.each(&:join) | ||
|
||
# Verify that the checkout happened once. | ||
order.reload | ||
expect(order.completed?).to be true | ||
expect(order.payments.count).to eq 1 | ||
end | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏅 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might not hurt to expect order.state to be confirmation, if perhaps the states change in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should be using
Orders::WorkflowService#next
to better replicate the checkout process ?