diff --git a/enrol/lti/tests/local/ltiadvantage/lti_advantage_testcase.php b/enrol/lti/tests/local/ltiadvantage/lti_advantage_testcase.php index 31535c9e5d236..ca844c26a852b 100644 --- a/enrol/lti/tests/local/ltiadvantage/lti_advantage_testcase.php +++ b/enrol/lti/tests/local/ltiadvantage/lti_advantage_testcase.php @@ -126,7 +126,7 @@ protected function get_mock_launch_users_with_ids(array $ids, bool $includepictu * @param \stdClass $resource the resource record, allowing the mock to generate a link to this. * @param array $mockuser the user on the platform who is performing the launch. * @param string|null $resourcelinkid the id of resource link in the platform, if desired. - * @param bool $ags whether to include a mock AGS claim or not. + * @param array|null $ags array representing the lti-ags claim info. Pass null to omit, empty array to use a default. * @param bool $nrps whether to include a mock NRPS claim or not. * @param array|null $migrationclaiminfo contains consumer key, secret and any fields which are sent in the claim. * @param array|null $customparams an array of custom params to send, or null to just use defaults. @@ -134,7 +134,7 @@ protected function get_mock_launch_users_with_ids(array $ids, bool $includepictu * @return LtiMessageLaunch the mock launch object with test launch data. */ protected function get_mock_launch(\stdClass $resource, array $mockuser, - ?string $resourcelinkid = null, bool $ags = true, bool $nrps = true, ?array $migrationclaiminfo = null, + ?string $resourcelinkid = null, ?array $ags = [], bool $nrps = true, ?array $migrationclaiminfo = null, ?array $customparams = null, $aud = '123'): LtiMessageLaunch { $mocklaunch = $this->getMockBuilder(LtiMessageLaunch::class) @@ -184,16 +184,21 @@ function() ]; } - if ($ags) { - $data["https://purl.imsglobal.org/spec/lti-ags/claim/endpoint"] = [ - "scope" => [ - "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", - "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", - "https://purl.imsglobal.org/spec/lti-ags/scope/score" - ], - "lineitems" => "https://platform.example.com/10/lineitems/", - "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" - ]; + if (is_array($ags)) { + if (empty($ags)) { + $agsclaim = [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", + "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitems" => "https://platform.example.com/10/lineitems/", + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ]; + } else { + $agsclaim = $ags; + } + $data["https://purl.imsglobal.org/spec/lti-ags/claim/endpoint"] = $agsclaim; } if ($nrps) { diff --git a/enrol/lti/tests/local/ltiadvantage/service/tool_launch_service_test.php b/enrol/lti/tests/local/ltiadvantage/service/tool_launch_service_test.php index 340680f50ad8d..6558e44ec4328 100644 --- a/enrol/lti/tests/local/ltiadvantage/service/tool_launch_service_test.php +++ b/enrol/lti/tests/local/ltiadvantage/service/tool_launch_service_test.php @@ -72,7 +72,7 @@ public function test_user_launches_tool(?array $legacydata, ?array $launchdata, } // Get a mock 1.3 launch, optionally including the lti1p1 migration claim based on a legacy tool secret. - $mocklaunch = $this->get_mock_launch($modresource, $launchdata['user'], null, true, true, + $mocklaunch = $this->get_mock_launch($modresource, $launchdata['user'], null, [], true, $launchdata['launch_migration_claim']); // Call the service. @@ -278,7 +278,7 @@ public function test_user_launches_tool_missing_custom_id() { $instructoruser = $this->getDataGenerator()->create_user(); $launchservice = $this->get_tool_launch_service(); $mockuser = $this->get_mock_launch_users_with_ids(['1p3_1'])[0]; - $mocklaunch = $this->get_mock_launch($modresource, $mockuser, null, false, false, null, []); + $mocklaunch = $this->get_mock_launch($modresource, $mockuser, null, null, false, null, []); $this->expectException(\moodle_exception::class); $this->expectExceptionMessage(get_string('ltiadvlauncherror:missingid', 'enrol_lti')); @@ -296,7 +296,7 @@ public function test_user_launches_tool_invalid_custom_id() { $instructoruser = $this->getDataGenerator()->create_user(); $launchservice = $this->get_tool_launch_service(); $mockuser = $this->get_mock_launch_users_with_ids(['1p3_1'])[0]; - $mocklaunch = $this->get_mock_launch($modresource, $mockuser, null, false, false, null, ['id' => 999999]); + $mocklaunch = $this->get_mock_launch($modresource, $mockuser, null, null, false, null, ['id' => 999999]); $this->expectException(\moodle_exception::class); $this->expectExceptionMessage(get_string('ltiadvlauncherror:invalidid', 'enrol_lti', 999999)); @@ -501,11 +501,11 @@ public function test_user_launches_tool_force_embedding_custom_param() { $learneruser = $this->getDataGenerator()->create_user(); $mockinstructoruser = $this->get_mock_launch_users_with_ids(['1'])[0]; $mocklearneruser = $this->get_mock_launch_users_with_ids(['1'], false, '')[0]; - $mockinstructorlaunch = $this->get_mock_launch($modresource, $mockinstructoruser, null, false, false, null, [ + $mockinstructorlaunch = $this->get_mock_launch($modresource, $mockinstructoruser, null, null, false, null, [ 'id' => $modresource->uuid, 'forcedembed' => true ]); - $mocklearnerlaunch = $this->get_mock_launch($modresource, $mocklearneruser, null, false, false, null, [ + $mocklearnerlaunch = $this->get_mock_launch($modresource, $mocklearneruser, null, null, false, null, [ 'id' => $modresource->uuid, 'forcedembed' => true ]); @@ -534,7 +534,7 @@ public function test_user_launches_tool_aud_variations($aud, array $expected) { [$course, $modresource] = $this->create_test_environment(); $instructoruser = $this->getDataGenerator()->create_user(); $mockinstructoruser = $this->get_mock_launch_users_with_ids(['1'])[0]; - $mockinstructorlaunch = $this->get_mock_launch($modresource, $mockinstructoruser, null, false, false, null, [ + $mockinstructorlaunch = $this->get_mock_launch($modresource, $mockinstructoruser, null, null, false, null, [ 'id' => $modresource->uuid, ], $aud); @@ -608,4 +608,221 @@ public function aud_data_provider(): array { ], ]; } + + /** + * Test verifying how changes to lti-ags claim information is handled across different launches. + * + * @param array $agsclaim1 the lti-ags claim data to use in the first launch. + * @param array $agsclaim2 the lti-ags claim data to use in the second launch. + * @param array $expected the array of test case expectations. + * @dataProvider ags_claim_provider + * @covers ::user_launches_tool + */ + public function test_user_launches_tool_ags_claim_handling(array $agsclaim1, array $agsclaim2, array $expected) { + $this->resetAfterTest(); + [$course, $modresource] = $this->create_test_environment(); + $instructoruser = $this->getDataGenerator()->create_user(); + $mockinstructoruser = $this->get_mock_launch_users_with_ids(['1'])[0]; + $userrepo = new user_repository(); + $resourcelinkrepo = new resource_link_repository(); + $launchservice = $this->get_tool_launch_service(); + + // Launch the first time. + $mockinstructorlaunch = $this->get_mock_launch($modresource, $mockinstructoruser, null, $agsclaim1, false, null, [ + 'id' => $modresource->uuid, + ]); + [$userid, $resource] = $launchservice->user_launches_tool($instructoruser, $mockinstructorlaunch); + + $ltiuser = $userrepo->find_single_user_by_resource($userid, $resource->id); + $resourcelink = $resourcelinkrepo->find_by_resource_and_user($resource->id, $ltiuser->get_id())[0]; + $gradeservice = $resourcelink->get_grade_service(); + $lineitemurl = $agsclaim1['lineitem'] ?? null; + $lineitemsurl = $agsclaim1['lineitems'] ?? null; + $this->assertEquals($agsclaim1['scope'], $gradeservice->get_scopes()); + $this->assertEquals($lineitemurl, $gradeservice->get_lineitemurl()); + $this->assertEquals($lineitemsurl, $gradeservice->get_lineitemsurl()); + + // Launch again, with a new lti-ags claim. + $mockinstructorlaunch = $this->get_mock_launch($modresource, $mockinstructoruser, null, $agsclaim2, false, null, [ + 'id' => $modresource->uuid, + ]); + [$userid, $resource] = $launchservice->user_launches_tool($instructoruser, $mockinstructorlaunch); + $ltiuser = $userrepo->find_single_user_by_resource($userid, $resource->id); + $resourcelink = $resourcelinkrepo->find_by_resource_and_user($resource->id, $ltiuser->get_id())[0]; + $gradeservice = $resourcelink->get_grade_service(); + $lineitemurl = $expected['lineitem'] ?? null; + $lineitemsurl = $expected['lineitems'] ?? null; + $this->assertEquals($expected['scope'], $gradeservice->get_scopes()); + $this->assertEquals($lineitemurl, $gradeservice->get_lineitemurl()); + $this->assertEquals($lineitemsurl, $gradeservice->get_lineitemsurl()); + } + + /** + * Data provider for testing user_launches tool with varying mocked lti-ags claim data over several launches. + * + * @return array the array of test case data. + */ + public function ags_claim_provider(): array { + return [ + 'Coupled line item with score post only, no change to scopes on subsequent launch' => [ + 'agsclaim1' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ], + 'agsclaim2' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ], + 'expected' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ] + ], + 'Coupled line item with score post only, addition to scopes on subsequent launch' => [ + 'agsclaim1' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ], + 'agsclaim2' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", + "https://purl.imsglobal.org/spec/lti-ags/scope/score", + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ], + 'expected' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", + "https://purl.imsglobal.org/spec/lti-ags/scope/score", + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ] + ], + 'Coupled line item with score post + result read, removal of scopes on subsequent launch' => [ + 'agsclaim1' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ], + 'agsclaim2' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/score", + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ], + 'expected' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/score", + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ] + ], + 'Decoupled line items with all capabilities, change and removal of scopes on subsequent launch' => [ + 'agsclaim1' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", + "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitems" => "https://platform.example.com/10/lineitems/", + ], + 'agsclaim2' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly", + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitems" => "https://platform.example.com/10/lineitems/", + ], + 'expected' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly", + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitems" => "https://platform.example.com/10/lineitems/", + ] + ], + 'Decoupled line items with all capabilities, removal of scopes on subsequent launch' => [ + 'agsclaim1' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", + "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitems" => "https://platform.example.com/10/lineitems/", + ], + 'agsclaim2' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitems" => "https://platform.example.com/10/lineitems/", + ], + 'expected' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitems" => "https://platform.example.com/10/lineitems/", + ] + ], + 'Coupled line items with score post only, addition of lineitemsurl and all capabilities on subsequent launch' => [ + 'agsclaim1' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ], + 'agsclaim2' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", + "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitems" => "https://platform.example.com/10/lineitems/", + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ], + 'expected' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", + "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitems" => "https://platform.example.com/10/lineitems/", + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ] + ], + 'Decoupled line items with all capabilities, change to coupled line item with score post only on subsequent launch' => [ + 'agsclaim1' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", + "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitems" => "https://platform.example.com/10/lineitems/", + ], + 'agsclaim2' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ], + 'expected' => [ + "scope" => [ + "https://purl.imsglobal.org/spec/lti-ags/scope/score" + ], + "lineitem" => "https://platform.example.com/10/lineitems/45/lineitem" + ] + ], + ]; + } } diff --git a/enrol/lti/tests/local/ltiadvantage/task/sync_grades_test.php b/enrol/lti/tests/local/ltiadvantage/task/sync_grades_test.php index 79896db8737a7..e8680a5f238bd 100644 --- a/enrol/lti/tests/local/ltiadvantage/task/sync_grades_test.php +++ b/enrol/lti/tests/local/ltiadvantage/task/sync_grades_test.php @@ -394,7 +394,7 @@ public function test_sync_grades_no_service_endpoint() { // Launch the resource for an instructor which will create the domain objects needed for service calls. $teachermocklaunch = $this->get_mock_launch($resource, $this->get_mock_launch_users_with_ids(['1'], false)[0], - null, false); + null, null); $instructoruser = $this->getDataGenerator()->create_user(); [$userid] = $launchservice->user_launches_tool($instructoruser, $teachermocklaunch); diff --git a/enrol/lti/tests/local/ltiadvantage/task/sync_members_test.php b/enrol/lti/tests/local/ltiadvantage/task/sync_members_test.php index 06cf3483efc28..7a2c3d3044c4b 100644 --- a/enrol/lti/tests/local/ltiadvantage/task/sync_members_test.php +++ b/enrol/lti/tests/local/ltiadvantage/task/sync_members_test.php @@ -621,7 +621,7 @@ public function test_sync_no_nrps_support() { // Launch the tool for a user. $mockinstructor = $this->get_mock_launch_users_with_ids([1])[0]; - $mocklaunch = $this->get_mock_launch($resource, $mockinstructor, null, false, false); + $mocklaunch = $this->get_mock_launch($resource, $mockinstructor, null, null, false); $launchservice = $this->get_tool_launch_service(); $instructoruser = $this->lti_advantage_user_authenticates('1'); $launchservice->user_launches_tool($instructoruser, $mocklaunch); @@ -708,7 +708,7 @@ public function test_sync_enrolments_and_migration(?array $legacydata, ?array $r } // Mock the launch for the specified user. - $mocklaunch = $this->get_mock_launch($resource, $launchdata['user'], null, true, true, + $mocklaunch = $this->get_mock_launch($resource, $launchdata['user'], null, [], true, $launchdata['launch_migration_claim']); // Perform the launch.