From 5f6ebef73090f45f0817afa9d2f37c784f6bf57d Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Fri, 7 Feb 2025 09:02:25 +0100 Subject: [PATCH 1/2] MDL-67462 badges: Fix badge alignments returned in external functions --- lib/badgeslib.php | 51 +++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/lib/badgeslib.php b/lib/badgeslib.php index 62e3e7681fe..d75049cba05 100644 --- a/lib/badgeslib.php +++ b/lib/badgeslib.php @@ -462,19 +462,26 @@ function badges_prepare_badge_for_external(stdClass $badge, stdClass $user): obj // Create a badge instance to be able to get the endorsement and other info. $badgeinstance = new badge($badge->id); $endorsement = $badgeinstance->get_endorsement(); - $alignments = $badgeinstance->get_alignments(); $relatedbadges = $badgeinstance->get_related_badges(); + $alignments = []; + foreach ($badgeinstance->get_alignments() as $alignment) { + $alignmentobj = (object) [ + 'id' => $alignment->id, + 'badgeid' => $alignment->badgeid, + 'targetName' => $alignment->targetname, + 'targetUrl' => $alignment->targeturl, + ]; + // Include only the properties visible by the user. + if ($canconfiguredetails) { + $alignmentobj->targetDescription = $alignment->targetdescription; + $alignmentobj->targetFramework = $alignment->targetframework; + $alignmentobj->targetCode = $alignment->targetcode; + } + $alignments[] = $alignmentobj; + } if (!$canconfiguredetails) { // Return only the properties visible by the user. - if (!empty($alignments)) { - foreach ($alignments as $alignment) { - unset($alignment->targetdescription); - unset($alignment->targetframework); - unset($alignment->targetcode); - } - } - if (!empty($relatedbadges)) { foreach ($relatedbadges as $relatedbadge) { unset($relatedbadge->version); @@ -530,21 +537,27 @@ function badges_prepare_badgeclass_for_external(core_badges\output\badgeclass $b // Create a badge instance to be able to get the endorsement and other info. $badgeinstance = new badge($badgeclass->badge->id); $endorsement = $badgeinstance->get_endorsement(); - $alignments = $badgeinstance->get_alignments(); $relatedbadges = $badgeinstance->get_related_badges(); - $canconfiguredetails = has_capability('moodle/badges:configuredetails', $context); + $alignments = []; + foreach ($badgeinstance->get_alignments() as $alignment) { + $alignmentobj = (object) [ + 'id' => $alignment->id, + 'badgeid' => $alignment->badgeid, + 'targetName' => $alignment->targetname, + 'targetUrl' => $alignment->targeturl, + ]; + // Include only the properties visible by the user. + if ($canconfiguredetails) { + $alignmentobj->targetDescription = $alignment->targetdescription; + $alignmentobj->targetFramework = $alignment->targetframework; + $alignmentobj->targetCode = $alignment->targetcode; + } + $alignments[] = $alignmentobj; + } if (!$canconfiguredetails) { // Return only the properties visible by the user. - if (!empty($alignments)) { - foreach ($alignments as $alignment) { - unset($alignment->targetdescription); - unset($alignment->targetframework); - unset($alignment->targetcode); - } - } - if (!empty($relatedbadges)) { foreach ($relatedbadges as $relatedbadge) { unset($relatedbadge->version); From 86016f1e11c0696ae706194ce03c7c8c1d593a54 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Fri, 7 Feb 2025 09:02:25 +0100 Subject: [PATCH 2/2] MDL-67462 badges: Improve tests of external functions Now units tests covering external functions `core_badges_get_badge`, `core_badges_get_user_badges` and `core_badges_get_user_badge_by_hash` check all badge fields, including cases where the user has no permission to view all details. --- badges/tests/classes/external_helper.php | 290 ++++++++++++++++++ badges/tests/external/external_test.php | 218 ++----------- badges/tests/external/get_badge_test.php | 96 +++--- .../external/get_user_badge_by_hash_test.php | 222 +++----------- 4 files changed, 400 insertions(+), 426 deletions(-) create mode 100644 badges/tests/classes/external_helper.php diff --git a/badges/tests/classes/external_helper.php b/badges/tests/classes/external_helper.php new file mode 100644 index 00000000000..16f970d1248 --- /dev/null +++ b/badges/tests/classes/external_helper.php @@ -0,0 +1,290 @@ +. + +namespace core_badges\tests; + +/** + * Helper trait for external function tests. + * + * @package core_badges + * @copyright 2025 Moodle Pty Ltd + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +trait external_helper { + + /** + * Asserts that an issued badge returned by an external function matches the given data. + * + * @param array $expected Expected badge data. + * @param array $actual Actual badge data returned by the external function. + * @param bool $isrecipient True if user is the badge recipient. + * @param bool $canconfiguredetails True if user has capability "moodle/badges:configuredetails". + * @throws \PHPUnit\Framework\ExpectationFailedException + */ + protected function assert_issued_badge(array $expected, array $actual, bool $isrecipient, bool $canconfiguredetails): void { + $this->assertEquals($expected['id'], $actual['id']); + $this->assertEquals($expected['name'], $actual['name']); + $this->assertEquals($expected['type'], $actual['type']); + $this->assertEquals($expected['description'], $actual['description']); + $this->assertEquals($expected['issuername'], $actual['issuername']); + $this->assertEquals($expected['issuerurl'], $actual['issuerurl']); + $this->assertEquals($expected['issuercontact'], $actual['issuercontact']); + $this->assertEquals($expected['uniquehash'], $actual['uniquehash']); + $this->assertEquals($expected['dateissued'], $actual['dateissued']); + $this->assertEquals($expected['dateexpire'], $actual['dateexpire']); + $this->assertEquals($expected['version'], $actual['version']); + $this->assertEquals($expected['language'], $actual['language']); + $this->assertEquals($expected['imagecaption'], $actual['imagecaption']); + $this->assertEquals($expected['badgeurl'], $actual['badgeurl']); + $this->assertEquals($expected['recipientid'], $actual['recipientid']); + $this->assertEquals($expected['recipientfullname'], $actual['recipientfullname']); + $this->assertEquals($expected['endorsement'] ?? null, $actual['endorsement'] ?? null); + + if ($isrecipient || $canconfiguredetails) { + $this->assertTimeCurrent($expected['timecreated']); + $this->assertTimeCurrent($expected['timemodified']); + $this->assertEquals($expected['usercreated'], $actual['usercreated']); + $this->assertEquals($expected['usermodified'], $actual['usermodified']); + $this->assertEquals($expected['expiredate'], $actual['expiredate']); + $this->assertEquals($expected['expireperiod'], $actual['expireperiod']); + $this->assertEquals($expected['courseid'], $actual['courseid']); + $this->assertEquals($expected['message'], $actual['message']); + $this->assertEquals($expected['messagesubject'], $actual['messagesubject']); + $this->assertEquals($expected['attachment'], $actual['attachment']); + $this->assertEquals($expected['notification'], $actual['notification']); + $this->assertEquals($expected['nextcron'], $actual['nextcron']); + $this->assertEquals($expected['status'], $actual['status']); + $this->assertEquals($expected['issuedid'], $actual['issuedid']); + $this->assertEquals($expected['visible'], $actual['visible']); + $this->assertEquals($expected['email'], $actual['email']); + } else { + $this->assertEquals(0, $actual['timecreated']); + $this->assertEquals(0, $actual['timemodified']); + $this->assertArrayNotHasKey('usercreated', $actual); + $this->assertArrayNotHasKey('usermodified', $actual); + $this->assertArrayNotHasKey('expiredate', $actual); + $this->assertArrayNotHasKey('expireperiod', $actual); + $this->assertArrayNotHasKey('courseid', $actual); + $this->assertArrayNotHasKey('message', $actual); + $this->assertArrayNotHasKey('messagesubject', $actual); + $this->assertEquals(1, $actual['attachment']); + $this->assertEquals(1, $actual['notification']); + $this->assertArrayNotHasKey('nextcron', $actual); + $this->assertEquals(0, $actual['status']); + $this->assertArrayNotHasKey('issuedid', $actual); + $this->assertEquals(0, $actual['visible']); + $this->assertArrayNotHasKey('email', $actual); + } + + $alignments = $expected['alignment']; + if (!$canconfiguredetails) { + foreach ($alignments as $index => $alignment) { + $alignments[$index] = [ + 'id' => $alignment['id'], + 'badgeid' => $alignment['badgeid'], + 'targetName' => $alignment['targetName'], + 'targetUrl' => $alignment['targetUrl'], + ]; + } + } + $this->assertEquals($alignments, $actual['alignment']); + + $relatedbadges = $expected['relatedbadges']; + if (!$canconfiguredetails) { + foreach ($relatedbadges as $index => $relatedbadge) { + $relatedbadges[$index] = [ + 'id' => $relatedbadge['id'], + 'name' => $relatedbadge['name'], + ]; + } + } + $this->assertEquals($relatedbadges, $actual['relatedbadges']); + } + + /** + * Creates and returns test data for external functions. + * + * The test data includes: + * - A teacher. + * - A student. + * - A site badge, with an endorsment and 2 alignments, issued to the student by the teacher. + * - A course badge, related to the site badge, issued to the student by the teacher. + * + * @return array + */ + private function prepare_test_data(): array { + global $DB; + + $this->resetAfterTest(); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + + // Setup test data. + $course = $generator->create_course(); + + // Create users and enrolments. + $student = $generator->create_and_enrol($course); + $teacher = $generator->create_and_enrol($course, 'editingteacher'); + $badgegenerator = $generator->get_plugin_generator('core_badges'); + + $systemcontext = \context_system::instance(); + $coursecontext = \context_course::instance($course->id); + + // Create a site badge. + $now = time(); + $sitebadge = $badgegenerator->create_badge([ + 'name' => "Test badge site", + 'description' => "Testing badges site", + 'timecreated' => $now, + 'timemodified' => $now, + 'usercreated' => (int) $teacher->id, + 'usermodified' => (int) $teacher->id, + 'expiredate' => $now + YEARSECS, + 'expireperiod' => YEARSECS, + 'type' => BADGE_TYPE_SITE, + ]); + + $sitebadge->issue($student->id, true); + $siteissuedbadge = $DB->get_record('badge_issued', [ 'badgeid' => $sitebadge->id ]); + + // Change issued time to ensure badges are fetched in a consistent order. + $siteissuedbadge->dateissued = $now - 1; + $DB->update_record('badge_issued', $siteissuedbadge); + + $sitebadgedata = [ + ...(array) $sitebadge, + 'issuedid' => (int) $siteissuedbadge->id, + 'uniquehash' => $siteissuedbadge->uniquehash, + 'dateissued' => (int) $siteissuedbadge->dateissued, + 'dateexpire' => $siteissuedbadge->dateexpire, + 'visible' => (int) $siteissuedbadge->visible, + 'badgeurl' => \moodle_url::make_webservice_pluginfile_url($systemcontext->id, 'badges', 'badgeimage', + $sitebadge->id, '/', 'f3')->out(false), + 'recipientid' => $student->id, + 'recipientfullname' => fullname($student), + 'email' => $student->email, + 'endorsement' => null, + 'alignment' => [], + 'relatedbadges' => [], + ]; + + // Add an endorsement for the site badge. + $endorsement = new \stdClass(); + $endorsement->badgeid = $sitebadge->id; + $endorsement->issuername = 'Issuer name'; + $endorsement->issuerurl = 'http://endorsement-issuer-url.domain.co.nz'; + $endorsement->issueremail = 'endorsementissuer@example.com'; + $endorsement->claimid = 'http://claim-url.domain.co.nz'; + $endorsement->claimcomment = 'Claim comment'; + $endorsement->dateissued = $now; + $endorsement->id = $sitebadge->save_endorsement($endorsement); + $sitebadgedata['endorsement'] = (array) $endorsement; + + // Add 2 alignments to the site badge. + $alignment = new \stdClass(); + $alignment->badgeid = $sitebadge->id; + $alignment->targetname = 'Alignment 1'; + $alignment->targeturl = 'http://a1-target-url.domain.co.nz'; + $alignment->targetdescription = 'A1 target description'; + $alignment->targetframework = 'A1 framework'; + $alignment->targetcode = 'A1 code'; + $alignment->id = $sitebadge->save_alignment($alignment); + $sitebadgedata['alignment'][] = [ + 'id' => $alignment->id, + 'badgeid' => $alignment->badgeid, + 'targetName' => $alignment->targetname, + 'targetUrl' => $alignment->targeturl, + 'targetDescription' => $alignment->targetdescription, + 'targetFramework' => $alignment->targetframework, + 'targetCode' => $alignment->targetcode, + ]; + + $alignment = new \stdClass(); + $alignment->badgeid = $sitebadge->id; + $alignment->targetname = 'Alignment 2'; + $alignment->targeturl = 'http://a2-target-url.domain.co.nz'; + $alignment->targetdescription = 'A2 target description'; + $alignment->targetframework = 'A2 framework'; + $alignment->targetcode = 'A2 code'; + $alignment->id = $sitebadge->save_alignment($alignment); + $sitebadgedata['alignment'][] = [ + 'id' => $alignment->id, + 'badgeid' => $alignment->badgeid, + 'targetName' => $alignment->targetname, + 'targetUrl' => $alignment->targeturl, + 'targetDescription' => $alignment->targetdescription, + 'targetFramework' => $alignment->targetframework, + 'targetCode' => $alignment->targetcode, + ]; + + // Create a course badge. + $coursebadge = $badgegenerator->create_badge([ + 'name' => "Test badge course", + 'description' => "Testing badges course", + 'timecreated' => $now, + 'timemodified' => $now, + 'usercreated' => (int) $teacher->id, + 'usermodified' => (int) $teacher->id, + 'expiredate' => $now + YEARSECS, + 'expireperiod' => YEARSECS, + 'type' => BADGE_TYPE_COURSE, + 'courseid' => (int) $course->id, + ]); + $coursebadge->issue($student->id, true); + + $courseissuedbadge = $DB->get_record('badge_issued', [ 'badgeid' => $coursebadge->id ]); + $coursebadgedata = [ + ...(array) $coursebadge, + 'issuedid' => (int) $courseissuedbadge->id, + 'uniquehash' => $courseissuedbadge->uniquehash, + 'dateissued' => (int) $courseissuedbadge->dateissued, + 'dateexpire' => $courseissuedbadge->dateexpire, + 'visible' => (int) $courseissuedbadge->visible, + 'badgeurl' => \moodle_url::make_webservice_pluginfile_url($coursecontext->id, 'badges', 'badgeimage', + $coursebadge->id, '/', 'f3')->out(false), + 'recipientid' => $student->id, + 'recipientfullname' => fullname($student), + 'email' => $student->email, + 'endorsement' => null, + 'alignment' => [], + 'relatedbadges' => [], + ]; + + // Add the course badge to the site badge. + $sitebadge->add_related_badges([$coursebadge->id]); + $sitebadgedata['relatedbadges'][] = [ + 'id' => (int) $coursebadge->id, + 'name' => $coursebadge->name, + 'version' => $coursebadge->version, + 'language' => $coursebadge->language, + 'type' => $coursebadge->type, + ]; + $coursebadgedata['relatedbadges'][] = [ + 'id' => (int) $sitebadge->id, + 'name' => $sitebadge->name, + 'version' => $sitebadge->version, + 'language' => $sitebadge->language, + 'type' => $sitebadge->type, + ]; + + return [ + 'coursebadge' => $coursebadgedata, + 'sitebadge' => $sitebadgedata, + 'student' => $student, + 'teacher' => $teacher, + ]; + } +} diff --git a/badges/tests/external/external_test.php b/badges/tests/external/external_test.php index dbc0e13b57a..5fdcada9d2e 100644 --- a/badges/tests/external/external_test.php +++ b/badges/tests/external/external_test.php @@ -27,6 +27,7 @@ namespace core_badges\external; use core_badges_external; +use core_badges\tests\external_helper; use core_external\external_api; use core_external\external_settings; use externallib_advanced_testcase; @@ -48,223 +49,66 @@ require_once($CFG->libdir . '/badgeslib.php'); * @since Moodle 3.1 */ final class external_test extends externallib_advanced_testcase { - - /** @var stdClass $course */ - private $course; - - /** @var stdClass $student */ - private $student; - - /** @var stdClass $teacher */ - private $teacher; - - /** - * Set up for every test - */ - public function setUp(): void { - global $DB; - parent::setUp(); - $this->resetAfterTest(); - $this->setAdminUser(); - - // Setup test data. - $this->course = $this->getDataGenerator()->create_course(); - - // Create users and enrolments. - $this->student = $this->getDataGenerator()->create_and_enrol($this->course, 'student'); - $this->teacher = $this->getDataGenerator()->create_and_enrol($this->course, 'editingteacher'); - - // Mock up a site badge. - $now = time(); - $badge = new \stdClass(); - $badge->id = null; - $badge->name = "Test badge site"; - $badge->description = "Testing badges site"; - $badge->timecreated = $now - 12; - $badge->timemodified = $now - 12; - $badge->usercreated = $this->teacher->id; - $badge->usermodified = $this->teacher->id; - $badge->issuername = "Test issuer"; - $badge->issuerurl = "http://issuer-url.domain.co.nz"; - $badge->issuercontact = "issuer@example.com"; - $badge->expiredate = null; - $badge->expireperiod = null; - $badge->type = BADGE_TYPE_SITE; - $badge->courseid = null; - $badge->messagesubject = "Test message subject for badge"; - $badge->message = "Test message body for badge"; - $badge->attachment = 1; - $badge->notification = 0; - $badge->status = BADGE_STATUS_ACTIVE; - $badge->version = '1'; - $badge->language = 'en'; - $badge->imagecaption = 'Caption'; - - $badgeid = $DB->insert_record('badge', $badge, true); - $badge = new \badge($badgeid); - $badge->issue($this->student->id, true); - - // Hack the database to adjust the time each badge was issued. - $DB->set_field('badge_issued', 'dateissued', $now - 11, array('userid' => $this->student->id, 'badgeid' => $badgeid)); - - // Add an endorsement for the badge. - $endorsement = new \stdClass(); - $endorsement->badgeid = $badgeid; - $endorsement->issuername = 'Issuer name'; - $endorsement->issuerurl = 'http://endorsement-issuer-url.domain.co.nz'; - $endorsement->issueremail = 'endorsementissuer@example.com'; - $endorsement->claimid = 'http://claim-url.domain.co.nz'; - $endorsement->claimcomment = 'Claim comment'; - $endorsement->dateissued = $now; - $badge->save_endorsement($endorsement); - - // Add 2 alignments. - $alignment = new \stdClass(); - $alignment->badgeid = $badgeid; - $alignment->targetname = 'Alignment 1'; - $alignment->targeturl = 'http://a1-target-url.domain.co.nz'; - $alignment->targetdescription = 'A1 target description'; - $alignment->targetframework = 'A1 framework'; - $alignment->targetcode = 'A1 code'; - $badge->save_alignment($alignment); - - $alignment->targetname = 'Alignment 2'; - $alignment->targeturl = 'http://a2-target-url.domain.co.nz'; - $alignment->targetdescription = 'A2 target description'; - $alignment->targetframework = 'A2 framework'; - $alignment->targetcode = 'A2 code'; - $badge->save_alignment($alignment); - - // Now a course badge. - $badge->id = null; - $badge->name = "Test badge course"; - $badge->description = "Testing badges course"; - $badge->type = BADGE_TYPE_COURSE; - $badge->courseid = $this->course->id; - - $coursebadgeid = $DB->insert_record('badge', $badge, true); - $badge = new \badge($coursebadgeid); - $badge->issue($this->student->id, true); - - // Hack the database to adjust the time each badge was issued. - $DB->set_field('badge_issued', 'dateissued', $now - 10, array('userid' => $this->student->id, 'badgeid' => $coursebadgeid)); - - // Make the site badge a related badge. - $badge->add_related_badges(array($badgeid)); - } + use external_helper; /** * Test get user badges. * These is a basic test since the badges_get_my_user_badges used by the external function already has unit tests. + * + * @covers \core_badges_external::get_user_badges */ public function test_get_my_user_badges(): void { + $data = $this->prepare_test_data(); - $this->setUser($this->student); - - $badges = (array) badges_get_user_badges($this->student->id); - $expectedbadges = array(); - $coursebadge = null; - - foreach ($badges as $badge) { - $context = ($badge->type == BADGE_TYPE_SITE) ? \context_system::instance() : \context_course::instance($badge->courseid); - $badge->badgeurl = \moodle_url::make_webservice_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', - 'f3')->out(false); - - // Get the endorsement, alignments and related badges. - $badgeinstance = new \badge($badge->id); - $endorsement = $badgeinstance->get_endorsement(); - $alignments = $badgeinstance->get_alignments(); - $relatedbadges = $badgeinstance->get_related_badges(); - $badge->alignment = array(); - $badge->relatedbadges = array(); - - if ($endorsement) { - $badge->endorsement = (array) $endorsement; - } - - if (!empty($alignments)) { - foreach ($alignments as $alignment) { - // Students cannot see some fields of the alignments. - unset($alignment->targetname); - unset($alignment->targeturl); - unset($alignment->targetdescription); - unset($alignment->targetframework); - unset($alignment->targetcode); - - $badge->alignment[] = (array) $alignment; - } - } - - if (!empty($relatedbadges)) { - foreach ($relatedbadges as $relatedbadge) { - // Students cannot see some fields of the related badges. - unset($relatedbadge->version); - unset($relatedbadge->language); - unset($relatedbadge->type); - - $badge->relatedbadges[] = (array) $relatedbadge; - } - } - - // Add recipient. - $badge->recipientid = $this->student->id; - $badge->recipientfullname = fullname($this->student); - - $expectedbadges[] = (array) $badge; - if (isset($badge->courseid)) { - // Save the course badge to be able to compare it in our tests. - $coursebadge = (array) $badge; - } - } - + $this->setUser($data['student']); $result = core_badges_external::get_user_badges(); $result = external_api::clean_returnvalue(core_badges_external::get_user_badges_returns(), $result); - $this->assertEquals($expectedbadges, $result['badges']); + $this->assertCount(2, $result['badges']); + $this->assert_issued_badge($data['coursebadge'], $result['badges'][0], true, false); + $this->assert_issued_badge($data['sitebadge'], $result['badges'][1], true, false); // Pagination and filtering. - $result = core_badges_external::get_user_badges(0, $this->course->id, 0, 1, '', true); + $result = core_badges_external::get_user_badges(0, 0, 0, 1, '', true); $result = external_api::clean_returnvalue(core_badges_external::get_user_badges_returns(), $result); $this->assertCount(1, $result['badges']); - $this->assertEquals($coursebadge, $result['badges'][0]); + $this->assert_issued_badge($data['coursebadge'], $result['badges'][0], true, false); } /** * Test get user badges. + * + * @covers \core_badges_external::get_user_badges */ public function test_get_other_user_badges(): void { + $data = $this->prepare_test_data(); - $this->setUser($this->teacher); - - $result = core_badges_external::get_user_badges($this->student->id); + // User with "moodle/badges:configuredetails" capability. + $this->setAdminUser(); + $result = core_badges_external::get_user_badges($data['student']->id); $result = external_api::clean_returnvalue(core_badges_external::get_user_badges_returns(), $result); - $this->assertCount(2, $result['badges']); + $this->assert_issued_badge($data['coursebadge'], $result['badges'][0], false, true); + $this->assert_issued_badge($data['sitebadge'], $result['badges'][1], false, true); - // Check that we don't have permissions for view the complete information for site badges. - foreach ($result['badges'] as $badge) { - if (isset($badge['type']) and $badge['type'] == BADGE_TYPE_COURSE) { - $this->assertTrue(isset($badge['message'])); - - // Check that we have permissions to see all the data in alignments and related badges. - foreach ($badge['alignment'] as $alignment) { - $this->assertTrue(isset($alignment['targetdescription'])); - } - - foreach ($badge['relatedbadges'] as $relatedbadge) { - $this->assertTrue(isset($relatedbadge['type'])); - } - } else { - $this->assertFalse(isset($badge['message'])); - } - } + // User without "moodle/badges:configuredetails" capability. + $this->setUser($this->getDataGenerator()->create_user()); + $result = core_badges_external::get_user_badges($data['student']->id); + $result = external_api::clean_returnvalue(core_badges_external::get_user_badges_returns(), $result); + $this->assertCount(2, $result['badges']); + $this->assert_issued_badge($data['coursebadge'], $result['badges'][0], false, false); + $this->assert_issued_badge($data['sitebadge'], $result['badges'][1], false, false); } /** * Test get_user_badges where issuername contains text to be filtered + * + * @covers \core_badges_external::get_user_badges */ public function test_get_user_badges_filter_issuername(): void { global $DB; + $data = $this->prepare_test_data(); + filter_set_global_state('multilang', TEXTFILTER_ON); filter_set_applies_to_strings('multilang', true); @@ -275,7 +119,7 @@ final class external_test extends externallib_advanced_testcase { $DB->set_field('badge', 'issuername', $issuername, ['name' => 'Test badge site']); // Retrieve student badges. - $result = core_badges_external::get_user_badges($this->student->id); + $result = core_badges_external::get_user_badges($data['student']->id); $result = external_api::clean_returnvalue(core_badges_external::get_user_badges_returns(), $result); // Site badge will be last, because it has the earlier issued date. diff --git a/badges/tests/external/get_badge_test.php b/badges/tests/external/get_badge_test.php index 5d53965f6fa..7cf373867d0 100644 --- a/badges/tests/external/get_badge_test.php +++ b/badges/tests/external/get_badge_test.php @@ -16,6 +16,7 @@ namespace core_badges\external; +use core_badges\tests\external_helper; use externallib_advanced_testcase; defined('MOODLE_INTERNAL') || die(); @@ -23,7 +24,6 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); -require_once($CFG->libdir . '/badgeslib.php'); /** * Tests for external function get_badge. @@ -37,56 +37,7 @@ require_once($CFG->libdir . '/badgeslib.php'); * @coversDefaultClass \core_badges\external\get_badge */ final class get_badge_test extends externallib_advanced_testcase { - /** - * Prepare the test. - * - * @return array - */ - private function prepare_test_data(): array { - global $DB; - $this->resetAfterTest(); - $this->setAdminUser(); - set_config('enablebadges', 1); - - // Mock up a site badge. - $now = time(); - $badge = new \stdClass(); - $badge->id = null; - $badge->name = "Test badge site"; - $badge->description = "Testing badges site"; - $badge->timecreated = $now; - $badge->timemodified = $now; - $badge->usercreated = 2; - $badge->usermodified = 2; - $badge->expiredate = null; - $badge->expireperiod = null; - $badge->type = BADGE_TYPE_SITE; - $badge->courseid = null; - $badge->messagesubject = "Test message subject for badge"; - $badge->message = "Test message body for badge"; - $badge->attachment = 1; - $badge->notification = 0; - $badge->status = BADGE_STATUS_ACTIVE; - $badge->version = '1'; - $badge->language = 'en'; - $badge->imagecaption = 'Caption'; - - $badgeid = $DB->insert_record('badge', $badge, true); - $badge->id = $badgeid; - - $context = \context_system::instance(); - $badge->badgeurl = \moodle_url::make_webservice_pluginfile_url( - $context->id, - 'badges', - 'badgeimage', - $badge->id, - '/', - 'f3' - )->out(false); - $badge->status = BADGE_STATUS_ACTIVE_LOCKED; - - return ['badge' => (array) $badge]; - } + use external_helper; /** * Test get badge by id without enablebadges active in moodle. @@ -99,7 +50,7 @@ final class get_badge_test extends externallib_advanced_testcase { $this->expectException(\moodle_exception::class); $this->expectExceptionMessage('Badges are not enabled on this site.'); - get_badge::execute($data['badge']['id']); + get_badge::execute($data['sitebadge']['id']); } /** @@ -110,9 +61,44 @@ final class get_badge_test extends externallib_advanced_testcase { $data = $this->prepare_test_data(); // Test with an existing badge. - $result = get_badge::execute($data['badge']['id']); + $result = get_badge::execute($data['sitebadge']['id']); $result = \core_external\external_api::clean_returnvalue(get_badge::execute_returns(), $result); - $this->assertEquals($data['badge']['name'], $result['badge']['name']); + $this->assertEquals('BadgeClass', $result['badge']['type']); + $badgeurl = new \moodle_url('/badges/badgeclass.php', ['id' => $data['sitebadge']['id']]); + $this->assertEquals($badgeurl->out(false), $result['badge']['id']); + $this->assertEquals($data['sitebadge']['issuername'], $result['badge']['issuer']); + $this->assertEquals($data['sitebadge']['name'], $result['badge']['name']); + $this->assertEquals($data['sitebadge']['badgeurl'], $result['badge']['image']); + $this->assertEquals($data['sitebadge']['description'], $result['badge']['description']); + $this->assertEquals($data['sitebadge']['issuerurl'], $result['badge']['hostedUrl']); + $this->assertEquals($data['sitebadge']['alignment'], $result['badge']['alignment']); + $this->assertEmpty($result['warnings']); + } + + /** + * Test get badge by id with an unprivileged user. + * @covers ::execute + */ + public function test_get_badge_with_unprivileged_user(): void { + $data = $this->prepare_test_data(); + foreach ($data['sitebadge']['alignment'] as &$alignment) { + unset($alignment['targetDescription']); + unset($alignment['targetFramework']); + unset($alignment['targetCode']); + } + $this->setUser($this->getDataGenerator()->create_user()); + + $result = get_badge::execute($data['sitebadge']['id']); + $result = \core_external\external_api::clean_returnvalue(get_badge::execute_returns(), $result); + $this->assertEquals('BadgeClass', $result['badge']['type']); + $badgeurl = new \moodle_url('/badges/badgeclass.php', ['id' => $data['sitebadge']['id']]); + $this->assertEquals($badgeurl->out(false), $result['badge']['id']); + $this->assertEquals($data['sitebadge']['issuername'], $result['badge']['issuer']); + $this->assertEquals($data['sitebadge']['name'], $result['badge']['name']); + $this->assertEquals($data['sitebadge']['badgeurl'], $result['badge']['image']); + $this->assertEquals($data['sitebadge']['description'], $result['badge']['description']); + $this->assertEquals($data['sitebadge']['issuerurl'], $result['badge']['hostedUrl']); + $this->assertEquals($data['sitebadge']['alignment'], $result['badge']['alignment']); $this->assertEmpty($result['warnings']); } @@ -121,7 +107,7 @@ final class get_badge_test extends externallib_advanced_testcase { * @covers ::execute */ public function test_get_badge_with_invalid_badge_id(): void { - $data = $this->prepare_test_data(); + $this->prepare_test_data(); $this->expectException(\moodle_exception::class); get_badge::execute(123); diff --git a/badges/tests/external/get_user_badge_by_hash_test.php b/badges/tests/external/get_user_badge_by_hash_test.php index d5910dd5d46..ba3a9164579 100644 --- a/badges/tests/external/get_user_badge_by_hash_test.php +++ b/badges/tests/external/get_user_badge_by_hash_test.php @@ -16,6 +16,7 @@ namespace core_badges\external; +use core_badges\tests\external_helper; use externallib_advanced_testcase; defined('MOODLE_INTERNAL') || die(); @@ -23,7 +24,6 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); -require_once($CFG->libdir . '/badgeslib.php'); /** * Tests for external function get_user_badge_by_hash. @@ -36,138 +36,7 @@ require_once($CFG->libdir . '/badgeslib.php'); * @coversDefaultClass \core_badges\external\get_user_badge_by_hash */ final class get_user_badge_by_hash_test extends externallib_advanced_testcase { - - /** - * Prepare the test. - * - * @return array - */ - private function prepare_test_data(): array { - global $DB; - $this->resetAfterTest(); - $this->setAdminUser(); - - // Setup test data. - $course = $this->getDataGenerator()->create_course(); - - // Create users and enrolments. - $student1 = $this->getDataGenerator()->create_and_enrol($course); - $student2 = $this->getDataGenerator()->create_and_enrol($course); - $student3 = $this->getDataGenerator()->create_and_enrol($course); - $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); - - // Mock up a site badge. - $now = time(); - $badge = new \stdClass(); - $badge->id = null; - $badge->name = "Test badge site"; - $badge->description = "Testing badges site"; - $badge->timecreated = $now; - $badge->timemodified = $now; - $badge->usercreated = (int) $teacher->id; - $badge->usermodified = (int) $teacher->id; - $badge->expiredate = null; - $badge->expireperiod = null; - $badge->type = BADGE_TYPE_SITE; - $badge->courseid = null; - $badge->messagesubject = "Test message subject for badge"; - $badge->message = "Test message body for badge"; - $badge->attachment = 1; - $badge->notification = 0; - $badge->status = BADGE_STATUS_ACTIVE; - $badge->version = '1'; - $badge->language = 'en'; - $badge->imagecaption = 'Caption'; - - $badgeid = $DB->insert_record('badge', $badge, true); - $badge->id = $badgeid; - $sitebadge = new \badge($badgeid); - $sitebadge->issue($student1->id, true); - $siteissuedbadge = $DB->get_record('badge_issued', [ 'badgeid' => $badge->id ]); - - $badge->issuername = $sitebadge->issuername; - $badge->issuercontact = $sitebadge->issuercontact; - $badge->issuerurl = $sitebadge->issuerurl; - $badge->nextcron = $sitebadge->nextcron; - $badge->issuedid = (int) $siteissuedbadge->id; - $badge->uniquehash = $siteissuedbadge->uniquehash; - $badge->dateissued = (int) $siteissuedbadge->dateissued; - $badge->dateexpire = $siteissuedbadge->dateexpire; - $badge->visible = (int) $siteissuedbadge->visible; - $context = \context_system::instance(); - $badge->badgeurl = \moodle_url::make_webservice_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', - 'f3')->out(false); - $badge->status = BADGE_STATUS_ACTIVE_LOCKED; - - // Add an endorsement for the badge. - $endorsement = new \stdClass(); - $endorsement->badgeid = $badgeid; - $endorsement->issuername = 'Issuer name'; - $endorsement->issuerurl = 'http://endorsement-issuer-url.domain.co.nz'; - $endorsement->issueremail = 'endorsementissuer@example.com'; - $endorsement->claimid = 'http://claim-url.domain.co.nz'; - $endorsement->claimcomment = 'Claim comment'; - $endorsement->dateissued = $now; - $endorsement->id = $sitebadge->save_endorsement($endorsement); - $badge->endorsement = (array) $endorsement; - - // Add 2 alignments. - $alignment = new \stdClass(); - $alignment->badgeid = $badgeid; - $alignment->id = $sitebadge->save_alignment($alignment); - $badge->alignment[] = (array) $alignment; - - $alignment->id = $sitebadge->save_alignment($alignment); - $badge->alignment[] = (array) $alignment; - $badge->relatedbadges = []; - $usersitebadge[] = (array) $badge; - - // Now a course badge. - $badge->id = null; - $badge->name = "Test badge course"; - $badge->description = "Testing badges course"; - $badge->type = BADGE_TYPE_COURSE; - $badge->courseid = (int) $course->id; - - $badge->id = $DB->insert_record('badge', $badge, true); - $coursebadge = new \badge($badge->id ); - $coursebadge->issue($student2->id, true); - $courseissuedbadge = $DB->get_record('badge_issued', [ 'badgeid' => $badge->id ]); - - $badge->issuername = $coursebadge->issuername; - $badge->issuercontact = $coursebadge->issuercontact; - $badge->issuerurl = $coursebadge->issuerurl; - $badge->nextcron = $coursebadge->nextcron; - $badge->issuedid = (int) $courseissuedbadge->id; - $badge->uniquehash = $courseissuedbadge->uniquehash; - $badge->dateissued = (int) $courseissuedbadge->dateissued; - $badge->dateexpire = $courseissuedbadge->dateexpire; - $badge->visible = (int) $courseissuedbadge->visible; - $context = \context_course::instance($badge->courseid); - $badge->badgeurl = \moodle_url::make_webservice_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id , '/', - 'f3')->out(false); - - unset($badge->endorsement); - $badge->alignment = []; - $usercoursebadge[] = (array) $badge; - // Make the site badge a related badge. - $sitebadge->add_related_badges([$badge->id]); - $usersitebadge[0]['relatedbadges'][0] = [ - 'id' => (int) $coursebadge->id, - 'name' => $coursebadge->name - ]; - $usercoursebadge[0]['relatedbadges'][0] = [ - 'id' => (int) $sitebadge->id, - 'name' => $sitebadge->name - ]; - return [ - 'coursebadge' => $usercoursebadge, - 'sitebadge' => $usersitebadge, - 'student1' => $student1, - 'student2' => $student2, - 'student3' => $student3, - ]; - } + use external_helper; /** * Test get user badge by hash. @@ -176,22 +45,47 @@ final class get_user_badge_by_hash_test extends externallib_advanced_testcase { */ public function test_get_user_badge_by_hash(): void { $data = $this->prepare_test_data(); - $this->setUser($data['student1']); - // Site badge. - $result = get_user_badge_by_hash::execute($data['sitebadge'][0]['uniquehash']); + // Site badge fetched by recipient. + $this->setUser($data['student']); + $result = get_user_badge_by_hash::execute($data['sitebadge']['uniquehash']); $result = \core_external\external_api::clean_returnvalue(get_user_badge_by_hash::execute_returns(), $result); - $this->assertEquals($data['sitebadge'][0]['uniquehash'], $result['badge'][0]['uniquehash']); - $this->assertEquals($data['student1']->id, $result['badge'][0]['recipientid']); - $this->assertEquals(fullname($data['student1']), $result['badge'][0]['recipientfullname']); + $this->assert_issued_badge($data['sitebadge'], $result['badge'][0], true, false); $this->assertEmpty($result['warnings']); - // Course badge. - $result = get_user_badge_by_hash::execute($data['coursebadge'][0]['uniquehash']); + // Site badge fetched by user without "moodle/badges:configuredetails" capability. + $this->setGuestUser(); + $result = get_user_badge_by_hash::execute($data['sitebadge']['uniquehash']); $result = \core_external\external_api::clean_returnvalue(get_user_badge_by_hash::execute_returns(), $result); - $this->assertEquals($data['coursebadge'][0]['uniquehash'], $result['badge'][0]['uniquehash']); - $this->assertEquals($data['student2']->id, $result['badge'][0]['recipientid']); - $this->assertEquals(fullname($data['student2']), $result['badge'][0]['recipientfullname']); + $this->assert_issued_badge($data['sitebadge'], $result['badge'][0], false, false); + $this->assertEmpty($result['warnings']); + + // Site badge fetched by user with "moodle/badges:configuredetails" capability. + $this->setAdminUser(); + $result = get_user_badge_by_hash::execute($data['sitebadge']['uniquehash']); + $result = \core_external\external_api::clean_returnvalue(get_user_badge_by_hash::execute_returns(), $result); + $this->assert_issued_badge($data['sitebadge'], $result['badge'][0], false, true); + $this->assertEmpty($result['warnings']); + + // Course badge fetched by recipient. + $this->setUser($data['student']); + $result = get_user_badge_by_hash::execute($data['coursebadge']['uniquehash']); + $result = \core_external\external_api::clean_returnvalue(get_user_badge_by_hash::execute_returns(), $result); + $this->assert_issued_badge($data['coursebadge'], $result['badge'][0], true, false); + $this->assertEmpty($result['warnings']); + + // Course badge fetched by user without "moodle/badges:configuredetails" capability. + $this->setGuestUser(); + $result = get_user_badge_by_hash::execute($data['coursebadge']['uniquehash']); + $result = \core_external\external_api::clean_returnvalue(get_user_badge_by_hash::execute_returns(), $result); + $this->assert_issued_badge($data['coursebadge'], $result['badge'][0], false, false); + $this->assertEmpty($result['warnings']); + + // Course badge fetched by user with "moodle/badges:configuredetails" capability. + $this->setAdminUser(); + $result = get_user_badge_by_hash::execute($data['coursebadge']['uniquehash']); + $result = \core_external\external_api::clean_returnvalue(get_user_badge_by_hash::execute_returns(), $result); + $this->assert_issued_badge($data['coursebadge'], $result['badge'][0], false, true); $this->assertEmpty($result['warnings']); // Wrong hash. @@ -201,44 +95,4 @@ final class get_user_badge_by_hash_test extends externallib_advanced_testcase { $this->assertNotEmpty($result['warnings']); $this->assertEquals('badgeawardnotfound', $result['warnings'][0]['warningcode']); } - - /** - * Test get user badge by hash with restrictions. - * @covers ::execute - */ - public function test_get_user_badge_by_hash_with_restrictions(): void { - $data = $this->prepare_test_data(); - $this->setUser($data['student3']); - - // Site badge. - $result = get_user_badge_by_hash::execute($data['sitebadge'][0]['uniquehash']); - $result = \core_external\external_api::clean_returnvalue(get_user_badge_by_hash::execute_returns(), $result); - $this->assertNotEmpty($result['badge']); - $this->assertEmpty($result['warnings']); - - // Check that we don't have permissions for view the complete information for site badges. - $this->assertArrayNotHasKey('message', $result['badge'][0]); - - // Check that we have permissions to see all the data in alignments and related badges. - $this->assertEquals([ - $data['sitebadge'][0]['id'], - $data['sitebadge'][0]['id'], - ], array_column($result['badge'][0]['alignment'], 'badgeid')); - - $this->assertEquals([ - [ - 'id' => $data['coursebadge'][0]['id'], - 'name' => $data['coursebadge'][0]['name'], - ], - ], $result['badge'][0]['relatedbadges']); - - // Course badge. - $result = get_user_badge_by_hash::execute($data['coursebadge'][0]['uniquehash']); - $result = \core_external\external_api::clean_returnvalue(get_user_badge_by_hash::execute_returns(), $result); - $this->assertNotEmpty($result['badge']); - $this->assertEmpty($result['warnings']); - - // Check that we don't have permissions for view the complete information for course badges. - $this->assertArrayNotHasKey('message', $result['badge'][0]); - } }