From 3ecf81fcf4d195e31671dc0ef1a2289653b285f0 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Fri, 9 Aug 2024 15:09:54 +0800 Subject: [PATCH] MDL-75947 ltiservice_gradebookservices: test sequential score posting This tests highlights a problem with the score handling logic and will currently fail. On score post, the current time is used for grade->timemodified instead of the score->timestamp provided by the tool in the POST JSON. --- .../gradebookservices/tests/lineitem_test.php | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/mod/lti/service/gradebookservices/tests/lineitem_test.php b/mod/lti/service/gradebookservices/tests/lineitem_test.php index ad5f8be601d..32e9237a3f8 100644 --- a/mod/lti/service/gradebookservices/tests/lineitem_test.php +++ b/mod/lti/service/gradebookservices/tests/lineitem_test.php @@ -17,6 +17,8 @@ namespace ltiservice_gradebookservices; use ltiservice_gradebookservices\local\resources\lineitem; +use ltiservice_gradebookservices\local\resources\results; +use ltiservice_gradebookservices\local\resources\scores; use ltiservice_gradebookservices\local\service\gradebookservices; /** @@ -174,6 +176,97 @@ final class lineitem_test extends \advanced_testcase { $this->assertFalse(isset($responseitem->submissionReview->custom)); } + /** + * Test running a series of score updates, highlighting problems with the score posting logic. + * + * @covers ::execute + * + * @return void + */ + public function test_sequential_score_posts(): void { + global $CFG; + require_once($CFG->dirroot . '/mod/lti/locallib.php'); + $this->resetAfterTest(); + $resourceid = 'test-resource-id'; + $tag = 'tag'; + $course = $this->getDataGenerator()->create_course(); + $typeid = $this->create_type(); + $user = $this->getDataGenerator()->create_and_enrol($course); + + // Create mod instance with line item - nothing pushed via services yet. + $gbservice = new gradebookservices(); + $gbservice->set_type(lti_get_type($typeid)); + $modinstance = $this->create_graded_lti($typeid, $course, $resourceid, $tag); + $gradeitems = $gbservice->get_lineitems($course->id, null, null, null, null, null, $typeid); + $this->assertEquals(1, $gradeitems[0]); // The 1st item in the array is the items count. + + // Post a score so that there's at least one grade present at the time of lineitem update. + $score = new scores($gbservice); + $_SERVER['REQUEST_METHOD'] = \mod_lti\local\ltiservice\resource_base::HTTP_POST; + $_SERVER['PATH_INFO'] = "/$course->id/lineitems/{$gradeitems[1][0]->id}/lineitem/scores?type_id=$typeid"; + $token = lti_new_access_token($typeid, ['https://purl.imsglobal.org/spec/lti-ags/scope/score']); + $_SERVER['HTTP_Authorization'] = 'Bearer '.$token->token; + $_GET['type_id'] = (string)$typeid; + $score->scoreGiven = "8.0"; + $score->scoreMaximum = "10.0"; + $score->activityProgress = "Completed"; + $score->timestamp = "2024-08-07T18:54:36.736+00:00"; + $score->gradingProgress = "FullyGraded"; + $score->userId = $user->id; + $response = new \mod_lti\local\ltiservice\response(); + $response->set_content_type('application/vnd.ims.lis.v1.score+json'); + $response->set_request_data(json_encode($score)); + $score->execute($response); + + // The grade in Moodle should reflect the score->timestamp, not the time of the score post. + $grades = grade_get_grades($course->id, 'mod', 'lti', $modinstance->id, $user->id); + $studentgrade = array_shift($grades->items[0]->grades); + $this->assertEquals(strtotime($score->timestamp), $studentgrade->dategraded); + + // Read the results via the service. This should also return the correct dategraded time (timemodified in LTI terms). + $_SERVER['REQUEST_METHOD'] = \mod_lti\local\ltiservice\resource_base::HTTP_GET; + $_SERVER['PATH_INFO'] = "/$course->id/lineitems/{$gradeitems[1][0]->id}/lineitem/results?type_id=$typeid"; + $token = lti_new_access_token($typeid, ['https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly']); + $_SERVER['HTTP_Authorization'] = 'Bearer '.$token->token; + $_GET['type_id'] = (string)$typeid; + $result = new results($gbservice); + $response = new \mod_lti\local\ltiservice\response(); + $response->set_content_type('application/vnd.ims.lis.v2.resultcontainer+json'); + $result->execute($response); + $body = json_decode($response->get_body()); + $result = array_shift($body); + $this->assertEquals(strtotime($score->timestamp), strtotime($result->timestamp)); + + // Now, try to post a newer score using a timestamp that is greater than the one originally sent, but less than the time at + // which the score was last posted. This should be valid since the timestamp is greater than the original posted score. + $_SERVER['REQUEST_METHOD'] = \mod_lti\local\ltiservice\resource_base::HTTP_POST; + $_SERVER['PATH_INFO'] = "/$course->id/lineitems/{$gradeitems[1][0]->id}/lineitem/scores?type_id=$typeid"; + $token = lti_new_access_token($typeid, ['https://purl.imsglobal.org/spec/lti-ags/scope/score']); + $_SERVER['HTTP_Authorization'] = 'Bearer '.$token->token; + $_GET['type_id'] = (string)$typeid; + $score->scoreGiven = "14"; + $score->timestamp = "2024-08-08T18:54:36.736+00:00"; + $response = new \mod_lti\local\ltiservice\response(); + $response->set_content_type('application/vnd.ims.lis.v1.score+json'); + $response->set_request_data(json_encode($score)); + $score->execute($response); + $this->assertEquals(200, json_decode($response->get_code())); + + // Finally, post a score that's just been updated (i.e. score->timestamp = now). + $_SERVER['REQUEST_METHOD'] = \mod_lti\local\ltiservice\resource_base::HTTP_POST; + $_SERVER['PATH_INFO'] = "/$course->id/lineitems/{$gradeitems[1][0]->id}/lineitem/scores?type_id=$typeid"; + $token = lti_new_access_token($typeid, ['https://purl.imsglobal.org/spec/lti-ags/scope/score']); + $_SERVER['HTTP_Authorization'] = 'Bearer '.$token->token; + $_GET['type_id'] = (string)$typeid; + $score->scoreGiven = "15"; + $score->timestamp = date('c', time()); + $response = new \mod_lti\local\ltiservice\response(); + $response->set_content_type('application/vnd.ims.lis.v1.score+json'); + $response->set_request_data(json_encode($score)); + $score->execute($response); + $this->assertEquals(200, json_decode($response->get_code())); + } + /** * Inserts a graded lti instance, which should create a grade_item and gradebookservices record. *