Skip to content
Open
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
2 changes: 1 addition & 1 deletion app/models/school_project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def returned?
state_machine.in_state?(:returned)
end

delegate :can_transition_to?, :history, to: :state_machine
delegate :can_transition_to?, :history, :in_state?, to: :state_machine

private

Expand Down
34 changes: 26 additions & 8 deletions lib/concepts/school_project/set_status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,32 @@ class SchoolProject
class SetStatus
class << self
def call(school_project:, status:, user_id:)
response = OperationResponse.new
response[:school_project] = school_project
response[:school_project].transition_status_to!(status, user_id)
response
rescue StandardError => e
Sentry.capture_exception(e)
response[:error] = e.message
response
retry_on(Statesman::TransitionConflictError, attempts: 2, record: school_project) do
response = OperationResponse.new
response[:school_project] = school_project

return response if school_project.in_state?(status)

Comment thread
zetter-rpf marked this conversation as resolved.
unless school_project.can_transition_to?(status)
message = "Cannot transition from '#{school_project.status}' to '#{status}'"
response[:error] = message
return response
end

school_project.transition_status_to!(status, user_id)
response
end
end

private

def retry_on(exception_class, attempts:, record:)
yield
rescue exception_class
record.reload
attempts -= 1
retry if attempts.positive?
raise
end
end
end
Expand Down
68 changes: 40 additions & 28 deletions spec/concepts/school_project/set_status_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,54 @@
let(:school_project) { create(:school_project, school:, project:) }

describe '.call' do
context 'when status transition is successful' do
it 'returns a successful operation response' do
response = described_class.call(school_project:, status: :submitted, user_id: student.id)
expect(response.success?).to be(true)
end
it 'returns a successful operation response' do
response = described_class.call(school_project:, status: :submitted, user_id: student.id)
expect(response.success?).to be(true)
end

it 'updates the school project status' do
described_class.call(school_project:, status: :submitted, user_id: student.id)
expect(school_project.status).to eq('submitted')
end
it 'updates the school project status' do
described_class.call(school_project:, status: :submitted, user_id: student.id)
expect(school_project.status).to eq('submitted')
end

it 'returns the updated school project in the response' do
response = described_class.call(school_project:, status: :submitted, user_id: student.id)
expect(response[:school_project]).to be_a(SchoolProject)
end
it 'returns the updated school project in the response' do
response = described_class.call(school_project:, status: :submitted, user_id: student.id)
expect(response[:school_project]).to be_a(SchoolProject)
end

context 'when status transition fails' do
before do
allow(school_project).to receive(:transition_status_to!).and_raise(StandardError, 'Transition failed')
end
it 'returns an error when transitioning to an invalid status' do
response = described_class.call(school_project:, status: :returned, user_id: student.id)
expect(response.success?).to be(false)
expect(response[:error]).to eq("Cannot transition from '#{school_project.status}' to 'returned'")
end

it 'returns a failed operation response' do
response = described_class.call(school_project:, status: :submitted, user_id: student.id)
expect(response.success?).to be(false)
end
it 'is successful when transitioning to the same status' do
school_project.transition_status_to!(:submitted, student.id)
response = described_class.call(school_project:, status: :submitted, user_id: student.id)
expect(response.success?).to be(true)
expect(school_project.status).to eq('submitted')
end

it 'does not update the school project status' do
described_class.call(school_project:, status: :submitted, user_id: student.id)
expect(school_project.status).to eq('unsubmitted')
end
it 'retries when transition raises a "Statesman::TransitionConflictError" error' do
call_count = 0
allow(school_project).to receive(:transition_status_to!).and_wrap_original do |original, *args|
call_count += 1
raise Statesman::TransitionConflictError if call_count == 1

it 'includes the error message in the response' do
response = described_class.call(school_project:, status: :submitted, user_id: student.id)
expect(response[:error]).to eq('Transition failed')
original.call(*args)
end

response = described_class.call(school_project:, status: :submitted, user_id: student.id)
expect(response.success?).to be(true)
expect(school_project.status).to eq('submitted')
end

it 'raises the "Statesman::TransitionConflictError" error after 2 attempts' do
allow(school_project).to receive(:transition_status_to!).and_raise(Statesman::TransitionConflictError).twice

expect do
described_class.call(school_project:, status: :submitted, user_id: student.id)
end.to raise_error(Statesman::TransitionConflictError)
end
end
end
15 changes: 0 additions & 15 deletions spec/features/school_project/complete_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,6 @@
expect(student_project.school_project).to be_complete
end
end
Comment thread
zetter-rpf marked this conversation as resolved.

context 'when attempting an invalid status transition' do
before do
student_project.school_project.transition_status_to!(:complete, student.id)
post("/api/projects/#{student_project.identifier}/complete", headers:)
end

it 'completes unauthorized response' do
expect(response).to have_http_status(:unprocessable_content)
end

it 'completes error message' do
expect(JSON.parse(response.body)['error']).to eq("Cannot transition from 'complete' to 'complete'")
end
end
end

context 'when user does not own the project and is not the class teacher' do
Expand Down
Loading