From 2d6d25ef3637a72b2a0054dad69cde4562446ed6 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Fri, 25 Sep 2026 12:40:19 -0500 Subject: [PATCH 1/2] Fix the total returned by grade_gateway when the best score is zero. Previously a set version only replaced the initial [0, 0] best set data if its score was greater than zero. So if a user scored zero on all versions of a test, or had not started the test, the total returned was also zero. For LTI 1.3 grade passback that resulted in a scoreMaximum of zero being sent, which Canvas rejects with "cannot be zero if line item's maximum is not zero". Now the first version is used if no version has a positive score, and if there are no versions the total is computed from the problems assigned to the user for the test. Co-Authored-By: Claude Opus 5.5 --- lib/WeBWorK/Utils/Sets.pm | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/WeBWorK/Utils/Sets.pm b/lib/WeBWorK/Utils/Sets.pm index ee145ddfb0..020da6e82c 100644 --- a/lib/WeBWorK/Utils/Sets.pm +++ b/lib/WeBWorK/Utils/Sets.pm @@ -107,12 +107,21 @@ sub grade_set ($db, $set, $studentName, $setIsVersioned = 0, $wantProblemDetails } sub grade_gateway ($db, $setName, $studentName) { - my $bestSetData = [ 0, 0, [] ]; + my $bestSetData; my @setVersions = $db->getSetVersionsWhere({ user_id => $studentName, set_id => $setName }); for (@setVersions) { my @setData = grade_set($db, $_, $studentName, 1); - $bestSetData = \@setData if $setData[0] > $bestSetData->[0]; + $bestSetData = \@setData if !$bestSetData || $setData[0] > $bestSetData->[0]; + } + + # If the student has not started the test, then the total is still needed for a score of zero out of that total. + unless ($bestSetData) { + my $total = 0; + for ($db->getAllMergedUserProblems($studentName, $setName)) { + $total += defined $_->value && $_->value ne '' ? $_->value : 1; + } + $bestSetData = [ 0, $total, [] ]; } return wantarray ? (@$bestSetData, \@setVersions) : ($bestSetData->[1] ? $bestSetData->[0] / $bestSetData->[1] : 0); @@ -327,7 +336,9 @@ In list context this returns a list of the total number of correct problems for the highest scoring version of this test, the total number of problems in that version, a reference to an array of merged user problem records from that version, and a reference to an array of merged user set versions for this user -and set. +and set. If the user has no versions of the test, then the total is computed +from the problems assigned to the user for the test, and the array of problem +records is empty. In scalar context this returns the percentage correct for the highest scoring version of this test. From ac6efccfc1d6e045b5d7f78c22d951c1c503e7fe Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Fri, 25 Sep 2026 15:23:01 -0500 Subject: [PATCH 2/2] Also switch to using `$response->body` for the failure message. This is a more informative message, and Moodle is also now sending a JSON response (perhaps it always was, and I was confusing this request with one of the other LTI requests). --- lib/WeBWorK/Authen/LTIAdvantage/SubmitGrade.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WeBWorK/Authen/LTIAdvantage/SubmitGrade.pm b/lib/WeBWorK/Authen/LTIAdvantage/SubmitGrade.pm index 982fd6cfac..742aa49518 100644 --- a/lib/WeBWorK/Authen/LTIAdvantage/SubmitGrade.pm +++ b/lib/WeBWorK/Authen/LTIAdvantage/SubmitGrade.pm @@ -363,7 +363,7 @@ async sub submit_grade ($self, $LMSuserID, $lineitem, $scoreGiven, $scoreMaximum return 1; } - $self->warning(join("\n", 'Failed to send grade:', $response->message)); + $self->warning(join("\n", 'Failed to send grade:', $response->body)); return 0; }