Handle rounding errors for min_score requirements

refs LS-1916
flag=none

Uses BigDecimal when comparing scores for min_score completion
requirements.

test plan: automated :)

The best way to test this locally is to:
- Create a course with a student
- Create a module and add an assignment to that module
- Make the assignment worth 10 points
- Add a module completion requirement for the assignment to
  score at least 1
- Publish the module and the assignment
- In the UI, give the student a grade of 0 for the assignment
- As the student, see that the item is incomplete
- From the rails console, find the submission and give it
  a score of 0.3 + 0.3 + 0.3 + 0.1

```
submission = Submission.last
submission.score = 0.3 + 0.3 + 0.3 + 0.1 # 0.9999999999
submission.save
```

- As the student, refresh the modules page
- See that the module item is marked as complete
- View the module item in the api and verify it is marked as complete
  there too `/api/v1/modules/:module_id`

Change-Id: I428fef5eb73e07ed64b5e43509159b9317d4645d
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/260593
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Eric Saupe <eric.saupe@instructure.com>
QA-Review: Eric Saupe <eric.saupe@instructure.com>
Product-Review: Nate Armstrong <narmstrong@instructure.com>
This commit is contained in:
Nate Armstrong 2021-03-12 13:56:17 -07:00
parent 06c04492cc
commit 7fc5013bd6
3 changed files with 36 additions and 2 deletions

View File

@ -275,7 +275,7 @@ class ContextModuleProgression < ActiveRecord::Base
subs.any? do |sub|
score = get_submission_score(sub)
requirement_met = (score.present? && score >= requirement[:min_score].to_f)
requirement_met = (score.present? && score.to_d >= requirement[:min_score].to_f)
if requirement_met
remove_incomplete_requirement(requirement[:id])
else

View File

@ -800,6 +800,27 @@ describe "Modules API", type: :request do
expect(json['completed_at']).not_to be_nil
end
it "should consider scores that are close enough as complete" do
teacher = @course.enroll_teacher(User.create!, enrollment_state: 'active').user
@module1.completion_requirements = {
@assignment_tag.id => { :type => 'min_score', :min_score => 1 },
}
@module1.save!
json = api_call(:get, "/api/v1/courses/#{@course.id}/modules/#{@module1.id}",
:controller => "context_modules_api", :action => "show", :format => "json",
:course_id => "#{@course.id}", :id => "#{@module1.id}")
expect(json['state']).to eq 'unlocked'
@assignment.grade_student(@user, score: 0.3 + 0.3 + 0.3 + 0.1, grader: teacher)
json = api_call(:get, "/api/v1/courses/#{@course.id}/modules/#{@module1.id}",
:controller => "context_modules_api", :action => "show", :format => "json",
:course_id => "#{@course.id}", :id => "#{@module1.id}")
expect(json['state']).to eq 'completed'
expect(json['completed_at']).not_to be_nil
end
context 'show including content details' do
let(:module1_json) do
api_call(:get, "/api/v1/courses/#{@course.id}/modules/#{@module1.id}?include[]=items&include[]=content_details",

View File

@ -170,12 +170,25 @@ describe ContextModuleProgression do
context "when post policies enabled" do
let(:assignment) { @course.assignments.create! }
let(:tag) { @module.add_item({id: assignment.id, type: "assignment"}) }
let(:min_score) { 90 }
before(:each) do
@module.update!(completion_requirements: {tag.id => {type: "min_score", min_score: 90}})
@module.update!(completion_requirements: {tag.id => {type: "min_score", min_score: min_score}})
@submission = assignment.submit_homework(@user, body: "my homework")
end
context 'when the score is close enough' do
let(:min_score) { 1 }
let(:score) { 0.9999999999999999 } # eg 0.3 + 0.3 + 0.3 + 0.1
it 'evaluates requirement as complete' do
@submission.update!(score: score, posted_at: 1.second.ago)
progression = @module.context_module_progressions.find_by(user: @user)
requirement = {id: tag.id, type: 'min_score', min_score: min_score}
expect(progression.requirements_met).to include requirement
end
end
it "doesn't mark students that haven't submitted as in-progress" do
other_student = student_in_course(:course => @course, :active_all => true).user
progression = @module.evaluate_for(other_student)