Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions core/app/jobs/spree/state_change_tracking_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@ class StateChangeTrackingJob < BaseJob
# @param previous_state [String] The previous state of the order
# @param current_state [String] The current state of the order
# @param transition_timestamp [Time] When the state transition occurred
def perform(stateful, previous_state, current_state, transition_timestamp)
# @param name [String] The element name of the state transition being
# tracked. It defaults to the `stateful` model element name.
def perform(
stateful,
previous_state,
current_state,
transition_timestamp,
name = stateful.class.model_name.element
)
Spree::StateChange.create!(
name: stateful.class.model_name.element,
name: name,
stateful: stateful,
previous_state: previous_state,
next_state: current_state,
Expand Down
3 changes: 2 additions & 1 deletion core/app/models/spree/order_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ def log_state_change(name)
order,
old_state,
new_state,
Time.current
Time.current,
name
)
end
end
Expand Down
130 changes: 78 additions & 52 deletions core/spec/jobs/spree/state_change_tracking_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,70 +8,96 @@
let(:transition_timestamp) { Time.current }

describe '#perform' do
it 'creates a state change record with correct attributes' do
expect {
described_class.perform_now(
order,
'cart',
'address',
transition_timestamp
)
}.to change(Spree::StateChange, :count).by(1)
context "when the stateful object and the state change name do not match" do
it 'creates a state change record with correct attributes' do
expect {
described_class.perform_now(
order,
'processing',
'pending',
transition_timestamp,
'payment'
)
}.to change(Spree::StateChange, :count).by(1)

state_change = Spree::StateChange.last
expect(state_change.previous_state).to eq('cart')
expect(state_change.next_state).to eq('address')
expect(state_change.name).to eq('order')
expect(state_change.user_id).to eq(user.id)
expect(state_change.stateful_id).to eq(order.id)
expect(state_change.stateful_type).to eq('Spree::Order')
expect(state_change.created_at).to be_within(1.second).of(transition_timestamp)
expect(state_change.updated_at).to be_within(1.second).of(transition_timestamp)
state_change = Spree::StateChange.last
expect(state_change.previous_state).to eq('processing')
expect(state_change.next_state).to eq('pending')
expect(state_change.name).to eq('payment')
expect(state_change.user_id).to eq(user.id)
expect(state_change.stateful_id).to eq(order.id)
expect(state_change.stateful_type).to eq('Spree::Order')
expect(state_change.created_at).to be_within(1.second).of(transition_timestamp)
expect(state_change.updated_at).to be_within(1.second).of(transition_timestamp)
end
end

it 'stores all state transitions in correct order' do
transitions = [
['cart', 'address'],
['address', 'delivery'],
['delivery', 'payment'],
['payment', 'confirm'],
['confirm', 'complete'],
['complete', 'canceled'],
['canceled', 'resumed']
]

transitions.each do |from_state, to_state|
described_class.perform_now(
order,
from_state,
to_state,
transition_timestamp
)
context "when the stateful object and the state change name match" do
it 'creates a state change record with correct attributes' do
expect {
described_class.perform_now(
order,
'cart',
'address',
transition_timestamp
)
}.to change(Spree::StateChange, :count).by(1)

state_change = Spree::StateChange.last
expect(state_change.previous_state).to eq(from_state)
expect(state_change.next_state).to eq(to_state)
expect(state_change.previous_state).to eq('cart')
expect(state_change.next_state).to eq('address')
expect(state_change.name).to eq('order')
expect(state_change.user_id).to eq(user.id)
expect(state_change.stateful_id).to eq(order.id)
expect(state_change.stateful_type).to eq('Spree::Order')
expect(state_change.created_at).to be_within(1.second).of(transition_timestamp)
expect(state_change.updated_at).to be_within(1.second).of(transition_timestamp)
end

expect(Spree::StateChange.count).to eq(transitions.length)
expect(Spree::StateChange.order(:created_at).pluck(:previous_state, :next_state)).to eq(transitions)
end
it 'stores all state transitions in correct order' do
transitions = [
['cart', 'address'],
['address', 'delivery'],
['delivery', 'payment'],
['payment', 'confirm'],
['confirm', 'complete'],
['complete', 'canceled'],
['canceled', 'resumed']
]

transitions.each do |from_state, to_state|
described_class.perform_now(
order,
from_state,
to_state,
transition_timestamp
)

state_change = Spree::StateChange.last
expect(state_change.previous_state).to eq(from_state)
expect(state_change.next_state).to eq(to_state)
expect(state_change.stateful_id).to eq(order.id)
expect(state_change.stateful_type).to eq('Spree::Order')
end

expect(Spree::StateChange.count).to eq(transitions.length)
expect(Spree::StateChange.order(:created_at).pluck(:previous_state, :next_state)).to eq(transitions)
end

it 'preserves the exact transition timestamp' do
specific_time = Time.zone.parse('2023-12-25 10:30:45')
it 'preserves the exact transition timestamp' do
specific_time = Time.zone.parse('2023-12-25 10:30:45')

described_class.perform_now(
order,
'cart',
'address',
specific_time
)
described_class.perform_now(
order,
'cart',
'address',
specific_time
)

state_change = Spree::StateChange.last
expect(state_change.created_at).to eq(specific_time)
expect(state_change.updated_at).to eq(specific_time)
state_change = Spree::StateChange.last
expect(state_change.created_at).to eq(specific_time)
expect(state_change.updated_at).to eq(specific_time)
end
end

context 'when the order has no user' do
Expand Down
26 changes: 26 additions & 0 deletions core/spec/models/spree/order_updater_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,19 @@ module Spree
allow(order).to receive_messages backordered?: false
end

it "logs a state change for the shipment" do
create :shipment, order:, state: "pending"

expect { updater.update_shipment_state }
.to enqueue_job(Spree::StateChangeTrackingJob)
.with(order, nil, "pending", a_kind_of(Time), "shipment")
.once

expect {
perform_enqueued_jobs
}.to change { Spree::StateChange.where(name: "shipment").count }.by(1)
end

it "is backordered" do
allow(order).to receive_messages backordered?: true
updater.update_shipment_state
Expand Down Expand Up @@ -188,6 +201,19 @@ module Spree
let(:updater) { order.recalculator }
before { allow(order).to receive(:refund_total).and_return(0) }

it "logs a state change for the payment" do
create :payment, order:, state: "processing"

expect { updater.update_payment_state }
.to enqueue_job(Spree::StateChangeTrackingJob)
.with(order, nil, "paid", a_kind_of(Time), "payment")
.once

expect {
perform_enqueued_jobs
}.to change { Spree::StateChange.where(name: "payment").count }.by(1)
end

context 'no valid payments with non-zero order total' do
it "is failed" do
create(:payment, order:, state: 'invalid')
Expand Down
Loading