Rev 11 | AutorÃa | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>.namespace enrol_lti\local\ltiadvantage\repository;use enrol_lti\local\ltiadvantage\entity\application_registration;use enrol_lti\local\ltiadvantage\entity\user;/*** Tests for user_repository objects.** @package enrol_lti* @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later* @coversDefaultClass \enrol_lti\local\ltiadvantage\repository\user_repository*/final class user_repository_test extends \advanced_testcase {/*** Helper to generate a new user instance.** @param int $mockresourceid used to spoof a published resource, to which this user is associated.* @param array $userfields user information like city, timezone which would normally come from the tool configuration.* @return user a user instance*/protected function generate_user(int $mockresourceid = 1, array $userfields = []): user {global $CFG;$registration = application_registration::create('Test','a2c94a2c94',new \moodle_url('http://lms.example.org'),'clientid_123',new \moodle_url('https://example.org/authrequesturl'),new \moodle_url('https://example.org/jwksurl'),new \moodle_url('https://example.org/accesstokenurl'));$registrationrepo = new application_registration_repository();$createdregistration = $registrationrepo->save($registration);$deployment = $createdregistration->add_tool_deployment('Deployment 1', 'DeployID123');$deploymentrepo = new deployment_repository();$saveddeployment = $deploymentrepo->save($deployment);$contextrepo = new context_repository();$context = $saveddeployment->add_context('CTX123',['http://purl.imsglobal.org/vocab/lis/v2/course#CourseSection']);$savedcontext = $contextrepo->save($context);$resourcelinkrepo = new resource_link_repository();$resourcelink = $saveddeployment->add_resource_link('resourcelinkid_123', $mockresourceid,$savedcontext->get_id());$savedresourcelink = $resourcelinkrepo->save($resourcelink);// Create a user using the DB defaults to simulate what would have occurred during an auth_lti user auth.$user = $this->getDataGenerator()->create_user(['city' => '','country' => '','institution' => '','timezone' => '99','maildisplay' => 2,'lang' => 'en']);$userdefaultvalues = ['lang' => $CFG->lang,'city' => '','country' => '','institution' => '','timezone' => '99','maildisplay' => 2];if (empty($userfields)) {// If userfields is omitted, assume the tool default configuration values (as if 'User default values' are unchanged).$userfields = $userdefaultvalues;} else {// If they have been provided, merge and override the defaults.$userfields = array_merge($userdefaultvalues, $userfields);}$ltiuser = $savedresourcelink->add_user($user->id,'source-id-123',...array_values($userfields));$ltiuser->set_lastgrade(67.33333333);return $ltiuser;}/*** Helper to assert that all the key elements of two users (i.e. excluding id) are equal.** @param user $expected the user whose values are deemed correct.* @param user $check the user to check.* @param bool $checkresourcelink whether or not to confirm the resource link value matches too.*/protected function assert_same_user_values(user $expected, user $check, bool $checkresourcelink = false): void {$this->assertEquals($expected->get_deploymentid(), $check->get_deploymentid());$this->assertEquals($expected->get_city(), $check->get_city());$this->assertEquals($expected->get_country(), $check->get_country());$this->assertEquals($expected->get_institution(), $check->get_institution());$this->assertEquals($expected->get_timezone(), $check->get_timezone());$this->assertEquals($expected->get_maildisplay(), $check->get_maildisplay());$this->assertEquals($expected->get_lang(), $check->get_lang());if ($checkresourcelink) {$this->assertEquals($expected->get_resourcelinkid(), $check->get_resourcelinkid());}}/*** Helper to assert that all the key elements of a user are present in the DB.** @param user $expected the user whose values are deemed correct.*/protected function assert_user_db_values(user $expected) {global $DB;$sql = "SELECT u.username, u.firstname, u.lastname, u.email, u.city, u.country, u.institution, u.timezone,u.maildisplay, u.mnethostid, u.confirmed, u.lang, u.authFROM {enrol_lti_users} luJOIN {user} uON (lu.userid = u.id)WHERE lu.id = :id";$userrecord = $DB->get_record_sql($sql, ['id' => $expected->get_id()]);$this->assertEquals($expected->get_city(), $userrecord->city);$this->assertEquals($expected->get_country(), $userrecord->country);$this->assertEquals($expected->get_institution(), $userrecord->institution);$this->assertEquals($expected->get_timezone(), $userrecord->timezone);$this->assertEquals($expected->get_maildisplay(), $userrecord->maildisplay);$this->assertEquals($expected->get_lang(), $userrecord->lang);$ltiuserrecord = $DB->get_record('enrol_lti_users', ['id' => $expected->get_id()]);$this->assertEquals($expected->get_id(), $ltiuserrecord->id);$this->assertEquals($expected->get_sourceid(), $ltiuserrecord->sourceid);$this->assertEquals($expected->get_resourceid(), $ltiuserrecord->toolid);$this->assertEquals($expected->get_lastgrade(), $ltiuserrecord->lastgrade);if ($expected->get_resourcelinkid()) {$sql = "SELECT rl.idFROM {enrol_lti_users} luJOIN {enrol_lti_user_resource_link} rljON (lu.id = rlj.ltiuserid)JOIN {enrol_lti_resource_link} rlON (rl.id = rlj.resourcelinkid)WHERE lu.id = :id";$resourcelinkrecord = $DB->get_record_sql($sql, ['id' => $expected->get_id()]);$this->assertEquals($expected->get_resourcelinkid(), $resourcelinkrecord->id);}}/*** Tests adding a user to the store, assuming that the user has been created using the default 'user default values'.** @covers ::save*/public function test_save_new_unchanged_user_defaults(): void {$this->resetAfterTest();$user = $this->generate_user();$userrepo = new user_repository();$sink = $this->redirectEvents();$saveduser = $userrepo->save($user);$events = $sink->get_events();$sink->close();$this->assertIsInt($saveduser->get_id());$this->assert_same_user_values($user, $saveduser, true);$this->assert_user_db_values($saveduser);// No change to underlying user: city, etc. take on default values matching those of the existing user record.$this->assertEmpty($events);}/*** Tests adding a user to the store, assuming that the user has been created using modified 'user default values'.** @covers ::save*/public function test_save_new_changed_user_defaults(): void {$this->resetAfterTest();$user = $this->generate_user(1, ['city' => 'Perth']);$userrepo = new user_repository();$sink = $this->redirectEvents();$saveduser = $userrepo->save($user);$events = $sink->get_events();$sink->close();$this->assertIsInt($saveduser->get_id());$this->assert_same_user_values($user, $saveduser, true);$this->assert_user_db_values($saveduser);// The underlying user record will change: city ('Perth') differs from that of the existing user ('').$this->assertInstanceOf(\core\event\user_updated::class, $events[0]);}/*** Test saving an existing user instance.** @covers ::save*/public function test_save_existing(): void {$this->resetAfterTest();$user = $this->generate_user();$userrepo = new user_repository();$sink = $this->redirectEvents();$saveduser = $userrepo->save($user);$events = $sink->get_events();$sink->close();$this->assertEmpty($events); // No event for the first save, since the underlying user record is unchanged.$saveduser->set_city('New City');$saveduser->set_country('NZ');$saveduser->set_lastgrade(99.99999999);$sink = $this->redirectEvents();$saveduser2 = $userrepo->save($saveduser);$events = $sink->get_events();$sink->close();$this->assertEquals($saveduser->get_id(), $saveduser2->get_id());$this->assert_same_user_values($saveduser, $saveduser2, true);$this->assert_user_db_values($saveduser2);// The underlying user record will change now, since city and country have changed.$this->assertInstanceOf(\core\event\user_updated::class, $events[0]);}/*** Test saving an instance which exists by id, but has a different localid to the data in the store.** @covers ::save*/public function test_save_existing_localid_mismatch(): void {$this->resetAfterTest();$user = $this->generate_user();$userrepo = new user_repository();$saveduser = $userrepo->save($user);$user2 = user::create($saveduser->get_resourceid(),999999,$saveduser->get_deploymentid(),$saveduser->get_sourceid(),$saveduser->get_lang(),$saveduser->get_timezone(),'','','',null,null,null,null,$saveduser->get_id());$this->expectException(\coding_exception::class);$this->expectExceptionMessage("Cannot update user mapping. LTI user '{$saveduser->get_id()}' is already mapped " ."to user '{$saveduser->get_localid()}' and can't be associated with another user '999999'.");$userrepo->save($user2);}/*** Test trying to save a user with an id that is invalid.** @covers ::save*/public function test_save_stale_id(): void {global $CFG;$this->resetAfterTest();$instructoruser = $this->getDataGenerator()->create_user();$userrepo = new user_repository();$user = user::create(4,$instructoruser->id,5,'source-id-123',$CFG->lang,'99','','','',null,null,null,null,999999);$this->expectException(\coding_exception::class);$this->expectExceptionMessage("Cannot save lti user with id '999999'. The record does not exist.");$userrepo->save($user);}/*** Verify that trying to save a stale object results in an exception referring to unique constraint violation.** @covers ::save*/public function test_save_uniqueness_constraint(): void {$this->resetAfterTest();$user = $this->generate_user();$userrepo = new user_repository();$userrepo->save($user);$this->expectException(\coding_exception::class);$this->expectExceptionMessageMatches("/Cannot create duplicate LTI user '[a-z0-9_]*' for resource '[0-9]*'/");$userrepo->save($user);}/*** Test finding a user instance by id.** @covers ::find*/public function test_find(): void {$this->resetAfterTest();$user = $this->generate_user();$userrepo = new user_repository();$saveduser = $userrepo->save($user);$founduser = $userrepo->find($saveduser->get_id());$this->assertIsInt($founduser->get_id());$this->assert_same_user_values($saveduser, $founduser, false);$this->assertNull($userrepo->find(0));}/*** Test finding all of users associated with a given published resource.** @covers ::find_by_resource*/public function test_find_by_resource(): void {global $CFG;$this->resetAfterTest();$user = $this->generate_user();$userrepo = new user_repository();$saveduser = $userrepo->save($user);$instructoruser = $this->getDataGenerator()->create_user();$user2 = user::create($saveduser->get_resourceid(),$instructoruser->id,$saveduser->get_deploymentid(),'another-user-123',$CFG->lang,'99','Perth','AU','An Example Institution',2);$saveduser2 = $userrepo->save($user2);$savedusers = [$saveduser->get_id() => $saveduser, $saveduser2->get_id() => $saveduser2];$foundusers = $userrepo->find_by_resource($saveduser->get_resourceid());$this->assertCount(2, $foundusers);foreach ($foundusers as $founduser) {$this->assert_same_user_values($savedusers[$founduser->get_id()], $founduser);}}/*** Test that users can be found based on their resource_link association.** @covers ::find_by_resource_link*/public function test_find_by_resource_link(): void {global $CFG;$this->resetAfterTest();$user = $this->generate_user();$user->set_resourcelinkid(33);$userrepo = new user_repository();$saveduser = $userrepo->save($user);$instructoruser = $this->getDataGenerator()->create_user();$user2 = user::create($saveduser->get_resourceid(),$instructoruser->id,$saveduser->get_deploymentid(),'another-user-123',$CFG->lang,'99','Perth','AU','An Example Institution',2,null,null,33);$saveduser2 = $userrepo->save($user2);$savedusers = [$saveduser->get_id() => $saveduser, $saveduser2->get_id() => $saveduser2];$foundusers = $userrepo->find_by_resource_link(33);$this->assertCount(2, $foundusers);foreach ($foundusers as $founduser) {$this->assert_same_user_values($savedusers[$founduser->get_id()], $founduser);}}/*** Test checking existence of a user instance, based on id.** @covers ::exists*/public function test_exists(): void {$this->resetAfterTest();$user = $this->generate_user();$userrepo = new user_repository();$saveduser = $userrepo->save($user);$this->assertTrue($userrepo->exists($saveduser->get_id()));$this->assertFalse($userrepo->exists(-50));}/*** Test deleting a user instance, based on id.** @covers ::delete*/public function test_delete(): void {$this->resetAfterTest();$user = $this->generate_user();$userrepo = new user_repository();$saveduser = $userrepo->save($user);$this->assertTrue($userrepo->exists($saveduser->get_id()));$userrepo->delete($saveduser->get_id());$this->assertFalse($userrepo->exists($saveduser->get_id()));global $DB;$this->assertFalse($DB->record_exists('enrol_lti_users', ['id' => $saveduser->get_id()]));$this->assertFalse($DB->record_exists('enrol_lti_user_resource_link', ['ltiuserid' => $saveduser->get_id()]));$this->assertTrue($DB->record_exists('user', ['id' => $saveduser->get_localid()]));$this->assertNull($userrepo->delete($saveduser->get_id()));}/*** Test deleting a collection of lti user instances by deployment.** @covers ::delete_by_deployment*/public function test_delete_by_deployment(): void {global $CFG;$this->resetAfterTest();$user = $this->generate_user();$userrepo = new user_repository();$saveduser = $userrepo->save($user);$instructoruser = $this->getDataGenerator()->create_user();$instructor2user = $this->getDataGenerator()->create_user();$user2 = user::create($saveduser->get_resourceid(),$instructoruser->id,$saveduser->get_deploymentid(),'another-user-123',$CFG->lang,'99','Perth','AU','An Example Institution',);$saveduser2 = $userrepo->save($user2);$user3 = user::create($saveduser->get_resourceid(),$instructor2user->id,$saveduser->get_deploymentid() + 1,'another-user-678',$CFG->lang,'99','Melbourne','AU','An Example Institution',);$saveduser3 = $userrepo->save($user3);$this->assertTrue($userrepo->exists($saveduser->get_id()));$this->assertTrue($userrepo->exists($saveduser2->get_id()));$this->assertTrue($userrepo->exists($saveduser3->get_id()));$userrepo->delete_by_deployment($saveduser->get_deploymentid());$this->assertFalse($userrepo->exists($saveduser->get_id()));$this->assertFalse($userrepo->exists($saveduser2->get_id()));$this->assertTrue($userrepo->exists($saveduser3->get_id()));}/*** Verify a user who has been deleted can be re-saved to the repository and matched to an existing local user.** @covers ::save*/public function test_save_deleted(): void {$this->resetAfterTest();$user = $this->generate_user();$userrepo = new user_repository();$saveduser = $userrepo->save($user);$userrepo->delete($saveduser->get_id());$this->assertFalse($userrepo->exists($saveduser->get_id()));$saveduser2 = $userrepo->save($user);$this->assertEquals($saveduser->get_localid(), $saveduser2->get_localid());$this->assertNotEquals($saveduser->get_id(), $saveduser2->get_id());}/*** Test confirming that any associated legacy lti user records are not returned by the repository.** This test ensures that any enrolment methods (resources) updated in-place from legacy LTI to 1.3 only return LTI 1.3 users.** @covers ::find* @covers ::find_single_user_by_resource* @covers ::find_by_resource*/public function test_find_filters_legacy_lti_users(): void {$this->resetAfterTest();global $DB;$user = $this->getDataGenerator()->create_user();$course = $this->getDataGenerator()->create_course();$resource = $this->getDataGenerator()->create_lti_tool((object)['courseid' => $course->id]);$ltiuserdata = ['userid' => $user->id,'toolid' => $resource->id,'sourceid' => '1001',];$ltiuserid = $DB->insert_record('enrol_lti_users', $ltiuserdata);$userrepo = new user_repository();$this->assertNull($userrepo->find($ltiuserid));$this->assertNull($userrepo->find_single_user_by_resource($user->id, $resource->id));$this->assertEmpty($userrepo->find_by_resource($resource->id));// Set deploymentid, indicating the user originated from an LTI 1.3 launch and should now be returned.$ltiuserdata['id'] = $ltiuserid;$ltiuserdata['ltideploymentid'] = '234';$DB->update_record('enrol_lti_users', $ltiuserdata);$this->assertInstanceOf(user::class, $userrepo->find($ltiuserid));$this->assertInstanceOf(user::class, $userrepo->find_single_user_by_resource($user->id, $resource->id));$ltiusers = $userrepo->find_by_resource($resource->id);$this->assertCount(1, $ltiusers);$this->assertInstanceOf(user::class, reset($ltiusers));}}