e8a6956fd3a3419e7cb38b99dde7be49b886f7d0
[moodle.git] / badges / tests / badgeslib_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Unit tests for badges
19  *
20  * @package    core
21  * @subpackage badges
22  * @copyright  2013 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  * @author     Yuliya Bozhko <yuliya.bozhko@totaralms.com>
25  */
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
30 require_once($CFG->libdir . '/badgeslib.php');
31 require_once($CFG->dirroot . '/badges/lib.php');
33 use core_badges\helper;
35 class core_badges_badgeslib_testcase extends advanced_testcase {
36     protected $badgeid;
37     protected $course;
38     protected $user;
39     protected $module;
40     protected $coursebadge;
41     protected $assertion;
43     /** @var $assertion2 to define json format for Open badge version 2 */
44     protected $assertion2;
46     protected function setUp(): void {
47         global $DB, $CFG;
48         $this->resetAfterTest(true);
49         $CFG->enablecompletion = true;
50         $user = $this->getDataGenerator()->create_user();
51         $fordb = new stdClass();
52         $fordb->id = null;
53         $fordb->name = "Test badge with 'apostrophe' and other friends (<>&@#)";
54         $fordb->description = "Testing badges";
55         $fordb->timecreated = time();
56         $fordb->timemodified = time();
57         $fordb->usercreated = $user->id;
58         $fordb->usermodified = $user->id;
59         $fordb->issuername = "Test issuer";
60         $fordb->issuerurl = "http://issuer-url.domain.co.nz";
61         $fordb->issuercontact = "issuer@example.com";
62         $fordb->expiredate = null;
63         $fordb->expireperiod = null;
64         $fordb->type = BADGE_TYPE_SITE;
65         $fordb->version = 1;
66         $fordb->language = 'en';
67         $fordb->courseid = null;
68         $fordb->messagesubject = "Test message subject";
69         $fordb->message = "Test message body";
70         $fordb->attachment = 1;
71         $fordb->notification = 0;
72         $fordb->imageauthorname = "Image Author 1";
73         $fordb->imageauthoremail = "author@example.com";
74         $fordb->imageauthorurl = "http://author-url.example.com";
75         $fordb->imagecaption = "Test caption image";
76         $fordb->status = BADGE_STATUS_INACTIVE;
78         $this->badgeid = $DB->insert_record('badge', $fordb, true);
80         // Set the default Issuer (because OBv2 needs them).
81         set_config('badges_defaultissuername', $fordb->issuername);
82         set_config('badges_defaultissuercontact', $fordb->issuercontact);
84         // Create a course with activity and auto completion tracking.
85         $this->course = $this->getDataGenerator()->create_course(array('enablecompletion' => true));
86         $this->user = $this->getDataGenerator()->create_user();
87         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
88         $this->assertNotEmpty($studentrole);
90         // Get manual enrolment plugin and enrol user.
91         require_once($CFG->dirroot.'/enrol/manual/locallib.php');
92         $manplugin = enrol_get_plugin('manual');
93         $maninstance = $DB->get_record('enrol', array('courseid' => $this->course->id, 'enrol' => 'manual'), '*', MUST_EXIST);
94         $manplugin->enrol_user($maninstance, $this->user->id, $studentrole->id);
95         $this->assertEquals(1, $DB->count_records('user_enrolments'));
96         $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC);
97         $this->module = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto);
99         // Build badge and criteria.
100         $fordb->type = BADGE_TYPE_COURSE;
101         $fordb->courseid = $this->course->id;
102         $fordb->status = BADGE_STATUS_ACTIVE;
103         $this->coursebadge = $DB->insert_record('badge', $fordb, true);
105         // Insert Endorsement.
106         $endorsement = new stdClass();
107         $endorsement->badgeid = $this->coursebadge;
108         $endorsement->issuername = "Issuer 123";
109         $endorsement->issueremail = "issuer123@email.com";
110         $endorsement->issuerurl = "https://example.org/issuer-123";
111         $endorsement->dateissued = 1524567747;
112         $endorsement->claimid = "https://example.org/robotics-badge.json";
113         $endorsement->claimcomment = "Test endorser comment";
114         $DB->insert_record('badge_endorsement', $endorsement, true);
116         // Insert related badges.
117         $badge = new badge($this->coursebadge);
118         $clonedid = $badge->make_clone();
119         $badgeclone = new badge($clonedid);
120         $badgeclone->status = BADGE_STATUS_ACTIVE;
121         $badgeclone->save();
123         $relatebadge = new stdClass();
124         $relatebadge->badgeid = $this->coursebadge;
125         $relatebadge->relatedbadgeid = $clonedid;
126         $relatebadge->relatedid = $DB->insert_record('badge_related', $relatebadge, true);
128         // Insert a aligment.
129         $alignment = new stdClass();
130         $alignment->badgeid = $this->coursebadge;
131         $alignment->targetname = 'CCSS.ELA-Literacy.RST.11-12.3';
132         $alignment->targeturl = 'http://www.corestandards.org/ELA-Literacy/RST/11-12/3';
133         $alignment->targetdescription = 'Test target description';
134         $alignment->targetframework = 'CCSS.RST.11-12.3';
135         $alignment->targetcode = 'CCSS.RST.11-12.3';
136         $DB->insert_record('badge_alignment', $alignment, true);
138         $this->assertion = new stdClass();
139         $this->assertion->badge = '{"uid":"%s","recipient":{"identity":"%s","type":"email","hashed":true,"salt":"%s"},"badge":"%s","verify":{"type":"hosted","url":"%s"},"issuedOn":"%d","evidence":"%s"}';
140         $this->assertion->class = '{"name":"%s","description":"%s","image":"%s","criteria":"%s","issuer":"%s"}';
141         $this->assertion->issuer = '{"name":"%s","url":"%s","email":"%s"}';
142         // Format JSON-LD for Openbadge specification version 2.0.
143         $this->assertion2 = new stdClass();
144         $this->assertion2->badge = '{"recipient":{"identity":"%s","type":"email","hashed":true,"salt":"%s"},' .
145             '"badge":{"name":"%s","description":"%s","image":"%s",' .
146             '"criteria":{"id":"%s","narrative":"%s"},"issuer":{"name":"%s","url":"%s","email":"%s",' .
147             '"@context":"https:\/\/w3id.org\/openbadges\/v2","id":"%s","type":"Issuer"},' .
148             '"@context":"https:\/\/w3id.org\/openbadges\/v2","id":"%s","type":"BadgeClass","version":"%s",' .
149             '"@language":"en","related":[{"id":"%s","version":"%s","@language":"%s"}],"endorsement":"%s",' .
150             '"alignments":[{"targetName":"%s","targetUrl":"%s","targetDescription":"%s","targetFramework":"%s",' .
151             '"targetCode":"%s"}]},"verify":{"type":"hosted","url":"%s"},"issuedOn":"%s","evidence":"%s",' .
152             '"@context":"https:\/\/w3id.org\/openbadges\/v2","type":"Assertion","id":"%s"}';
154         $this->assertion2->class = '{"name":"%s","description":"%s","image":"%s",' .
155             '"criteria":{"id":"%s","narrative":"%s"},"issuer":{"name":"%s","url":"%s","email":"%s",' .
156             '"@context":"https:\/\/w3id.org\/openbadges\/v2","id":"%s","type":"Issuer"},' .
157             '"@context":"https:\/\/w3id.org\/openbadges\/v2","id":"%s","type":"BadgeClass","version":"%s",' .
158             '"@language":"%s","related":[{"id":"%s","version":"%s","@language":"%s"}],"endorsement":"%s",' .
159             '"alignments":[{"targetName":"%s","targetUrl":"%s","targetDescription":"%s","targetFramework":"%s",' .
160             '"targetCode":"%s"}]}';
161         $this->assertion2->issuer = '{"name":"%s","url":"%s","email":"%s",' .
162             '"@context":"https:\/\/w3id.org\/openbadges\/v2","id":"%s","type":"Issuer"}';
163     }
165     public function test_create_badge() {
166         $badge = new badge($this->badgeid);
168         $this->assertInstanceOf('badge', $badge);
169         $this->assertEquals($this->badgeid, $badge->id);
170     }
172     public function test_clone_badge() {
173         $badge = new badge($this->badgeid);
174         $newid = $badge->make_clone();
175         $clonedbadge = new badge($newid);
177         $this->assertEquals($badge->description, $clonedbadge->description);
178         $this->assertEquals($badge->issuercontact, $clonedbadge->issuercontact);
179         $this->assertEquals($badge->issuername, $clonedbadge->issuername);
180         $this->assertEquals($badge->issuercontact, $clonedbadge->issuercontact);
181         $this->assertEquals($badge->issuerurl, $clonedbadge->issuerurl);
182         $this->assertEquals($badge->expiredate, $clonedbadge->expiredate);
183         $this->assertEquals($badge->expireperiod, $clonedbadge->expireperiod);
184         $this->assertEquals($badge->type, $clonedbadge->type);
185         $this->assertEquals($badge->courseid, $clonedbadge->courseid);
186         $this->assertEquals($badge->message, $clonedbadge->message);
187         $this->assertEquals($badge->messagesubject, $clonedbadge->messagesubject);
188         $this->assertEquals($badge->attachment, $clonedbadge->attachment);
189         $this->assertEquals($badge->notification, $clonedbadge->notification);
190         $this->assertEquals($badge->version, $clonedbadge->version);
191         $this->assertEquals($badge->language, $clonedbadge->language);
192         $this->assertEquals($badge->imagecaption, $clonedbadge->imagecaption);
193         $this->assertEquals($badge->imageauthorname, $clonedbadge->imageauthorname);
194         $this->assertEquals($badge->imageauthoremail, $clonedbadge->imageauthoremail);
195         $this->assertEquals($badge->imageauthorurl, $clonedbadge->imageauthorurl);
196     }
198     public function test_badge_status() {
199         $badge = new badge($this->badgeid);
200         $old_status = $badge->status;
201         $badge->set_status(BADGE_STATUS_ACTIVE);
202         $this->assertNotEquals($old_status, $badge->status);
203         $this->assertEquals(BADGE_STATUS_ACTIVE, $badge->status);
204     }
206     public function test_delete_badge() {
207         $badge = new badge($this->badgeid);
208         $badge->delete();
209         // We don't actually delete badges. We archive them.
210         $this->assertEquals(BADGE_STATUS_ARCHIVED, $badge->status);
211     }
213     /**
214      * Really delete the badge.
215      */
216     public function test_delete_badge_for_real() {
217         global $DB;
219         $badge = new badge($this->badgeid);
221         $newid1 = $badge->make_clone();
222         $newid2 = $badge->make_clone();
223         $newid3 = $badge->make_clone();
225         // Insert related badges to badge 1.
226         $badge->add_related_badges([$newid1, $newid2, $newid3]);
228         // Another badge.
229         $badge2 = new badge($newid2);
230         // Make badge 1 related for badge 2.
231         $badge2->add_related_badges([$this->badgeid]);
233         // Confirm that the records about this badge about its relations have been removed as well.
234         $relatedsql = 'badgeid = :badgeid OR relatedbadgeid = :relatedbadgeid';
235         $relatedparams = array(
236             'badgeid' => $this->badgeid,
237             'relatedbadgeid' => $this->badgeid
238         );
239         // Badge 1 has 4 related records. 3 where it's the badgeid, 1 where it's the relatedbadgeid.
240         $this->assertEquals(4, $DB->count_records_select('badge_related', $relatedsql, $relatedparams));
242         // Delete the badge for real.
243         $badge->delete(false);
245         // Confirm that the badge itself has been removed.
246         $this->assertFalse($DB->record_exists('badge', ['id' => $this->badgeid]));
248         // Confirm that the records about this badge about its relations have been removed as well.
249         $this->assertFalse($DB->record_exists_select('badge_related', $relatedsql, $relatedparams));
250     }
252     public function test_create_badge_criteria() {
253         $badge = new badge($this->badgeid);
254         $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
255         $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL));
257         $this->assertCount(1, $badge->get_criteria());
259         $criteria_profile = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE, 'badgeid' => $badge->id));
260         $params = array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address');
261         $criteria_profile->save($params);
263         $this->assertCount(2, $badge->get_criteria());
264     }
266     public function test_add_badge_criteria_description() {
267         $criteriaoverall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $this->badgeid));
268         $criteriaoverall->save(array(
269                 'agg' => BADGE_CRITERIA_AGGREGATION_ALL,
270                 'description' => 'Overall description',
271                 'descriptionformat' => FORMAT_HTML
272         ));
274         $criteriaprofile = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE, 'badgeid' => $this->badgeid));
275         $params = array(
276                 'agg' => BADGE_CRITERIA_AGGREGATION_ALL,
277                 'field_address' => 'address',
278                 'description' => 'Description',
279                 'descriptionformat' => FORMAT_HTML
280         );
281         $criteriaprofile->save($params);
283         $badge = new badge($this->badgeid);
284         $this->assertEquals('Overall description', $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->description);
285         $this->assertEquals('Description', $badge->criteria[BADGE_CRITERIA_TYPE_PROFILE]->description);
286     }
288     public function test_delete_badge_criteria() {
289         $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $this->badgeid));
290         $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL));
291         $badge = new badge($this->badgeid);
293         $this->assertInstanceOf('award_criteria_overall', $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]);
295         $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->delete();
296         $this->assertEmpty($badge->get_criteria());
297     }
299     public function test_badge_awards() {
300         global $DB;
301         $this->preventResetByRollback(); // Messaging is not compatible with transactions.
302         $badge = new badge($this->badgeid);
303         $user1 = $this->getDataGenerator()->create_user();
305         $sink = $this->redirectMessages();
307         $DB->set_field_select('message_processors', 'enabled', 0, "name <> 'email'");
308         set_user_preference('message_provider_moodle_badgerecipientnotice_loggedoff', 'email', $user1);
310         $badge->issue($user1->id, false);
311         $this->assertDebuggingCalled(); // Expect debugging while baking a badge via phpunit.
312         $this->assertTrue($badge->is_issued($user1->id));
314         $messages = $sink->get_messages();
315         $sink->close();
316         $this->assertCount(1, $messages);
317         $message = array_pop($messages);
318         // Check we have the expected data.
319         $customdata = json_decode($message->customdata);
320         $this->assertObjectHasAttribute('notificationiconurl', $customdata);
321         $this->assertObjectHasAttribute('hash', $customdata);
323         $user2 = $this->getDataGenerator()->create_user();
324         $badge->issue($user2->id, true);
325         $this->assertTrue($badge->is_issued($user2->id));
327         $this->assertCount(2, $badge->get_awards());
328     }
330     /**
331      * Test the {@link badges_get_user_badges()} function in lib/badgeslib.php
332      */
333     public function test_badges_get_user_badges() {
334         global $DB;
336         // Messaging is not compatible with transactions.
337         $this->preventResetByRollback();
339         $badges = array();
340         $user1 = $this->getDataGenerator()->create_user();
341         $user2 = $this->getDataGenerator()->create_user();
343         // Record the current time, we need to be precise about a couple of things.
344         $now = time();
345         // Create 11 badges with which to test.
346         for ($i = 1; $i <= 11; $i++) {
347             // Mock up a badge.
348             $badge = new stdClass();
349             $badge->id = null;
350             $badge->name = "Test badge $i";
351             $badge->description = "Testing badges $i";
352             $badge->timecreated = $now - 12;
353             $badge->timemodified = $now - 12;
354             $badge->usercreated = $user1->id;
355             $badge->usermodified = $user1->id;
356             $badge->issuername = "Test issuer";
357             $badge->issuerurl = "http://issuer-url.domain.co.nz";
358             $badge->issuercontact = "issuer@example.com";
359             $badge->expiredate = null;
360             $badge->expireperiod = null;
361             $badge->type = BADGE_TYPE_SITE;
362             $badge->courseid = null;
363             $badge->messagesubject = "Test message subject for badge $i";
364             $badge->message = "Test message body for badge $i";
365             $badge->attachment = 1;
366             $badge->notification = 0;
367             $badge->status = BADGE_STATUS_INACTIVE;
368             $badge->version = "Version $i";
369             $badge->language = "en";
370             $badge->imagecaption = "Image caption $i";
371             $badge->imageauthorname = "Image author's name $i";
372             $badge->imageauthoremail = "author$i@example.com";
373             $badge->imageauthorname = "Image author's name $i";
375             $badgeid = $DB->insert_record('badge', $badge, true);
376             $badges[$badgeid] = new badge($badgeid);
377             $badges[$badgeid]->issue($user2->id, true);
378             // Check it all actually worked.
379             $this->assertCount(1, $badges[$badgeid]->get_awards());
381             // Hack the database to adjust the time each badge was issued.
382             // The alternative to this is sleep which is a no-no in unit tests.
383             $DB->set_field('badge_issued', 'dateissued', $now - 11 + $i, array('userid' => $user2->id, 'badgeid' => $badgeid));
384         }
386         // Make sure the first user has no badges.
387         $result = badges_get_user_badges($user1->id);
388         $this->assertIsArray($result);
389         $this->assertCount(0, $result);
391         // Check that the second user has the expected 11 badges.
392         $result = badges_get_user_badges($user2->id);
393         $this->assertCount(11, $result);
395         // Test pagination.
396         // Ordering is by time issued desc, so things will come out with the last awarded badge first.
397         $result = badges_get_user_badges($user2->id, 0, 0, 4);
398         $this->assertCount(4, $result);
399         $lastbadgeissued = reset($result);
400         $this->assertSame('Test badge 11', $lastbadgeissued->name);
401         // Page 2. Expecting 4 results again.
402         $result = badges_get_user_badges($user2->id, 0, 1, 4);
403         $this->assertCount(4, $result);
404         $lastbadgeissued = reset($result);
405         $this->assertSame('Test badge 7', $lastbadgeissued->name);
406         // Page 3. Expecting just three results here.
407         $result = badges_get_user_badges($user2->id, 0, 2, 4);
408         $this->assertCount(3, $result);
409         $lastbadgeissued = reset($result);
410         $this->assertSame('Test badge 3', $lastbadgeissued->name);
411         // Page 4.... there is no page 4.
412         $result = badges_get_user_badges($user2->id, 0, 3, 4);
413         $this->assertCount(0, $result);
415         // Test search.
416         $result = badges_get_user_badges($user2->id, 0, 0, 0, 'badge 1');
417         $this->assertCount(3, $result);
418         $lastbadgeissued = reset($result);
419         $this->assertSame('Test badge 11', $lastbadgeissued->name);
420         // The term Totara doesn't appear anywhere in the badges.
421         $result = badges_get_user_badges($user2->id, 0, 0, 0, 'Totara');
422         $this->assertCount(0, $result);
424         // Issue a user with a course badge and verify its returned based on if
425         // coursebadges are enabled or disabled.
426         $sitebadgeid = key($badges);
427         $badges[$sitebadgeid]->issue($this->user->id, true);
429         $badge = new stdClass();
430         $badge->id = null;
431         $badge->name = "Test course badge";
432         $badge->description = "Testing course badge";
433         $badge->timecreated = $now;
434         $badge->timemodified = $now;
435         $badge->usercreated = $user1->id;
436         $badge->usermodified = $user1->id;
437         $badge->issuername = "Test issuer";
438         $badge->issuerurl = "http://issuer-url.domain.co.nz";
439         $badge->issuercontact = "issuer@example.com";
440         $badge->expiredate = null;
441         $badge->expireperiod = null;
442         $badge->type = BADGE_TYPE_COURSE;
443         $badge->courseid = $this->course->id;
444         $badge->messagesubject = "Test message subject for course badge";
445         $badge->message = "Test message body for course badge";
446         $badge->attachment = 1;
447         $badge->notification = 0;
448         $badge->status = BADGE_STATUS_ACTIVE;
449         $badge->version = "Version $i";
450         $badge->language = "en";
451         $badge->imagecaption = "Image caption";
452         $badge->imageauthorname = "Image author's name";
453         $badge->imageauthoremail = "author@example.com";
454         $badge->imageauthorname = "Image author's name";
456         $badgeid = $DB->insert_record('badge', $badge, true);
457         $badges[$badgeid] = new badge($badgeid);
458         $badges[$badgeid]->issue($this->user->id, true);
460         // With coursebadges off, we should only get the site badge.
461         set_config('badges_allowcoursebadges', false);
462         $result = badges_get_user_badges($this->user->id);
463         $this->assertCount(1, $result);
465         // With it on, we should get both.
466         set_config('badges_allowcoursebadges', true);
467         $result = badges_get_user_badges($this->user->id);
468         $this->assertCount(2, $result);
470     }
472     public function data_for_message_from_template() {
473         return array(
474             array(
475                 'This is a message with no variables',
476                 array(), // no params
477                 'This is a message with no variables'
478             ),
479             array(
480                 'This is a message with %amissing% variables',
481                 array(), // no params
482                 'This is a message with %amissing% variables'
483             ),
484             array(
485                 'This is a message with %one% variable',
486                 array('one' => 'a single'),
487                 'This is a message with a single variable'
488             ),
489             array(
490                 'This is a message with %one% %two% %three% variables',
491                 array('one' => 'more', 'two' => 'than', 'three' => 'one'),
492                 'This is a message with more than one variables'
493             ),
494             array(
495                 'This is a message with %three% %two% %one%',
496                 array('one' => 'variables', 'two' => 'ordered', 'three' => 'randomly'),
497                 'This is a message with randomly ordered variables'
498             ),
499             array(
500                 'This is a message with %repeated% %one% %repeated% of variables',
501                 array('one' => 'and', 'repeated' => 'lots'),
502                 'This is a message with lots and lots of variables'
503             ),
504         );
505     }
507     /**
508      * @dataProvider data_for_message_from_template
509      */
510     public function test_badge_message_from_template($message, $params, $result) {
511         $this->assertEquals(badge_message_from_template($message, $params), $result);
512     }
514     /**
515      * Test badges observer when course module completion event id fired.
516      */
517     public function test_badges_observer_course_module_criteria_review() {
518         $this->preventResetByRollback(); // Messaging is not compatible with transactions.
519         $badge = new badge($this->coursebadge);
520         $this->assertFalse($badge->is_issued($this->user->id));
522         $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
523         $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
524         $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_ACTIVITY, 'badgeid' => $badge->id));
525         $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'module_'.$this->module->cmid => $this->module->cmid));
527         // Assert the badge will not be issued to the user as is.
528         $badge = new badge($this->coursebadge);
529         $badge->review_all_criteria();
530         $this->assertFalse($badge->is_issued($this->user->id));
532         // Set completion for forum activity.
533         $c = new completion_info($this->course);
534         $activities = $c->get_activities();
535         $this->assertEquals(1, count($activities));
536         $this->assertTrue(isset($activities[$this->module->cmid]));
537         $this->assertEquals($activities[$this->module->cmid]->name, $this->module->name);
539         $current = $c->get_data($activities[$this->module->cmid], false, $this->user->id);
540         $current->completionstate = COMPLETION_COMPLETE;
541         $current->timemodified = time();
542         $sink = $this->redirectEmails();
543         $c->internal_set_data($activities[$this->module->cmid], $current);
544         $this->assertCount(1, $sink->get_messages());
545         $sink->close();
547         // Check if badge is awarded.
548         $this->assertDebuggingCalled('Error baking badge image!');
549         $this->assertTrue($badge->is_issued($this->user->id));
550     }
552     /**
553      * Test badges observer when course_completed event is fired.
554      */
555     public function test_badges_observer_course_criteria_review() {
556         $this->preventResetByRollback(); // Messaging is not compatible with transactions.
557         $badge = new badge($this->coursebadge);
558         $this->assertFalse($badge->is_issued($this->user->id));
560         $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
561         $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
562         $criteria_overall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_COURSE, 'badgeid' => $badge->id));
563         $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'course_'.$this->course->id => $this->course->id));
565         $ccompletion = new completion_completion(array('course' => $this->course->id, 'userid' => $this->user->id));
567         // Assert the badge will not be issued to the user as is.
568         $badge = new badge($this->coursebadge);
569         $badge->review_all_criteria();
570         $this->assertFalse($badge->is_issued($this->user->id));
572         // Mark course as complete.
573         $sink = $this->redirectMessages();
574         $ccompletion->mark_complete();
575         // Two messages are generated: One for the course completed and the other one for the badge awarded.
576         $messages = $sink->get_messages();
577         $this->assertCount(2, $messages);
578         $this->assertEquals('badgerecipientnotice', $messages[0]->eventtype);
579         $this->assertEquals('coursecompleted', $messages[1]->eventtype);
580         $sink->close();
582         // Check if badge is awarded.
583         $this->assertDebuggingCalled('Error baking badge image!');
584         $this->assertTrue($badge->is_issued($this->user->id));
585     }
587     /**
588      * Test badges observer when user_updated event is fired.
589      */
590     public function test_badges_observer_profile_criteria_review() {
591         global $CFG, $DB;
592         require_once($CFG->dirroot.'/user/profile/lib.php');
594         // Add a custom field of textarea type.
595         $customprofileid = $DB->insert_record('user_info_field', array(
596             'shortname' => 'newfield', 'name' => 'Description of new field', 'categoryid' => 1,
597             'datatype' => 'textarea'));
599         $this->preventResetByRollback(); // Messaging is not compatible with transactions.
600         $badge = new badge($this->coursebadge);
602         $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
603         $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
604         $criteria_overall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE, 'badgeid' => $badge->id));
605         $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address', 'field_aim' => 'aim',
606             'field_' . $customprofileid => $customprofileid));
608         // Assert the badge will not be issued to the user as is.
609         $badge = new badge($this->coursebadge);
610         $badge->review_all_criteria();
611         $this->assertFalse($badge->is_issued($this->user->id));
613         // Set the required fields and make sure the badge got issued.
614         $this->user->address = 'Test address';
615         $this->user->aim = '999999999';
616         $sink = $this->redirectEmails();
617         profile_save_data((object)array('id' => $this->user->id, 'profile_field_newfield' => 'X'));
618         user_update_user($this->user, false);
619         $this->assertCount(1, $sink->get_messages());
620         $sink->close();
621         // Check if badge is awarded.
622         $this->assertDebuggingCalled('Error baking badge image!');
623         $this->assertTrue($badge->is_issued($this->user->id));
624     }
626     /**
627      * Test badges observer when cohort_member_added event is fired.
628      */
629     public function test_badges_observer_cohort_criteria_review() {
630         global $CFG;
632         require_once("$CFG->dirroot/cohort/lib.php");
634         $cohort = $this->getDataGenerator()->create_cohort();
636         $this->preventResetByRollback(); // Messaging is not compatible with transactions.
637         $badge = new badge($this->badgeid);
638         $this->assertFalse($badge->is_issued($this->user->id));
640         // Set up the badge criteria.
641         $criteriaoverall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
642         $criteriaoverall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
643         $criteriaoverall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_COHORT, 'badgeid' => $badge->id));
644         $criteriaoverall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'cohort_cohorts' => array('0' => $cohort->id)));
646         // Make the badge active.
647         $badge->set_status(BADGE_STATUS_ACTIVE);
649         // Add the user to the cohort.
650         cohort_add_member($cohort->id, $this->user->id);
652         // Verify that the badge was awarded.
653         $this->assertDebuggingCalled();
654         $this->assertTrue($badge->is_issued($this->user->id));
656     }
658     /**
659      * Test badges assertion generated when a badge is issued.
660      */
661     public function test_badges_assertion() {
662         $this->preventResetByRollback(); // Messaging is not compatible with transactions.
663         $badge = new badge($this->coursebadge);
664         $this->assertFalse($badge->is_issued($this->user->id));
666         $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
667         $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
668         $criteria_overall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE, 'badgeid' => $badge->id));
669         $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address'));
671         $this->user->address = 'Test address';
672         $sink = $this->redirectEmails();
673         user_update_user($this->user, false);
674         $this->assertCount(1, $sink->get_messages());
675         $sink->close();
676         // Check if badge is awarded.
677         $this->assertDebuggingCalled('Error baking badge image!');
678         $awards = $badge->get_awards();
679         $this->assertCount(1, $awards);
681         // Get assertion.
682         $award = reset($awards);
683         $assertion = new core_badges_assertion($award->uniquehash, OPEN_BADGES_V1);
684         $testassertion = $this->assertion;
686         // Make sure JSON strings have the same structure.
687         $this->assertStringMatchesFormat($testassertion->badge, json_encode($assertion->get_badge_assertion()));
688         $this->assertStringMatchesFormat($testassertion->class, json_encode($assertion->get_badge_class()));
689         $this->assertStringMatchesFormat($testassertion->issuer, json_encode($assertion->get_issuer()));
691         // Test Openbadge specification version 2.
692         // Get assertion version 2.
693         $award = reset($awards);
694         $assertion2 = new core_badges_assertion($award->uniquehash, OPEN_BADGES_V2);
695         $testassertion2 = $this->assertion2;
697         // Make sure JSON strings have the same structure.
698         $this->assertStringMatchesFormat($testassertion2->badge, json_encode($assertion2->get_badge_assertion()));
699         $this->assertStringMatchesFormat($testassertion2->class, json_encode($assertion2->get_badge_class()));
700         $this->assertStringMatchesFormat($testassertion2->issuer, json_encode($assertion2->get_issuer()));
701     }
703     /**
704      * Tests the core_badges_myprofile_navigation() function.
705      */
706     public function test_core_badges_myprofile_navigation() {
707         // Set up the test.
708         $tree = new \core_user\output\myprofile\tree();
709         $this->setAdminUser();
710         $badge = new badge($this->badgeid);
711         $badge->issue($this->user->id, true);
712         $iscurrentuser = true;
713         $course = null;
715         // Enable badges.
716         set_config('enablebadges', true);
718         // Check the node tree is correct.
719         core_badges_myprofile_navigation($tree, $this->user, $iscurrentuser, $course);
720         $reflector = new ReflectionObject($tree);
721         $nodes = $reflector->getProperty('nodes');
722         $nodes->setAccessible(true);
723         $this->assertArrayHasKey('localbadges', $nodes->getValue($tree));
724     }
726     /**
727      * Tests the core_badges_myprofile_navigation() function with badges disabled..
728      */
729     public function test_core_badges_myprofile_navigation_badges_disabled() {
730         // Set up the test.
731         $tree = new \core_user\output\myprofile\tree();
732         $this->setAdminUser();
733         $badge = new badge($this->badgeid);
734         $badge->issue($this->user->id, true);
735         $iscurrentuser = false;
736         $course = null;
738         // Disable badges.
739         set_config('enablebadges', false);
741         // Check the node tree is correct.
742         core_badges_myprofile_navigation($tree, $this->user, $iscurrentuser, $course);
743         $reflector = new ReflectionObject($tree);
744         $nodes = $reflector->getProperty('nodes');
745         $nodes->setAccessible(true);
746         $this->assertArrayNotHasKey('localbadges', $nodes->getValue($tree));
747     }
749     /**
750      * Tests the core_badges_myprofile_navigation() function with a course badge.
751      */
752     public function test_core_badges_myprofile_navigation_with_course_badge() {
753         // Set up the test.
754         $tree = new \core_user\output\myprofile\tree();
755         $this->setAdminUser();
756         $badge = new badge($this->coursebadge);
757         $badge->issue($this->user->id, true);
758         $iscurrentuser = false;
760         // Check the node tree is correct.
761         core_badges_myprofile_navigation($tree, $this->user, $iscurrentuser, $this->course);
762         $reflector = new ReflectionObject($tree);
763         $nodes = $reflector->getProperty('nodes');
764         $nodes->setAccessible(true);
765         $this->assertArrayHasKey('localbadges', $nodes->getValue($tree));
766     }
768     /**
769      * Test insert and update endorsement with a site badge.
770      */
771     public function test_badge_endorsement() {
772         $badge = new badge($this->badgeid);
774         // Insert Endorsement.
775         $endorsement = new stdClass();
776         $endorsement->badgeid = $this->badgeid;
777         $endorsement->issuername = "Issuer 123";
778         $endorsement->issueremail = "issuer123@email.com";
779         $endorsement->issuerurl = "https://example.org/issuer-123";
780         $endorsement->dateissued = 1524567747;
781         $endorsement->claimid = "https://example.org/robotics-badge.json";
782         $endorsement->claimcomment = "Test endorser comment";
784         $badge->save_endorsement($endorsement);
785         $endorsement1 = $badge->get_endorsement();
786         $this->assertEquals($endorsement->badgeid, $endorsement1->badgeid);
787         $this->assertEquals($endorsement->issuername, $endorsement1->issuername);
788         $this->assertEquals($endorsement->issueremail, $endorsement1->issueremail);
789         $this->assertEquals($endorsement->issuerurl, $endorsement1->issuerurl);
790         $this->assertEquals($endorsement->dateissued, $endorsement1->dateissued);
791         $this->assertEquals($endorsement->claimid, $endorsement1->claimid);
792         $this->assertEquals($endorsement->claimcomment, $endorsement1->claimcomment);
794         // Update Endorsement.
795         $endorsement1->issuername = "Issuer update";
796         $badge->save_endorsement($endorsement1);
797         $endorsement2 = $badge->get_endorsement();
798         $this->assertEquals($endorsement1->id, $endorsement2->id);
799         $this->assertEquals($endorsement1->issuername, $endorsement2->issuername);
800     }
802     /**
803      * Test insert and delete related badge with a site badge.
804      */
805     public function test_badge_related() {
806         $badge = new badge($this->badgeid);
807         $newid1 = $badge->make_clone();
808         $newid2 = $badge->make_clone();
809         $newid3 = $badge->make_clone();
811         // Insert an related badge.
812         $badge->add_related_badges([$newid1, $newid2, $newid3]);
813         $this->assertCount(3, $badge->get_related_badges());
815         // Only get related is active.
816         $clonedbage1 = new badge($newid1);
817         $clonedbage1->status = BADGE_STATUS_ACTIVE;
818         $clonedbage1->save();
819         $this->assertCount(1, $badge->get_related_badges(true));
821         // Delete an related badge.
822         $badge->delete_related_badge($newid2);
823         $this->assertCount(2, $badge->get_related_badges());
824     }
826     /**
827      * Test insert, update, delete alignment with a site badge.
828      */
829     public function test_alignments() {
830         $badge = new badge($this->badgeid);
832         // Insert a alignment.
833         $alignment1 = new stdClass();
834         $alignment1->badgeid = $this->badgeid;
835         $alignment1->targetname = 'CCSS.ELA-Literacy.RST.11-12.3';
836         $alignment1->targeturl = 'http://www.corestandards.org/ELA-Literacy/RST/11-12/3';
837         $alignment1->targetdescription = 'Test target description';
838         $alignment1->targetframework = 'CCSS.RST.11-12.3';
839         $alignment1->targetcode = 'CCSS.RST.11-12.3';
840         $alignment2 = clone $alignment1;
841         $newid1 = $badge->save_alignment($alignment1);
842         $newid2 = $badge->save_alignment($alignment2);
843         $alignments1 = $badge->get_alignments();
844         $this->assertCount(2, $alignments1);
846         $this->assertEquals($alignment1->badgeid, $alignments1[$newid1]->badgeid);
847         $this->assertEquals($alignment1->targetname, $alignments1[$newid1]->targetname);
848         $this->assertEquals($alignment1->targeturl, $alignments1[$newid1]->targeturl);
849         $this->assertEquals($alignment1->targetdescription, $alignments1[$newid1]->targetdescription);
850         $this->assertEquals($alignment1->targetframework, $alignments1[$newid1]->targetframework);
851         $this->assertEquals($alignment1->targetcode, $alignments1[$newid1]->targetcode);
853         // Update aligment.
854         $alignments1[$newid1]->targetname = 'CCSS.ELA-Literacy.RST.11-12.3 update';
855         $badge->save_alignment($alignments1[$newid1], $alignments1[$newid1]->id);
856         $alignments2 = $badge->get_alignments();
857         $this->assertEquals($alignments1[$newid1]->id, $alignments2[$newid1]->id);
858         $this->assertEquals($alignments1[$newid1]->targetname, $alignments2[$newid1]->targetname);
860         // Delete alignment.
861         $badge->delete_alignment($alignments1[$newid2]->id);
862         $this->assertCount(1, $badge->get_alignments());
863     }
865     /**
866      * Test badges_delete_site_backpack().
867      *
868      */
869     public function test_badges_delete_site_backpack(): void {
870         global $DB;
872         $this->setAdminUser();
874         // Create one backpack.
875         $total = $DB->count_records('badge_external_backpack');
876         $this->assertEquals(1, $total);
878         $data = new \stdClass();
879         $data->apiversion = OPEN_BADGES_V2P1;
880         $data->backpackapiurl = 'https://dc.imsglobal.org/obchost/ims/ob/v2p1';
881         $data->backpackweburl = 'https://dc.imsglobal.org';
882         badges_create_site_backpack($data);
883         $backpack = $DB->get_record('badge_external_backpack', ['backpackweburl' => $data->backpackweburl]);
884         $user1 = $this->getDataGenerator()->create_user();
885         $user2 = $this->getDataGenerator()->create_user();
886         // User1 is connected to the backpack to be removed and has 2 collections.
887         $backpackuser1 = helper::create_fake_backpack(['userid' => $user1->id, 'externalbackpackid' => $backpack->id]);
888         helper::create_fake_backpack_collection(['backpackid' => $backpackuser1->id]);
889         helper::create_fake_backpack_collection(['backpackid' => $backpackuser1->id]);
890         // User2 is connected to a different backpack and has 1 collection.
891         $backpackuser2 = helper::create_fake_backpack(['userid' => $user2->id]);
892         helper::create_fake_backpack_collection(['backpackid' => $backpackuser2->id]);
894         $total = $DB->count_records('badge_external_backpack');
895         $this->assertEquals(2, $total);
896         $total = $DB->count_records('badge_backpack');
897         $this->assertEquals(2, $total);
898         $total = $DB->count_records('badge_external');
899         $this->assertEquals(3, $total);
901         // Remove the backpack created previously.
902         $result = badges_delete_site_backpack($backpack->id);
903         $this->assertTrue($result);
905         $total = $DB->count_records('badge_external_backpack');
906         $this->assertEquals(1, $total);
908         $total = $DB->count_records('badge_backpack');
909         $this->assertEquals(1, $total);
911         $total = $DB->count_records('badge_external');
912         $this->assertEquals(1, $total);
914         // Try to remove an non-existent backpack.
915         $result = badges_delete_site_backpack($backpack->id);
916         $this->assertFalse($result);
917     }
919     /**
920      * Test to validate badges_save_backpack_credentials.
921      *
922      * @dataProvider save_backpack_credentials_provider
923      * @param  bool $addbackpack True if backpack data has to be created; false otherwise (empty data will be used then).
924      * @param  string|null  $mail  Backpack mail address.
925      * @param  string|null  $password  Backpack password.
926      */
927     public function test_save_backpack_credentials(bool $addbackpack = true, ?string $mail = null, ?string $password = null) {
928         global $DB;
930         $this->resetAfterTest();
931         $this->setAdminUser();
933         $data = [];
934         if ($addbackpack) {
935             $data = new \stdClass();
936             $data->apiversion = OPEN_BADGES_V2P1;
937             $data->backpackapiurl = 'https://dc.imsglobal.org/obchost/ims/ob/v2p1';
938             $data->backpackweburl = 'https://dc.imsglobal.org';
939             badges_create_site_backpack($data);
940             $backpack = $DB->get_record('badge_external_backpack', ['backpackweburl' => $data->backpackweburl]);
941             $user = $this->getDataGenerator()->create_user();
943             $data = [
944                 'externalbackpackid' => $backpack->id,
945                 'userid' => $user->id,
946             ];
948             if (!empty($mail)) {
949                 $data['backpackemail'] = $mail;
950             }
951             if (!empty($password)) {
952                 $data['password'] = $password;
953             }
954         }
956         $return = badges_save_backpack_credentials((object) $data);
957         if (array_key_exists('userid', $data)) {
958             $record = $DB->get_record('badge_backpack', ['userid' => $user->id]);
959         } else {
960             $record = $DB->get_records('badge_backpack');
961         }
963         if (!empty($mail) && !empty($password)) {
964             // The backpack credentials are created if the given information is right.
965             $this->assertNotEmpty($record);
966             $this->assertEquals($data['externalbackpackid'], $return);
967         } else if ($addbackpack) {
968             // If no email and password are given, no backpack is created/modified.
969             $this->assertEmpty($record);
970             $this->assertEquals($data['externalbackpackid'], $return);
971         } else {
972             // There weren't fields to add to the backpack so no DB change is expected.
973             $this->assertEmpty($record);
974             $this->assertEquals(0, $return);
975         }
977         // Confirm the existing backpack credential can be updated (if it has been created).
978         if (!empty($record)) {
979             $data['backpackemail'] = 'modified_' . $mail;
980             $data['id'] = $record->id;
981             $return = badges_save_backpack_credentials((object) $data);
982             $record = $DB->get_record('badge_backpack', ['userid' => $user->id]);
984             $this->assertNotEmpty($record);
985             $this->assertEquals($data['backpackemail'], $record->email);
986             $this->assertEquals($data['externalbackpackid'], $return);
987         }
988     }
990     /**
991      * Data provider for test_create_backpack_credentials().
992      *
993      * @return array
994      */
995     public function save_backpack_credentials_provider(): array {
996         return [
997             'Empty fields' => [
998                 false,
999             ],
1000             'No backpack mail or password are defined' => [
1001                 true,
1002             ],
1003             'Both backpack mail and password are defined' => [
1004                 true, 'test@test.com', '1234',
1005             ],
1006             'Only backpack mail is defined (no password is given)' => [
1007                 true, 'test@test.com', null,
1008             ],
1009             'Only backpack password is defined (no mail is given)' => [
1010                 true, null, '1234'
1011             ],
1012         ];
1013     }
1016     /**
1017      * Test badges_save_external_backpack without any auth details and also tests duplicate entries.
1018      *
1019      * @param boolean $withauth Test with authentication details provided
1020      * @param boolean $duplicates Test for duplicates
1021      * @dataProvider test_badges_save_external_backpack_provider
1022      * @throws dml_exception
1023      */
1024     public function test_badges_save_external_backpack($withauth, $duplicates) {
1025         global $DB;
1026         $this->resetAfterTest();
1027         $user = $this->getDataGenerator()->create_user();
1029         $data = [
1030             'userid' => $user->id,
1031             'apiversion' => 2,
1032             'backpackapiurl' => 'https://api.ca.badgr.io/v2',
1033             'backpackweburl' => 'https://ca.badgr.io',
1034         ];
1036         if ($withauth) {
1037             $data['backpackemail'] = 'test@test.com';
1038             $data['password'] = 'test';
1039         }
1041         $result = badges_save_external_backpack((object) $data);
1042         $record = $DB->get_record('badge_external_backpack', ['id' => $result]);
1043         $this->assertEquals($record->backpackweburl, $data['backpackweburl']);
1044         $this->assertEquals($record->backpackapiurl, $data['backpackapiurl']);
1045         $record = $DB->get_record('badge_backpack', ['userid' => $user->id]);
1046         if (!$withauth) {
1047             $this->assertEmpty($record);
1048         } else {
1049             $this->assertNotEmpty($record);
1050         }
1052         if ($duplicates) {
1053             // We shouldn't be able to insert multiple external_backpacks with the same values.
1054             $this->expectException('dml_write_exception');
1055             $result = badges_save_external_backpack((object)$data);
1056         }
1057     }
1059     /**
1060      * Provider for test_badges_save_external_backpack
1061      *
1062      * @return array
1063      */
1064     public function test_badges_save_external_backpack_provider() {
1065         return [
1066             "Test without any auth details and duplicates" => [
1067                 false, true
1068             ],
1069             "Test without any auth details and without duplicates" => [
1070                 false, false
1071             ],
1072             "Test with auth details and duplicates" => [
1073                 true, true
1074             ],
1075             "Test with any auth details and duplicates" => [
1076                 true, false
1077             ],
1078         ];
1079     }
1081     /**
1082      * Test backpack creation/update with auth details provided
1083      *
1084      * @param boolean $isadmin
1085      * @param boolean $updatetest
1086      * @dataProvider test_badges_create_site_backpack_provider
1087      */
1088     public function test_badges_create_site_backpack($isadmin, $updatetest) {
1089         global $DB;
1090         $this->resetAfterTest();
1092         $data = [
1093             'apiversion' => 2,
1094             'backpackapiurl' => 'https://api.ca.badgr.io/v2',
1095             'backpackweburl' => 'https://ca.badgr.io',
1096         ];
1098         $data['backpackemail'] = 'test@test.com';
1099         $data['password'] = 'test';
1100         if ($isadmin || $updatetest) {
1101             $this->setAdminUser();
1102             $backpack = badges_create_site_backpack((object) $data);
1103         }
1105         if ($isadmin) {
1106             if ($updatetest) {
1107                 $record = $DB->get_record('badge_backpack', ['userid' => 0]);
1108                 $data['badgebackpack'] = $record->id;
1109                 $data['backpackapiurl'] = 'https://api.ca.badgr.io/v3';
1110                 badges_update_site_backpack($backpack, (object)$data);
1111             }
1112             $record = $DB->get_record('badge_external_backpack', ['id' => $backpack]);
1113             $this->assertEquals($record->backpackweburl, $data['backpackweburl']);
1114             $this->assertEquals($record->backpackapiurl, $data['backpackapiurl']);
1115             $record = $DB->get_record('badge_backpack', ['userid' => 0]);
1116             $this->assertNotEmpty($record);
1117         } else {
1118             $user = $this->getDataGenerator()->create_user();
1119             $this->setUser($user);
1120             $this->expectException('required_capability_exception');
1121             if ($updatetest) {
1122                 $result = badges_update_site_backpack($backpack, (object) $data);
1123             } else {
1124                 $result = badges_create_site_backpack((object)$data);
1125             }
1126         }
1127     }
1129     /**
1130      * Provider for test_badges_(create/update)_site_backpack
1131      */
1132     public function test_badges_create_site_backpack_provider() {
1133         return [
1134             "Test as admin user - creation test" => [true, true],
1135             "Test as admin user - update test" => [true, false],
1136             "Test as normal user - creation test" => [false, true],
1137             "Test as normal user - update test" => [false, false],
1138         ];
1139     }
1141     /**
1142      * Test the badges_open_badges_backpack_api with different backpacks
1143      */
1144     public function test_badges_open_badges_backpack_api() {
1145         $this->resetAfterTest();
1147         $data = [
1148             'apiversion' => 2,
1149             'backpackapiurl' => 'https://api.ca.badgr.io/v2',
1150             'backpackweburl' => 'https://ca.badgr.io',
1151         ];
1153         // Given a complete set of unique data, a new backpack and auth records should exist in the tables.
1154         $data['backpackemail'] = 'test@test.com';
1155         $data['password'] = 'test';
1156         $backpack1 = badges_save_external_backpack((object) $data);
1157         $data['backpackweburl'] = 'https://eu.badgr.io';
1158         $data['backpackapiurl'] = 'https://api.eu.badgr.io/v2';
1159         $data['apiversion'] = 2.1;
1160         $backpack2 = badges_save_external_backpack((object) $data);
1162         set_config('badges_site_backpack', $backpack2);
1163         // The default response should check the default site backpack api version.
1164         $this->assertEquals(2.1, badges_open_badges_backpack_api());
1165         // Check the api version for the other backpack created.
1166         $this->assertEquals(2, badges_open_badges_backpack_api($backpack1));
1167         $this->assertEquals(2.1, badges_open_badges_backpack_api($backpack2));
1168     }
1170     /**
1171      * Test the badges_get_site_backpack function
1172      */
1173     public function test_badges_get_site_backpack() {
1174         $this->resetAfterTest();
1175         $user = $this->getDataGenerator()->create_user();
1176         $data = [
1177             'apiversion' => '2',
1178             'backpackapiurl' => 'https://api.ca.badgr.io/v2',
1179             'backpackweburl' => 'https://ca.badgr.io',
1180         ];
1181         $backpack1 = badges_save_external_backpack((object) $data);
1182         $data2 = array_merge($data, [
1183             'backpackapiurl' => 'https://api.eu.badgr.io/v2',
1184             'backpackweburl' => 'https://eu.badgr.io',
1185             'backpackemail' => 'test@test.com',
1186             'password' => 'test',
1187         ]);
1188         $backpack2 = badges_save_external_backpack((object) $data2);
1189         $data3 = array_merge($data2, [
1190             'userid' => $user->id,
1191             'externalbackpackid' => $backpack2,
1192             'backpackemail' => 'test2@test.com'
1193         ]);
1194         // In the following case, the id returned below equals backpack2. So we aren't storing it.
1195         badges_save_backpack_credentials((object) $data3);
1196         unset($data3['userid']);
1198         // Get a site back based on the id returned from creation and no user id provided.
1199         $this->assertEquals($data, array_intersect($data, (array) badges_get_site_backpack($backpack1)));
1200         $this->assertEquals($data2, array_intersect($data2, (array) badges_get_site_backpack($backpack2)));
1201         $this->assertEquals($data2, array_intersect($data2, (array) badges_get_site_backpack($backpack2, 0)));
1202         $this->assertEquals($data3, array_intersect($data3, (array) badges_get_site_backpack($backpack2, $user->id)));
1204         // Non-existent user backpack should return only configuration details and not auth details.
1205         $userbackpack = badges_get_site_backpack($backpack1, $user->id);
1206         $this->assertNull($userbackpack->badgebackpack);
1207         $this->assertNull($userbackpack->password);
1208         $this->assertNull($userbackpack->backpackemail);
1209     }
1211     /**
1212      * Test the badges_get_user_backpack function
1213      */
1214     public function test_badges_get_user_backpack() {
1215         $this->resetAfterTest();
1216         $user = $this->getDataGenerator()->create_user();
1217         $data = [
1218             'apiversion' => '2',
1219             'backpackapiurl' => 'https://api.ca.badgr.io/v2',
1220             'backpackweburl' => 'https://ca.badgr.io',
1221         ];
1222         $backpack1 = badges_save_external_backpack((object) $data);
1223         $data2 = array_merge($data, [
1224             'backpackapiurl' => 'https://api.eu.badgr.io/v2',
1225             'backpackweburl' => 'https://eu.badgr.io',
1226             'backpackemail' => 'test@test.com',
1227             'password' => 'test',
1228         ]);
1229         $backpack2 = badges_save_external_backpack((object) $data2);
1230         $data3 = array_merge($data2, [
1231             'userid' => $user->id,
1232             'externalbackpackid' => $backpack2,
1233             'backpackemail' => 'test2@test.com'
1234         ]);
1235         // In the following case, the id returned below equals backpack2. So we aren't storing it.
1236         badges_save_backpack_credentials((object) $data3);
1237         unset($data3['userid']);
1239         // Currently logged in as admin.
1240         $this->assertEquals($data2, array_intersect($data2, (array) badges_get_user_backpack()));
1241         $this->assertEquals($data2, array_intersect($data2, (array) badges_get_user_backpack(0)));
1242         $this->assertEquals($data3, array_intersect($data3, (array) badges_get_user_backpack($user->id)));
1244         // Non-existent user backpack should return nothing.
1245         $this->assertFalse(badges_get_user_backpack($backpack1, $user->id));
1247         // Login as user.
1248         $this->setUser($user);
1249         $this->assertEquals($data3, array_intersect($data3, (array) badges_get_user_backpack()));
1250     }
1252     /**
1253      * Test the badges_get_site_primary_backpack function
1254      *
1255      * @param boolean $withauth Testing with authentication or not.
1256      * @dataProvider test_badges_get_site_primary_backpack_provider
1257      */
1258     public function test_badges_get_site_primary_backpack($withauth) {
1259         $data = [
1260             'apiversion' => '2',
1261             'backpackapiurl' => 'https://api.ca.badgr.io/v2',
1262             'backpackweburl' => 'https://ca.badgr.io',
1263         ];
1264         if ($withauth) {
1265             $data = array_merge($data, [
1266                 'backpackemail' => 'test@test.com',
1267                 'password' => 'test',
1268             ]);
1269         }
1270         $backpack = badges_save_external_backpack((object) $data);
1272         set_config('badges_site_backpack', $backpack);
1273         $sitebackpack = badges_get_site_primary_backpack();
1274         $this->assertEquals($backpack, $sitebackpack->id);
1276         if ($withauth) {
1277             $this->assertEquals($data, array_intersect($data, (array) $sitebackpack));
1278             $this->assertEquals($data['password'], $sitebackpack->password);
1279             $this->assertEquals($data['backpackemail'], $sitebackpack->backpackemail);
1280         } else {
1281             $this->assertNull($sitebackpack->badgebackpack);
1282             $this->assertNull($sitebackpack->password);
1283             $this->assertNull($sitebackpack->backpackemail);
1284         }
1285     }
1287     /**
1288      * Test the test_badges_get_site_primary_backpack function.
1289      *
1290      * @return array
1291      */
1292     public function test_badges_get_site_primary_backpack_provider() {
1293         return [
1294             "Test with auth details" => [true],
1295             "Test without auth details" => [false],
1296         ];
1297     }
1299     /**
1300      * Test the Badgr URL generator function
1301      *
1302      * @param mixed $type Type corresponding to the badge entites
1303      * @param string $expected Expected string result
1304      * @dataProvider badgr_open_url_generator
1305      */
1306     public function test_badges_generate_badgr_open_url($type, $expected) {
1307         $data = [
1308             'apiversion' => '2',
1309             'backpackapiurl' => 'https://api.ca.badgr.io/v2',
1310             'backpackweburl' => 'https://ca.badgr.io',
1311             'backpackemail' => 'test@test.com',
1312             'password' => 'test',
1313         ];
1314         $backpack2 = badges_save_external_backpack((object) $data);
1315         $backpack = badges_get_site_backpack($backpack2);
1316         $this->assertEquals($expected, badges_generate_badgr_open_url($backpack, $type, 123455));
1317     }
1319     /**
1320      * Data provider for test_badges_generate_badgr_open_url
1321      * @return array
1322      */
1323     public function badgr_open_url_generator() {
1324         return [
1325             'Badgr Assertion URL test' => [
1326                 OPEN_BADGES_V2_TYPE_ASSERTION, "https://api.ca.badgr.io/public/assertions/123455"
1327             ],
1328             'Badgr Issuer URL test' => [
1329                 OPEN_BADGES_V2_TYPE_ISSUER, "https://api.ca.badgr.io/public/issuers/123455"
1330             ],
1331             'Badgr Badge URL test' => [
1332                 OPEN_BADGES_V2_TYPE_BADGE, "https://api.ca.badgr.io/public/badges/123455"
1333             ]
1334         ];
1335     }
1337     /**
1338      * Test badges_external_get_mapping function
1339      *
1340      * @param int $internalid The internal id of the mapping
1341      * @param int $externalid The external / remote ref to the mapping
1342      * @param mixed $expected The expected result from the function
1343      * @param string|null $field The field we are passing to the function. Null if we don't want to pass anything.ss
1344      *
1345      * @dataProvider badges_external_get_mapping_provider
1346      */
1347     public function test_badges_external_get_mapping($internalid, $externalid, $expected, $field = null) {
1348         $data = [
1349             'apiversion' => '2',
1350             'backpackapiurl' => 'https://api.ca.badgr.io/v2',
1351             'backpackweburl' => 'https://ca.badgr.io',
1352             'backpackemail' => 'test@test.com',
1353             'password' => 'test',
1354         ];
1355         $backpack2 = badges_save_external_backpack((object) $data);
1356         badges_external_create_mapping($backpack2, OPEN_BADGES_V2_TYPE_BADGE, $internalid, $externalid);
1357         $expected = $expected == "id" ? $backpack2 : $expected;
1358         if ($field) {
1359             $this->assertEquals($expected, badges_external_get_mapping($backpack2, OPEN_BADGES_V2_TYPE_BADGE, $internalid, $field));
1360         } else {
1361             $this->assertEquals($expected, badges_external_get_mapping($backpack2, OPEN_BADGES_V2_TYPE_BADGE, $internalid));
1362         }
1363     }
1365     /**
1366      * Data provider for badges_external_get_mapping_provider
1367      *
1368      * @return array
1369      */
1370     public function badges_external_get_mapping_provider() {
1371         return [
1372             "Get the site backpack value" => [
1373                 1234, 4321, 'id', 'sitebackpackid'
1374             ],
1375             "Get the type of the mapping" => [
1376                 1234, 4321, OPEN_BADGES_V2_TYPE_BADGE, 'type'
1377             ],
1378             "Get the externalid of the mapping" => [
1379                 1234, 4321, 4321, 'externalid'
1380             ],
1381             "Get the externalid of the mapping without providing a param" => [
1382                 1234, 4321, 4321, null
1383             ],
1384             "Get the internalid of the mapping" => [
1385                 1234, 4321, 1234, 'internalid'
1386             ]
1387         ];
1388     }