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
7 changes: 5 additions & 2 deletions app/models/course/assessment/submission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -274,14 +274,17 @@ def force_submit_job_scheduled?
# when neither applies, i.e. the submission is never force-submitted. The deadline component honours
# the submitter's personalised timeline. Does not include the grace period.
#
# A submission that has been unsubmitted by staff is exempt: the student is redoing it with explicit
# permission, so it has no deadline and is never force-submitted.
# Exempt from the deadline (returns nil) when the submission has been unsubmitted by staff (a
# permitted redo). The deadline is also ignored when the submission was created after it had already
# passed — students cannot do this (creation is blocked), but staff can start a test run at any time,
# and such a run should behave as if there were no end date rather than be finalised immediately.
#
# @return [Time, nil]
def force_submit_at
return nil if unsubmitted_at.present?

deadline = assessment.submission_deadline_for(course_user)
deadline = nil if deadline && created_at > deadline
time_limit_at = assessment.time_limit && (created_at + assessment.time_limit.minutes)
[deadline, time_limit_at].compact.min
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ elsif attempting_submission.present?
elsif submitted_submission.present?
status = 'submitted'
action_url = edit_course_assessment_submission_path(current_course, assessment, submitted_submission)
elsif assessment.submission_deadline_passed_for?(current_course_user)
elsif !can_manage && assessment.submission_deadline_passed_for?(current_course_user)
# End date passed on an assessment that disallows late submissions, and no submission exists: the
# student can no longer start one. No action URL — the frontend shows a disabled button.
# student can no longer start one. No action URL — the frontend shows a disabled button. Staff who
# can manage the assessment are exempt (they may create test submissions at any time), matching the
# create authorization.
status = 'closed'
else
status = 'open'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,20 +341,23 @@ const AssessmentForm = (props: AssessmentFormProps): JSX.Element => {
/>
)}

<Controller
control={control}
name="is_late_submission_allowed"
render={({ field, fieldState }): JSX.Element => (
<FormCheckboxField
description={t(translations.allowLateSubmissionHint)}
// The toggle only has an effect with a deadline, and cannot be enforced on Koditsu.
disabled={disabled || !endAt || isKoditsuAssessmentEnabled}
field={field}
fieldState={fieldState}
label={t(translations.allowLateSubmission)}
/>
)}
/>
{/* The setting only has an effect with an end date, so it is hidden without one. */}
{endAt && (
<Controller
control={control}
name="is_late_submission_allowed"
render={({ field, fieldState }): JSX.Element => (
<FormCheckboxField
description={t(translations.allowLateSubmissionHint)}
// Cannot be enforced on Koditsu.
disabled={disabled || isKoditsuAssessmentEnabled}
field={field}
fieldState={fieldState}
label={t(translations.allowLateSubmission)}
/>
)}
/>
)}

<Typography>{t(translations.description)}</Typography>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@

describe '#edit fail-safe for an overdue submission' do
let(:end_at) { 20.minutes.ago }
# The student began before the deadline, which has since passed while they were still attempting.
let!(:submission) do
create(:submission, :attempting, assessment: assessment,
creator: student.user, course_user: student)
creator: student.user, course_user: student).
tap { |s| s.update_column(:created_at, 1.hour.ago) }
end

subject do
Expand Down Expand Up @@ -103,6 +105,22 @@
expect(submission.reload).to be_attempting
end
end

context 'when staff started a test run after the deadline had passed' do
let(:staff) { create(:course_teaching_assistant, course: course) }
# Created now, i.e. after the passed end date — the deadline does not apply to it.
let!(:submission) do
create(:submission, :attempting, assessment: assessment,
creator: staff.user, course_user: staff)
end

before { controller_sign_in(controller, staff.user) }

it 'does not force-submit the staff test submission' do
expect { subject }.not_to(change { submission.reload.workflow_state })
expect(submission.reload).to be_attempting
end
end
end

describe '#edit late flag' do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
create(:assessment, :with_mcq_question, course: course,
end_at: end_at, is_late_submission_allowed: false)
end
# The attempt was begun before the (now-passed) deadline.
let!(:submission) do
create(:submission, :attempting, assessment: assessment,
creator: student.user, course_user: student)
creator: student.user, course_user: student).
tap { |s| s.update_column(:created_at, 2.hours.ago) }
end

def run
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ def enqueued_for(submission)
context 'when a submission is past due (its scheduled job was lost)' do
let(:end_at) { (10.minutes + delay).before(Time.zone.now) }

# The attempt was begun before the deadline it has since blown past.
before { submission.update_column(:created_at, 1.hour.ago) }

it 'schedules an immediate force-submit job' do
run
jobs = enqueued_for(submission)
Expand Down
30 changes: 30 additions & 0 deletions spec/models/course/assessment/submission_force_submit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,34 @@
is_expected.to be_nil
end
end

context 'when the submission was created after the deadline (e.g. a staff test run)' do
let(:late_allowed) { false }
let(:end_at) { 20.minutes.ago }

it 'does not enforce the already-passed deadline' do
is_expected.to be_nil
end

context 'but a time limit still applies' do
let(:time_limit) { 30 }

it 'still enforces the time limit from creation' do
is_expected.to be_within(1.second).of(submission.created_at + 30.minutes)
end
end
end

context 'when the submission was created before a since-passed deadline' do
let(:late_allowed) { false }
let(:end_at) { 20.minutes.ago }

before { submission.update_column(:created_at, 1.hour.ago) }

it 'still enforces the deadline (the student began in time)' do
is_expected.to be_within(1.second).of(end_at)
end
end
end

describe 'the unsubmit and finalise events' do
Expand Down Expand Up @@ -90,6 +118,8 @@

describe '#force_submit_overdue?' do
let(:late_allowed) { false }
# An overdue attempt was necessarily begun before its deadline.
before { submission.update_column(:created_at, 1.hour.ago) }

context 'when past the force-submit time plus grace' do
let(:end_at) { (10.minutes + delay).before(Time.zone.now) }
Expand Down