Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
16
 
17
namespace enrol_lti\local\ltiadvantage\task;
18
 
19
use enrol_lti\helper;
20
use enrol_lti\local\ltiadvantage\entity\user;
21
use enrol_lti\local\ltiadvantage\repository\resource_link_repository;
22
use enrol_lti\local\ltiadvantage\repository\user_repository;
23
 
24
defined('MOODLE_INTERNAL') || die();
25
 
26
require_once(__DIR__ . '/../lti_advantage_testcase.php');
27
 
28
/**
29
 * Tests for the enrol_lti\local\ltiadvantage\task\sync_members scheduled task.
30
 *
31
 * @package enrol_lti
32
 * @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com>
33
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34
 * @coversDefaultClass \enrol_lti\local\ltiadvantage\task\sync_members
35
 */
1441 ariadna 36
final class sync_members_test extends \lti_advantage_testcase {
1 efrain 37
 
38
    /**
39
     * Verify the user's profile picture has been set, which is useful to verify picture syncs.
40
     *
41
     * @param int $userid the id of the Moodle user.
42
     * @param bool $match true to verify a match, false to verify a non-match.
43
     */
44
    protected function verify_user_profile_image(int $userid, bool $match = true): void {
45
        global $CFG;
46
        $user = \core_user::get_user($userid);
47
        $usercontext = \context_user::instance($user->id);
48
        $expected = $CFG->wwwroot . '/pluginfile.php/' . $usercontext->id . '/user/icon/boost/f2?rev='. $user->picture;
49
 
50
        $page = new \moodle_page();
51
        $page->set_url('/user/profile.php');
52
        $page->set_context(\context_system::instance());
53
        $renderer = $page->get_renderer('core');
54
        $userpicture = new \user_picture($user);
55
        if ($match) {
56
            $this->assertEquals($expected, $userpicture->get_url($page, $renderer)->out(false));
57
        } else {
58
            $this->assertNotEquals($expected, $userpicture->get_url($page, $renderer)->out(false));
59
        }
60
 
61
    }
62
 
63
    /**
64
     * Helper to get a list of mocked member entries for use in the mocked sync task.
65
     *
66
     * @param array $userids the array of lti user ids to use.
67
     * @param array|null $legacyuserids legacy user ids for the lti11_legacy_user_id property, null if not desired.
68
     * @param bool $names whether to include names in the user data or not.
69
     * @param bool $emails whether to include email in the user data or not.
70
     * @param bool $linklevel whether to mock the user return data at link-level (true) or context-level (false).
71
     * @param bool $picture whether to mock a user's picture field in the return data.
72
     * @param array $roles an array of IMS roles to include with each member which, if empty, defaults to just the learner role.
73
     * @return array the array of users.
74
     * @throws \Exception if the legacyuserids array doesn't contain the correct number of ids.
75
     */
1441 ariadna 76
    protected static function get_mock_members_with_ids(
77
        array $userids,
78
        ?array $legacyuserids = null,
79
        $names = true,
80
        $emails = true,
81
        bool $linklevel = true,
82
        bool $picture = false,
83
        array $roles = [],
84
    ): array {
1 efrain 85
 
86
        if (!is_null($legacyuserids) && count($legacyuserids) != count($userids)) {
87
            throw new \Exception('legacyuserids must contain the same number of ids as $userids.');
88
        }
89
 
90
        if (empty($roles)) {
91
            $roles = ['http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'];
92
        }
93
 
94
        $users = [];
95
        foreach ($userids as $userid) {
96
            $user = ['user_id' => (string) $userid, 'roles' => $roles];
97
            if ($picture) {
1441 ariadna 98
                $user['picture'] = static::getExternalTestFileUrl('/test.jpg', false);
1 efrain 99
            }
100
            if ($names) {
101
                $user['given_name'] = 'Firstname' . $userid;
102
                $user['family_name'] = 'Surname' . $userid;
103
            }
104
            if ($emails) {
105
                $user['email'] = "firstname.surname{$userid}@lms.example.org";
106
            }
107
            if ($legacyuserids) {
108
                $user['lti11_legacy_user_id'] = array_shift($legacyuserids);
109
            }
110
            if ($linklevel) {
111
                // Link-level memberships also include a message property.
112
                $user['message'] = [
113
                    'https://purl.imsglobal.org/spec/lti/claim/message_type' => 'LtiResourceLinkRequest'
114
                ];
115
            }
116
            $users[] = $user;
117
        }
118
        return $users;
119
    }
120
 
121
    /**
122
     * Gets a task mocked to only support resource-link-level memberships request.
123
     *
124
     * @param array $resourcelinks array for stipulating per link users, containing list of [resourcelink, members].
125
     * @return sync_members|\PHPUnit\Framework\MockObject\MockObject
126
     */
127
    protected function get_mock_task_resource_link_level(array $resourcelinks = []) {
128
        $mocktask = $this->getMockBuilder(sync_members::class)
129
            ->onlyMethods(['get_resource_link_level_members', 'get_context_level_members'])
130
            ->getMock();
131
        $mocktask->expects($this->any())
132
            ->method('get_context_level_members')
133
            ->will($this->returnCallback(function() {
134
                return false;
135
            }));
136
        $expectedcount = !empty($resourcelinks) ? count($resourcelinks) : 1;
137
        $mocktask->expects($this->exactly($expectedcount))
138
            ->method('get_resource_link_level_members')
139
            ->will($this->returnCallback(function ($nrpsinfo, $serviceconnector, $registration, $reslink) use ($resourcelinks) {
140
                if ($resourcelinks) {
141
                    foreach ($resourcelinks as $rl) {
142
                        if ($reslink->get_resourcelinkid() === $rl[0]->get_resourcelinkid()) {
143
                            return $rl[1];
144
                        }
145
                    }
146
                } else {
1441 ariadna 147
                    return self::get_mock_members_with_ids(range(1, 2));
1 efrain 148
                }
149
            }));
150
        return $mocktask;
151
    }
152
 
153
    /**
154
     * Gets a task mocked to only support context-level memberships request.
155
     *
156
     * @return sync_members|\PHPUnit\Framework\MockObject\MockObject
157
     */
158
    protected function get_mock_task_context_level() {
159
        $mocktask = $this->getMockBuilder(sync_members::class)
160
            ->onlyMethods(['get_resource_link_level_members', 'get_context_level_members'])
161
            ->getMock();
162
        $mocktask->expects($this->any())
163
            ->method('get_resource_link_level_members')
164
            ->will($this->returnCallback(function() {
165
                // An exception is what the service code will throw if the resource link level service isn't available.
166
                throw new \Exception();
167
            }));
168
        $mocktask->expects($this->any())
169
            ->method('get_context_level_members')
170
            ->will($this->returnCallback(function() {
1441 ariadna 171
                return self::get_mock_members_with_ids(range(1, 3), null, true, true, false);
1 efrain 172
            }));;
173
        return $mocktask;
174
    }
175
 
176
    /**
177
     * Gets a sync task, with the remote calls mocked to return the supplied users.
178
     *
179
     * See get_mock_members_with_ids() for generating the users for input.
180
     *
181
     * @param array $users a list of users, the result of a call to get_mock_members_with_ids().
182
     * @return \PHPUnit\Framework\MockObject\MockObject the mock task.
183
     */
184
    protected function get_mock_task_with_users(array $users) {
185
        $mocktask = $this->getMockBuilder(sync_members::class)
186
            ->onlyMethods(['get_resource_link_level_members', 'get_context_level_members'])
187
            ->getMock();
188
        $mocktask->expects($this->any())
189
            ->method('get_context_level_members')
190
            ->will($this->returnCallback(function() {
191
                return false;
192
            }));
193
        $mocktask->expects($this->any())
194
            ->method('get_resource_link_level_members')
195
            ->will($this->returnCallback(function () use ($users) {
196
                return $users;
197
            }));
198
        return $mocktask;
199
    }
200
 
201
    /**
202
     * Check that all the given ltiusers are enrolled in the course.
203
     *
204
     * @param \stdClass $course the course instance.
205
     * @param user[] $ltiusers array of lti user instances.
206
     */
207
    protected function verify_course_enrolments(\stdClass $course, array $ltiusers) {
208
        global $CFG;
209
        require_once($CFG->libdir . '/enrollib.php');
210
        $enrolledusers = get_enrolled_users(\context_course::instance($course->id));
211
        $this->assertCount(count($ltiusers), $enrolledusers);
212
        $enrolleduserids = array_map(function($stringid) {
213
            return (int) $stringid;
214
        }, array_column($enrolledusers, 'id'));
215
        foreach ($ltiusers as $ltiuser) {
216
            $this->assertContains($ltiuser->get_localid(), $enrolleduserids);
217
        }
218
    }
219
 
220
    /**
221
     * Test confirming task name.
222
     *
223
     * @covers ::get_name
224
     */
11 efrain 225
    public function test_get_name(): void {
1 efrain 226
        $this->assertEquals(get_string('tasksyncmembers', 'enrol_lti'), (new sync_members())->get_name());
227
    }
228
 
229
    /**
230
     * Test a resource-link-level membership sync, confirming that all relevant domain objects are updated properly.
231
     *
232
     * @covers ::execute
233
     */
11 efrain 234
    public function test_resource_link_level_sync(): void {
1 efrain 235
        $this->resetAfterTest();
236
        [$course, $resource] = $this->create_test_environment();
237
 
238
        // Launch the tool for a user.
1441 ariadna 239
        $mocklaunch = $this->get_mock_launch($resource, self::get_mock_launch_users_with_ids(['1'])[0]);
1 efrain 240
        $instructoruser = $this->lti_advantage_user_authenticates('1');
241
        $launchservice = $this->get_tool_launch_service();
242
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
243
 
244
        // Sync members.
245
        $task = $this->get_mock_task_resource_link_level();
246
        $task->execute();
247
 
248
        // Verify 2 users and their corresponding course enrolments exist.
249
        $this->expectOutputRegex(
250
            "/Completed - Synced members for tool '$resource->id' in the course '$course->id'. ".
251
            "Processed 2 users; enrolled 2 members; unenrolled 0 members./"
252
        );
253
        $userrepo = new user_repository();
254
        $ltiusers = $userrepo->find_by_resource($resource->id);
255
        $this->assertCount(2, $ltiusers);
256
        $this->verify_course_enrolments($course, $ltiusers);
257
    }
258
 
259
    /**
260
     * Test a resource-link-level membership sync when there are more than one resource links for the resource.
261
     *
262
     * @covers ::execute
263
     */
11 efrain 264
    public function test_resource_link_level_sync_multiple_resource_links(): void {
1 efrain 265
        $this->resetAfterTest();
266
        [$course, $resource] = $this->create_test_environment();
267
 
268
        // Launch twice - once from each resource link in the platform.
269
        $launchservice = $this->get_tool_launch_service();
270
        $instructoruser = $this->lti_advantage_user_authenticates('1');
1441 ariadna 271
        $mocklaunch = $this->get_mock_launch($resource, self::get_mock_launch_users_with_ids(['1'])[0], '123');
1 efrain 272
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
1441 ariadna 273
        $mocklaunch = $this->get_mock_launch($resource, self::get_mock_launch_users_with_ids(['1'])[0], '456');
1 efrain 274
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
275
 
276
        // Now, grab the resource links.
277
        $rlrepo = new resource_link_repository();
278
        $reslinks = $rlrepo->find_by_resource($resource->id);
1441 ariadna 279
        $mockmembers = self::get_mock_members_with_ids(range(1, 10));
1 efrain 280
        $mockusers1 = array_slice($mockmembers, 0, 6);
281
        $mockusers2 = array_slice($mockmembers, 6);
282
        $resourcelinks = [
283
            [$reslinks[0], $mockusers1],
284
            [$reslinks[1], $mockusers2]
285
        ];
286
 
287
        // Sync the members, using the mock task set up to sync different sets of users for each resource link.
288
        $task = $this->get_mock_task_resource_link_level($resourcelinks);
289
        ob_start();
290
        $task->execute();
291
        $output = ob_get_contents();
292
        ob_end_clean();
293
 
294
        // Verify 10 users and their corresponding course enrolments exist.
295
        $userrepo = new user_repository();
296
        $ltiusers = $userrepo->find_by_resource($resource->id);
297
        $this->assertCount(10, $ltiusers);
298
        $this->assertStringContainsString("Completed - Synced 6 members for the resource link", $output);
299
        $this->assertStringContainsString("Completed - Synced 4 members for the resource link", $output);
300
        $this->assertStringContainsString("Completed - Synced members for tool '$resource->id' in the course '".
301
            "$resource->courseid'. Processed 10 users; enrolled 10 members; unenrolled 0 members.\n", $output);
302
        $this->verify_course_enrolments($course, $ltiusers);
303
    }
304
 
305
    /**
306
     * Verify the task will update users' profile pictures if the 'picture' member field is provided.
307
     *
308
     * @covers ::execute
309
     */
11 efrain 310
    public function test_user_profile_image_sync(): void {
1 efrain 311
        $this->resetAfterTest();
312
        [$course, $resource] = $this->create_test_environment();
313
 
314
        // Launch the tool for a user.
1441 ariadna 315
        $mocklaunch = $this->get_mock_launch($resource, self::get_mock_launch_users_with_ids(['1'])[0]);
1 efrain 316
        $launchservice = $this->get_tool_launch_service();
317
        $instructoruser = $this->lti_advantage_user_authenticates('1');
318
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
319
 
320
        // Sync members.
1441 ariadna 321
        $task = $this->get_mock_task_with_users(self::get_mock_members_with_ids(['1'], null, true, true, true, true));
1 efrain 322
        ob_start();
323
        $task->execute();
324
        ob_end_clean();
325
 
326
        // Verify 1 users and their corresponding course enrolments exist.
327
        $userrepo = new user_repository();
328
        $ltiusers = $userrepo->find_by_resource($resource->id);
329
        $this->assertCount(1, $ltiusers);
330
        $this->verify_course_enrolments($course, $ltiusers);
331
 
332
        // Verify user profile image has been updated.
333
        $this->verify_user_profile_image($ltiusers[0]->get_localid());
334
    }
335
 
336
    /**
337
     * Test a context-level membership sync, confirming that all relevant domain objects are updated properly.
338
     *
339
     * @covers ::execute
340
     */
11 efrain 341
    public function test_context_level_sync(): void {
1 efrain 342
        $this->resetAfterTest();
343
        [$course, $resource] = $this->create_test_environment();
344
 
345
        // Launch the tool for a user.
1441 ariadna 346
        $mocklaunch = $this->get_mock_launch($resource, self::get_mock_launch_users_with_ids(['1'])[0]);
1 efrain 347
        $launchservice = $this->get_tool_launch_service();
348
        $instructoruser = $this->lti_advantage_user_authenticates('1');
349
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
350
 
351
        // Sync members.
352
        $task = $this->get_mock_task_context_level();
353
        ob_start();
354
        $task->execute();
355
        ob_end_clean();
356
 
357
        // Verify 3 users and their corresponding course enrolments exist.
358
        $userrepo = new user_repository();
359
        $ltiusers = $userrepo->find_by_resource($resource->id);
360
        $this->assertCount(3, $ltiusers);
361
        $this->verify_course_enrolments($course, $ltiusers);
362
    }
363
 
364
    /**
365
     * Test verifying the sync task handles the omission/inclusion of PII information for users.
366
     *
367
     * @covers ::execute
368
     */
11 efrain 369
    public function test_sync_user_data(): void {
1 efrain 370
        $this->resetAfterTest();
371
        [$course, $resource, $resource2, $resource3, $appreg] = $this->create_test_environment();
372
        $userrepo = new user_repository();
373
 
374
        // Launch the tool for a user.
1441 ariadna 375
        $mocklaunch = $this->get_mock_launch($resource, self::get_mock_launch_users_with_ids(['1'])[0]);
1 efrain 376
        $launchservice = $this->get_tool_launch_service();
377
        $instructoruser = $this->lti_advantage_user_authenticates('1');
378
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
379
 
380
        // Sync members.
1441 ariadna 381
        $task = $this->get_mock_task_with_users(self::get_mock_members_with_ids(range(1, 5), null, false, false));
1 efrain 382
 
383
        ob_start();
384
        $task->execute();
385
        ob_end_clean();
386
 
387
        // Verify 5 users and their corresponding course enrolments exist.
388
        $ltiusers = $userrepo->find_by_resource($resource->id);
389
        $this->assertCount(5, $ltiusers);
390
        $this->verify_course_enrolments($course, $ltiusers);
391
 
392
        // Since user data wasn't included in the response, the users will have been synced using fallbacks,
393
        // so verify these.
394
        foreach ($ltiusers as $ltiuser) {
395
            $user = \core_user::get_user($ltiuser->get_localid());
396
            // Firstname falls back to sourceid.
397
            $this->assertEquals($ltiuser->get_sourceid(), $user->firstname);
398
 
399
            // Lastname falls back to resource context id.
400
            $this->assertEquals($appreg->get_platformid(), $user->lastname);
401
 
402
            // Email falls back to example.com.
403
            $issuersubhash = sha1($appreg->get_platformid() . '_' . $ltiuser->get_sourceid());
404
            $this->assertEquals("enrol_lti_13_{$issuersubhash}@example.com", $user->email);
405
        }
406
 
407
        // Sync again, this time with user data included.
1441 ariadna 408
        $mockmembers = self::get_mock_members_with_ids(range(1, 5));
1 efrain 409
        $task = $this->get_mock_task_with_users($mockmembers);
410
 
411
        ob_start();
412
        $task->execute();
413
        ob_end_clean();
414
 
415
        // User data was included in the response and should have been updated.
416
        $ltiusers = $userrepo->find_by_resource($resource->id);
417
        $this->assertCount(5, $ltiusers);
418
        $this->verify_course_enrolments($course, $ltiusers);
419
        foreach ($ltiusers as $ltiuser) {
420
            $user = \core_user::get_user($ltiuser->get_localid());
421
            $mockmemberindex = array_search($ltiuser->get_sourceid(), array_column($mockmembers, 'user_id'));
422
            $mockmember = $mockmembers[$mockmemberindex];
423
            $this->assertEquals($mockmember['given_name'], $user->firstname);
424
            $this->assertEquals($mockmember['family_name'], $user->lastname);
425
            $this->assertEquals($mockmember['email'], $user->email);
426
        }
427
    }
428
 
429
    /**
430
     * Test verifying the task won't sync members for shared resources having member sync disabled.
431
     *
432
     * @covers ::execute
433
     */
11 efrain 434
    public function test_membership_sync_disabled(): void {
1 efrain 435
        $this->resetAfterTest();
436
        [$course, $resource] = $this->create_test_environment(true, true, false);
437
 
438
        // Launch the tool for a user.
1441 ariadna 439
        $mockuser = self::get_mock_launch_users_with_ids(['1'])[0];
1 efrain 440
        $mocklaunch = $this->get_mock_launch($resource, $mockuser);
441
        $launchservice = $this->get_tool_launch_service();
442
        $instructoruser = $this->lti_advantage_user_authenticates('1');
443
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
444
 
445
        // Sync members.
1441 ariadna 446
        $task = $this->get_mock_task_with_users(self::get_mock_launch_users_with_ids(range(1, 4)));
1 efrain 447
        ob_start();
448
        $task->execute();
449
        ob_end_clean();
450
 
451
        // Verify no users were added or removed.
452
        // A single user (the user who launched the resource link) is expected.
453
        $userrepo = new user_repository();
454
        $ltiusers = $userrepo->find_by_resource($resource->id);
455
        $this->assertCount(1, $ltiusers);
456
        $this->assertEquals($mockuser['user_id'], $ltiusers[0]->get_sourceid());
457
        $this->verify_course_enrolments($course, $ltiusers);
458
    }
459
 
460
    /**
461
     * Test verifying the sync task for resources configured as 'helper::MEMBER_SYNC_ENROL_AND_UNENROL'.
462
     *
463
     * @covers ::execute
464
     */
11 efrain 465
    public function test_sync_mode_enrol_and_unenrol(): void {
1 efrain 466
        $this->resetAfterTest();
467
        [$course, $resource] = $this->create_test_environment();
468
        $userrepo = new user_repository();
469
 
470
        // Launch the tool for a user.
1441 ariadna 471
        $mockuser = self::get_mock_launch_users_with_ids(['1'])[0];
1 efrain 472
        $mocklaunch = $this->get_mock_launch($resource, $mockuser);
473
        $launchservice = $this->get_tool_launch_service();
474
        $instructoruser = $this->lti_advantage_user_authenticates('1');
475
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
476
 
477
        // Sync members.
1441 ariadna 478
        $task = $this->get_mock_task_with_users(self::get_mock_members_with_ids(range(1, 3)));
1 efrain 479
 
480
        ob_start();
481
        $task->execute();
482
        ob_end_clean();
483
 
484
        // Verify 3 users and their corresponding course enrolments exist.
485
        $ltiusers = $userrepo->find_by_resource($resource->id);
486
        $this->assertCount(3, $ltiusers);
487
        $this->verify_course_enrolments($course, $ltiusers);
488
 
489
        // Now, simulate a subsequent sync in which 1 existing user maintains access,
490
        // 2 existing users are unenrolled and 3 new users are enrolled.
1441 ariadna 491
        $task2 = $this->get_mock_task_with_users(self::get_mock_members_with_ids(['1', '4', '5', '6']));
1 efrain 492
        ob_start();
493
        $task2->execute();
494
        ob_end_clean();
495
 
496
        // Verify the missing users have been unenrolled and new users enrolled.
497
        $ltiusers = $userrepo->find_by_resource($resource->id);
498
        $this->assertCount(4, $ltiusers);
499
        $unenrolleduserids = ['2', '3'];
500
        $enrolleduserids = ['1', '4', '5', '6'];
501
        foreach ($ltiusers as $ltiuser) {
502
            $this->assertNotContains($ltiuser->get_sourceid(), $unenrolleduserids);
503
            $this->assertContains($ltiuser->get_sourceid(), $enrolleduserids);
504
        }
505
        $this->verify_course_enrolments($course, $ltiusers);
506
    }
507
 
508
    /**
509
     * Confirm the sync task operation for resources configured as 'helper::MEMBER_SYNC_UNENROL_MISSING'.
510
     *
511
     * @covers ::execute
512
     */
11 efrain 513
    public function test_sync_mode_unenrol_missing(): void {
1 efrain 514
        $this->resetAfterTest();
515
        [$course, $resource] = $this->create_test_environment(true, true, true, helper::MEMBER_SYNC_UNENROL_MISSING);
516
        $userrepo = new user_repository();
517
 
518
        // Launch the tool for a user.
1441 ariadna 519
        $mocklaunch = $this->get_mock_launch($resource, self::get_mock_launch_users_with_ids([1])[0]);
1 efrain 520
        $launchservice = $this->get_tool_launch_service();
521
        $instructoruser = $this->lti_advantage_user_authenticates('1');
522
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
523
        $this->assertCount(1, $userrepo->find_by_resource($resource->id));
524
 
525
        // Sync members using a payload which doesn't include the original launch user (User id = 1).
1441 ariadna 526
        $task = $this->get_mock_task_with_users(self::get_mock_members_with_ids(range(2, 3)));
1 efrain 527
 
528
        ob_start();
529
        $task->execute();
530
        ob_end_clean();
531
 
532
        // Verify the original user (launching user) has been unenrolled and that no new members have been enrolled.
533
        $ltiusers = $userrepo->find_by_resource($resource->id);
534
        $this->assertCount(0, $ltiusers);
535
    }
536
 
537
    /**
538
     * Confirm the sync task operation for resources configured as 'helper::MEMBER_SYNC_ENROL_NEW'.
539
     *
540
     * @covers ::execute
541
     */
11 efrain 542
    public function test_sync_mode_enrol_new(): void {
1 efrain 543
        $this->resetAfterTest();
544
        [$course, $resource] = $this->create_test_environment(true, true, true, helper::MEMBER_SYNC_ENROL_NEW);
545
        $userrepo = new user_repository();
546
 
547
        // Launch the tool for a user.
1441 ariadna 548
        $mocklaunch = $this->get_mock_launch($resource, self::get_mock_launch_users_with_ids([1])[0]);
1 efrain 549
        $launchservice = $this->get_tool_launch_service();
550
        $instructoruser = $this->lti_advantage_user_authenticates('1');
551
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
552
        $this->assertCount(1, $userrepo->find_by_resource($resource->id));
553
 
554
        // Sync members using a payload which includes two new members only (i.e. not the original launching user).
1441 ariadna 555
        $task = $this->get_mock_task_with_users(self::get_mock_members_with_ids(range(2, 3)));
1 efrain 556
 
557
        ob_start();
558
        $task->execute();
559
        ob_end_clean();
560
 
561
        // Verify we now have 3 enrolments. The original user (who was not unenrolled) and the 2 new users.
562
        $ltiusers = $userrepo->find_by_resource($resource->id);
563
        $this->assertCount(3, $ltiusers);
564
        $this->verify_course_enrolments($course, $ltiusers);
565
    }
566
 
567
    /**
568
     * Test confirming that no changes take place if the auth_lti plugin is not enabled.
569
     *
570
     * @covers ::execute
571
     */
11 efrain 572
    public function test_sync_auth_disabled(): void {
1 efrain 573
        $this->resetAfterTest();
574
        [$course, $resource] = $this->create_test_environment(false);
575
        $userrepo = new user_repository();
576
 
577
        // Launch the tool for a user.
1441 ariadna 578
        $mocklaunch = $this->get_mock_launch($resource, self::get_mock_launch_users_with_ids([1])[0]);
1 efrain 579
        $launchservice = $this->get_tool_launch_service();
580
        $instructoruser = $this->lti_advantage_user_authenticates('1');
581
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
582
        $this->assertCount(1, $userrepo->find_by_resource($resource->id));
583
 
584
        // If the task were to run, this would trigger 1 unenrolment (the launching user) and 3 enrolments.
1441 ariadna 585
        $task = $this->get_mock_task_with_users(self::get_mock_members_with_ids(range(2, 2)));
1 efrain 586
        $task->execute();
587
 
588
        // Verify that the sync didn't take place.
589
        $this->expectOutputRegex("/Skipping task - Authentication plugin 'LTI' is not enabled/");
590
        $this->assertCount(1, $userrepo->find_by_resource($resource->id));
591
    }
592
 
593
    /**
594
     * Test confirming that no sync takes place when the enrol_lti plugin is not enabled.
595
     *
596
     * @covers ::execute
597
     */
11 efrain 598
    public function test_sync_enrol_disabled(): void {
1 efrain 599
        $this->resetAfterTest();
600
        [$course, $resource] = $this->create_test_environment(true, false);
601
        $userrepo = new user_repository();
602
 
603
        // Launch the tool for a user.
1441 ariadna 604
        $mocklaunch = $this->get_mock_launch($resource, self::get_mock_launch_users_with_ids([1])[0]);
1 efrain 605
        $launchservice = $this->get_tool_launch_service();
606
        $instructoruser = $this->lti_advantage_user_authenticates('1');
607
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
608
        $this->assertCount(1, $userrepo->find_by_resource($resource->id));
609
 
610
        // If the task were to run, this would trigger 1 unenrolment of the launching user and enrolment of 3 users.
1441 ariadna 611
        $task = $this->get_mock_task_with_users(self::get_mock_members_with_ids(range(2, 2)));
1 efrain 612
        $task->execute();
613
 
614
        // Verify that the sync didn't take place.
615
        $this->expectOutputRegex("/Skipping task - The 'Publish as LTI tool' plugin is disabled/");
616
        $this->assertCount(1, $userrepo->find_by_resource($resource->id));
617
    }
618
 
619
    /**
620
     * Test syncing members when the enrolment instance is disabled.
621
     *
622
     * @covers ::execute
623
     */
11 efrain 624
    public function test_sync_members_disabled_instance(): void {
1 efrain 625
        $this->resetAfterTest();
626
        global $DB;
627
 
628
        [$course, $resource, $resource2, $resource3] = $this->create_test_environment();
629
        $userrepo = new user_repository();
630
 
631
        // Disable resource 1.
632
        $enrol = (object) ['id' => $resource->enrolid, 'status' => ENROL_INSTANCE_DISABLED];
633
        $DB->update_record('enrol', $enrol);
634
 
635
        // Delete the activity being shared by resource2, leaving resource 2 disabled as a result.
636
        $modcontext = \context::instance_by_id($resource2->contextid);
637
        course_delete_module($modcontext->instanceid);
638
 
639
        // Only the enabled resource 3 should sync members.
1441 ariadna 640
        $task = $this->get_mock_task_with_users(self::get_mock_members_with_ids(range(1, 1)));
1 efrain 641
        $task->execute();
642
 
643
        $this->expectOutputRegex(
644
            "/^Starting - Member sync for published resource '$resource3->id' for course '$course->id'.\n".
645
            "Completed - Synced members for tool '$resource3->id' in the course '$course->id'. Processed 0 users; ".
646
            "enrolled 0 members; unenrolled 0 members.\n$/"
647
        );
648
        $this->assertCount(0, $userrepo->find_by_resource($resource->id));
649
    }
650
 
651
    /**
652
     * Test syncing members for a membersync-enabled resource when the launch omits the NRPS service endpoints.
653
     *
654
     * @covers ::execute
655
     */
11 efrain 656
    public function test_sync_no_nrps_support(): void {
1 efrain 657
        $this->resetAfterTest();
658
        [$course, $resource] = $this->create_test_environment();
659
        $userrepo = new user_repository();
660
 
661
        // Launch the tool for a user.
1441 ariadna 662
        $mockinstructor = self::get_mock_launch_users_with_ids([1])[0];
1 efrain 663
        $mocklaunch = $this->get_mock_launch($resource, $mockinstructor, null, null, false);
664
        $launchservice = $this->get_tool_launch_service();
665
        $instructoruser = $this->lti_advantage_user_authenticates('1');
666
        $launchservice->user_launches_tool($instructoruser, $mocklaunch);
667
        $this->assertCount(1, $userrepo->find_by_resource($resource->id));
668
 
669
        // The task would sync an additional 2 users if the link had NRPS service support.
1441 ariadna 670
        $task = $this->get_mock_task_with_users(self::get_mock_members_with_ids(range(2, 2)));
1 efrain 671
 
672
        // We expect the task to report that it is skipping the resource due to a lack of NRPS support.
673
        $task->execute();
674
 
675
        // Verify no enrolments or unenrolments.
676
        $this->expectOutputRegex(
677
            "/Skipping - No names and roles service found.\n".
678
            "Completed - Synced members for tool '{$resource->id}' in the course '{$course->id}'. ".
679
            "Processed 0 users; enrolled 0 members; unenrolled 0 members./"
680
        );
681
        $this->assertCount(1, $userrepo->find_by_resource($resource->id));
682
    }
683
 
684
    /**
685
     * Test confirming that preexisting, non-lti user accounts do not have their profiles or pictures updated during sync.
686
     *
687
     * @covers ::execute
688
     */
11 efrain 689
    public function test_sync_non_lti_linked_user(): void {
1 efrain 690
        $this->resetAfterTest();
691
 
692
        // Set up the environment.
693
        [$course, $resource] = $this->create_test_environment();
694
 
695
        // Fake an auth - making sure it's a manual account.
696
        $authenticateduser = $this->lti_advantage_user_authenticates('123');
697
        $authenticateduser->auth = 'manual';
698
        $authenticateduser->password = '1234abcD*';
699
        user_update_user($authenticateduser);
700
        $authenticateduser = \core_user::get_user($authenticateduser->id);
701
 
702
        // Mock the launch for the specified user.
1441 ariadna 703
        $mocklaunchuser = self::get_mock_launch_users_with_ids([$authenticateduser->id])[0];
1 efrain 704
        $mocklaunch = $this->get_mock_launch($resource, $mocklaunchuser);
705
        $this->get_tool_launch_service()->user_launches_tool($authenticateduser, $mocklaunch);
706
 
707
        // Prepare the sync task, with a stubbed list of members.
1441 ariadna 708
        $task = $this->get_mock_task_with_users(self::get_mock_members_with_ids(['123'], null, true, true, true, true));
1 efrain 709
 
710
        // Run the member sync.
711
        $this->expectOutputRegex(
712
            "/Skipped profile sync for user '$authenticateduser->id'. The user does not belong to the LTI auth method.\n" .
713
            "Skipped picture sync for user '$authenticateduser->id'. The user does not belong to the LTI auth method/"
714
        );
715
        $task->execute();
716
 
717
        $updateduser = \core_user::get_user($authenticateduser->id);
718
        $this->assertEquals($authenticateduser->firstname, $updateduser->firstname);
719
        $this->assertEquals($authenticateduser->lastname, $updateduser->lastname);
720
        $this->assertEquals($authenticateduser->email, $updateduser->email);
721
        $this->verify_user_profile_image($authenticateduser->id, false);
722
    }
723
 
724
    /**
725
     * Test the member sync for a range of scenarios including migrated tools, unlaunched tools, provisioning methods.
726
     *
727
     * @dataProvider member_sync_data_provider
728
     * @param array|null $legacydata array detailing what legacy information to create, or null if not required.
729
     * @param array|null $resourceconfig array detailing config values to be used when creating the test enrol_lti instances.
730
     * @param array $launchdata array containing details of the launch, including user and migration claim.
731
     * @param array|null $syncmembers the members to use in the mock sync.
732
     * @param array $expected the array detailing expectations.
733
     * @covers ::execute
734
     */
735
    public function test_sync_enrolments_and_migration(?array $legacydata, ?array $resourceconfig, array $launchdata,
11 efrain 736
            ?array $syncmembers, array $expected): void {
1 efrain 737
 
738
        $this->resetAfterTest();
739
 
740
        // Set up the environment.
741
        [$course, $resource] = $this->create_test_environment(true, true, true, helper::MEMBER_SYNC_ENROL_AND_UNENROL, true, false,
742
            0, $resourceconfig['provisioningmodeinstructor'] ?? 0, $resourceconfig['provisioningmodelearner'] ?? 0);
743
 
744
        // Set up legacy tool and user data.
745
        if ($legacydata) {
746
            [$legacytools, $legacyconsumerrecord, $legacyusers] = $this->setup_legacy_data($course, $legacydata);
747
        }
748
 
749
        // Mock the launch for the specified user.
750
        $mocklaunch = $this->get_mock_launch($resource, $launchdata['user'], null, [], true,
751
            $launchdata['launch_migration_claim']);
752
 
753
        // Perform the launch.
754
        $instructoruser = $this->lti_advantage_user_authenticates(
755
            $launchdata['user']['user_id'],
756
            $launchdata['launch_migration_claim'] ?? []
757
        );
758
        $this->get_tool_launch_service()->user_launches_tool($instructoruser, $mocklaunch);
759
 
760
        // Prepare the sync task, with a stubbed list of members.
761
        $task = $this->get_mock_task_with_users($syncmembers);
762
 
763
        // Run the member sync.
764
        ob_start();
765
        $task->execute();
766
        ob_end_clean();
767
 
768
        // Verify enrolments.
769
        $ltiusers = (new user_repository())->find_by_resource($resource->id);
770
        $enrolled = array_filter($expected['enrolments'], function($user) {
771
            return $user['is_enrolled'];
772
        });
773
        $this->assertCount(count($enrolled), $ltiusers);
774
        $this->verify_course_enrolments($course, $ltiusers);
775
 
776
        // Verify migration, if expected.
777
        if ($legacydata) {
778
            $legacyuserids = array_column($legacyusers, 'id');
779
            foreach ($ltiusers as $ltiuser) {
780
                $this->assertArrayHasKey($ltiuser->get_sourceid(), $expected['enrolments']);
781
                if (!$expected['enrolments'][$ltiuser->get_sourceid()]['is_migrated']) {
782
                    // Those members who hadn't launched over 1p1 prior will have new lti user records created.
783
                    $this->assertNotContains((string)$ltiuser->get_localid(), $legacyuserids);
784
                } else {
785
                    // Those members who were either already migrated during launch, or were migrated during the sync,
786
                    // will be mapped to their legacy user accounts.
787
                    $this->assertContains((string)$ltiuser->get_localid(), $legacyuserids);
788
                }
789
            }
790
        }
791
    }
792
 
793
    /**
794
     * Data provider for member syncs.
795
     *
796
     * @return array[] the array of test data.
797
     */
1441 ariadna 798
    public static function member_sync_data_provider(): array {
1 efrain 799
        global $CFG;
800
        require_once($CFG->dirroot . '/auth/lti/auth.php');
801
        return [
802
            'Migrated tool, user ids changed, new and existing users present in sync' => [
1441 ariadna 803
                'legacydata' => [
1 efrain 804
                    'users' => [
805
                        ['user_id' => '1'],
806
                        ['user_id' => '2'],
807
                    ],
808
                    'consumer_key' => 'CONSUMER_1',
809
                    'tools' => [
810
                        ['secret' => 'toolsecret1'],
811
                        ['secret' => 'toolsecret2'],
812
                    ]
813
                ],
1441 ariadna 814
                'resourceconfig' => null,
815
                'launchdata' => [
816
                    'user' => self::get_mock_launch_users_with_ids(['1p3_1'])[0],
1 efrain 817
                    'launch_migration_claim' => [
818
                        'consumer_key' => 'CONSUMER_1',
819
                        'signing_secret' => 'toolsecret1',
820
                        'user_id' => '1',
821
                        'context_id' => 'd345b',
822
                        'tool_consumer_instance_guid' => '12345-123',
823
                        'resource_link_id' => '4b6fa'
824
                    ],
825
                ],
1441 ariadna 826
                'syncmembers' => [
827
                    self::get_mock_members_with_ids(['1p3_1'], ['1'])[0],
828
                    self::get_mock_members_with_ids(['1p3_2'], ['2'])[0],
829
                    self::get_mock_members_with_ids(['1p3_3'], ['3'])[0],
830
                    self::get_mock_members_with_ids(['1p3_4'], ['4'])[0],
1 efrain 831
                ],
832
                'expected' => [
833
                    'enrolments' => [
834
                        '1p3_1' => [
835
                            'is_enrolled' => true,
836
                            'is_migrated' => true,
837
                        ],
838
                        '1p3_2' => [
839
                            'is_enrolled' => true,
840
                            'is_migrated' => true,
841
                        ],
842
                        '1p3_3' => [
843
                            'is_enrolled' => true,
844
                            'is_migrated' => false,
845
                        ],
846
                        '1p3_4' => [
847
                            'is_enrolled' => true,
848
                            'is_migrated' => false,
849
                        ]
850
                    ]
851
                ]
852
            ],
853
            'Migrated tool, no change in user ids, new and existing users present in sync' => [
1441 ariadna 854
                'legacydata' => [
1 efrain 855
                    'users' => [
856
                        ['user_id' => '1'],
857
                        ['user_id' => '2'],
858
                    ],
859
                    'consumer_key' => 'CONSUMER_1',
860
                    'tools' => [
861
                        ['secret' => 'toolsecret1'],
862
                        ['secret' => 'toolsecret2'],
863
                    ]
864
                ],
1441 ariadna 865
                'resourceconfig' => null,
866
                'launchdata' => [
867
                    'user' => self::get_mock_launch_users_with_ids(['1'])[0],
1 efrain 868
                    'launch_migration_claim' => [
869
                        'consumer_key' => 'CONSUMER_1',
870
                        'signing_secret' => 'toolsecret1',
871
                        'context_id' => 'd345b',
872
                        'tool_consumer_instance_guid' => '12345-123',
873
                        'resource_link_id' => '4b6fa'
874
                    ],
875
                ],
1441 ariadna 876
                'syncmembers' => [
877
                    self::get_mock_members_with_ids(['1'], null)[0],
878
                    self::get_mock_members_with_ids(['2'], null)[0],
879
                    self::get_mock_members_with_ids(['3'], null)[0],
880
                    self::get_mock_members_with_ids(['4'], null)[0],
1 efrain 881
                ],
882
                'expected' => [
883
                    'enrolments' => [
884
                        '1' => [
885
                            'is_enrolled' => true,
886
                            'is_migrated' => true,
887
                        ],
888
                        '2' => [
889
                            'is_enrolled' => true,
890
                            'is_migrated' => true,
891
                        ],
892
                        '3' => [
893
                            'is_enrolled' => true,
894
                            'is_migrated' => false,
895
                        ],
896
                        '4' => [
897
                            'is_enrolled' => true,
898
                            'is_migrated' => false,
899
                        ]
900
                    ]
901
                ]
902
            ],
903
            'New tool, no launch migration claim, change in user ids, new and existing users present in sync' => [
1441 ariadna 904
                'legacydata' => [
1 efrain 905
                    'users' => [
906
                        ['user_id' => '1'],
907
                        ['user_id' => '2'],
908
                    ],
909
                    'consumer_key' => 'CONSUMER_1',
910
                    'tools' => [
911
                        ['secret' => 'toolsecret1'],
912
                        ['secret' => 'toolsecret2'],
913
                    ]
914
                ],
1441 ariadna 915
                'resourceconfig' => null,
916
                'launchdata' => [
917
                    'user' => self::get_mock_launch_users_with_ids(['1p3_1'])[0],
1 efrain 918
                    'launch_migration_claim' => null,
919
                ],
1441 ariadna 920
                'syncmembers' => [
921
                    self::get_mock_members_with_ids(['1p3_1'], null)[0],
922
                    self::get_mock_members_with_ids(['1p3_2'], null)[0],
923
                    self::get_mock_members_with_ids(['1p3_3'], null)[0],
924
                    self::get_mock_members_with_ids(['1p3_4'], null)[0],
1 efrain 925
                ],
926
                'expected' => [
927
                    'enrolments' => [
928
                        '1p3_1' => [
929
                            'is_enrolled' => true,
930
                            'is_migrated' => false,
931
                        ],
932
                        '1p3_2' => [
933
                            'is_enrolled' => true,
934
                            'is_migrated' => false,
935
                        ],
936
                        '1p3_3' => [
937
                            'is_enrolled' => true,
938
                            'is_migrated' => false,
939
                        ],
940
                        '1p3_4' => [
941
                            'is_enrolled' => true,
942
                            'is_migrated' => false,
943
                        ]
944
                    ]
945
                ]
946
            ],
947
            'New tool, no launch migration claim, no change in user ids, new and existing users present in sync' => [
1441 ariadna 948
                'legacydata' => [
1 efrain 949
                    'users' => [
950
                        ['user_id' => '1'],
951
                        ['user_id' => '2'],
952
                    ],
953
                    'consumer_key' => 'CONSUMER_1',
954
                    'tools' => [
955
                        ['secret' => 'toolsecret1'],
956
                        ['secret' => 'toolsecret2'],
957
                    ]
958
                ],
1441 ariadna 959
                'resourceconfig' => null,
960
                'launchdata' => [
961
                    'user' => self::get_mock_launch_users_with_ids(['1'])[0],
1 efrain 962
                    'launch_migration_claim' => null,
963
                ],
1441 ariadna 964
                'syncmembers' => [
965
                    self::get_mock_members_with_ids(['1'], null)[0],
966
                    self::get_mock_members_with_ids(['2'], null)[0],
967
                    self::get_mock_members_with_ids(['3'], null)[0],
968
                    self::get_mock_members_with_ids(['4'], null)[0],
1 efrain 969
                ],
970
                'expected' => [
971
                    'enrolments' => [
972
                        '1' => [
973
                            'is_enrolled' => true,
974
                            'is_migrated' => false,
975
                        ],
976
                        '2' => [
977
                            'is_enrolled' => true,
978
                            'is_migrated' => false,
979
                        ],
980
                        '3' => [
981
                            'is_enrolled' => true,
982
                            'is_migrated' => false,
983
                        ],
984
                        '4' => [
985
                            'is_enrolled' => true,
986
                            'is_migrated' => false,
987
                        ]
988
                    ]
989
                ]
990
            ],
991
            'New tool, migration only via member sync, no launch claim, new and existing users present in sync' => [
1441 ariadna 992
                'legacydata' => [
1 efrain 993
                    'users' => [
994
                        ['user_id' => '1'],
995
                        ['user_id' => '2'],
996
                    ],
997
                    'consumer_key' => 'CONSUMER_1',
998
                    'tools' => [
999
                        ['secret' => 'toolsecret1'],
1000
                        ['secret' => 'toolsecret2'],
1001
                    ]
1002
                ],
1441 ariadna 1003
                'resourceconfig' => null,
1004
                'launchdata' => [
1005
                    'user' => self::get_mock_launch_users_with_ids(['1p3_1'])[0],
1 efrain 1006
                    'launch_migration_claim' => null,
1007
                ],
1441 ariadna 1008
                'syncmembers' => [
1009
                    self::get_mock_members_with_ids(['1p3_1'], ['1'])[0],
1010
                    self::get_mock_members_with_ids(['1p3_2'], ['2'])[0],
1011
                    self::get_mock_members_with_ids(['1p3_3'], ['3'])[0],
1012
                    self::get_mock_members_with_ids(['1p3_4'], ['4'])[0],
1 efrain 1013
                ],
1014
                'expected' => [
1015
                    'enrolments' => [
1016
                        '1p3_1' => [
1017
                            'is_enrolled' => true,
1018
                            'is_migrated' => false,
1019
                        ],
1020
                        '1p3_2' => [
1021
                            'is_enrolled' => true,
1022
                            'is_migrated' => false,
1023
                        ],
1024
                        '1p3_3' => [
1025
                            'is_enrolled' => true,
1026
                            'is_migrated' => false,
1027
                        ],
1028
                        '1p3_4' => [
1029
                            'is_enrolled' => true,
1030
                            'is_migrated' => false,
1031
                        ]
1032
                    ]
1033
                ]
1034
            ],
1035
            'Default provisioning modes, mixed bag of users and roles' => [
1441 ariadna 1036
                'legacydata' => null,
1037
                'resourceconfig' => [
1 efrain 1038
                    'provisioningmodelearner' => \auth_plugin_lti::PROVISIONING_MODE_AUTO_ONLY,
1039
                    'provisioningmodeinstructor' => \auth_plugin_lti::PROVISIONING_MODE_PROMPT_NEW_EXISTING
1040
                ],
1441 ariadna 1041
                'launchdata' => [
1042
                    'user' => self::get_mock_launch_users_with_ids(['1p3_1'])[0],
1 efrain 1043
                    'launch_migration_claim' => null,
1044
                ],
1441 ariadna 1045
                'syncmembers' => [
1 efrain 1046
                    // This user is just an instructor but is also the user who is already linked, via the launch above.
1441 ariadna 1047
                    self::get_mock_members_with_ids(['1p3_1'], null, true, true, true, false, [
1 efrain 1048
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
1049
                    ])[0],
1050
                    // This user is just a learner.
1441 ariadna 1051
                    self::get_mock_members_with_ids(['1p3_2'], null, true, true, true, false, [
1 efrain 1052
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1053
                    ])[0],
1054
                    // This user is also a learner.
1441 ariadna 1055
                    self::get_mock_members_with_ids(['1p3_3'], null, true, true, true, false, [
1 efrain 1056
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1057
                    ])[0],
1058
                    // This user is both an instructor and a learner.
1441 ariadna 1059
                    self::get_mock_members_with_ids(['1p3_4'], null, true, true, true, false, [
1 efrain 1060
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
1061
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1062
                    ])[0],
1063
                ],
1064
                'expected' => [
1065
                    'enrolments' => [
1066
                        '1p3_1' => [
1067
                            'is_enrolled' => true, // Instructor - enrolled because they are also the launch user (already linked).
1068
                            'is_migrated' => false,
1069
                        ],
1070
                        '1p3_2' => [
1071
                            'is_enrolled' => true, // Learner - enrolled due to 'auto' provisioning mode.
1072
                            'is_migrated' => false,
1073
                        ],
1074
                        '1p3_3' => [
1075
                            'is_enrolled' => true, // Learner - enrolled due to 'auto' provisioning mode.
1076
                            'is_migrated' => false,
1077
                        ],
1078
                        '1p3_4' => [
1079
                            'is_enrolled' => false,  // Both roles - not enrolled due to instructor's 'prompt' provisioning mode.
1080
                            'is_migrated' => false,
1081
                        ]
1082
                    ]
1083
                ]
1084
            ],
1085
            'All automatic provisioning, mixed bag of users and roles' => [
1441 ariadna 1086
                'legacydata' => null,
1087
                'resourceconfig' => [
1 efrain 1088
                    'provisioningmodelearner' => \auth_plugin_lti::PROVISIONING_MODE_AUTO_ONLY,
1089
                    'provisioningmodeinstructor' => \auth_plugin_lti::PROVISIONING_MODE_AUTO_ONLY
1090
                ],
1441 ariadna 1091
                'launchdata' => [
1092
                    'user' => self::get_mock_launch_users_with_ids(['1p3_1'])[0],
1 efrain 1093
                    'launch_migration_claim' => null,
1094
                ],
1441 ariadna 1095
                'syncmembers' => [
1 efrain 1096
                    // This user is just an instructor but is also the user who is already linked, via the launch above.
1441 ariadna 1097
                    self::get_mock_members_with_ids(['1p3_1'], null, true, true, true, false, [
1 efrain 1098
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
1099
                    ])[0],
1100
                    // This user is just a learner.
1441 ariadna 1101
                    self::get_mock_members_with_ids(['1p3_2'], null, true, true, true, false, [
1 efrain 1102
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1103
                    ])[0],
1104
                    // This user is also a learner.
1441 ariadna 1105
                    self::get_mock_members_with_ids(['1p3_3'], null, true, true, true, false, [
1 efrain 1106
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1107
                    ])[0],
1108
                    // This user is both an instructor and a learner.
1441 ariadna 1109
                    self::get_mock_members_with_ids(['1p3_4'], null, true, true, true, false, [
1 efrain 1110
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
1111
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1112
                    ])[0],
1113
                ],
1114
                'expected' => [
1115
                    'enrolments' => [
1116
                        '1p3_1' => [
1117
                            'is_enrolled' => true, // Instructor - enrolled because they are also the launch user (already linked).
1118
                            'is_migrated' => false,
1119
                        ],
1120
                        '1p3_2' => [
1121
                            'is_enrolled' => true, // Learner - enrolled due to 'auto' provisioning mode.
1122
                            'is_migrated' => false,
1123
                        ],
1124
                        '1p3_3' => [
1125
                            'is_enrolled' => true, // Learner - enrolled due to 'auto' provisioning mode.
1126
                            'is_migrated' => false,
1127
                        ],
1128
                        '1p3_4' => [
1129
                            'is_enrolled' => true, // Both roles - enrolled due to instructor's 'auto' provisioning mode.
1130
                            'is_migrated' => false,
1131
                        ]
1132
                    ]
1133
                ]
1134
            ],
1135
            'All prompt provisioning, mixed bag of users and roles' => [
1441 ariadna 1136
                'legacydata' => null,
1137
                'resourceconfig' => [
1 efrain 1138
                    'provisioningmodelearner' => \auth_plugin_lti::PROVISIONING_MODE_PROMPT_NEW_EXISTING,
1139
                    'provisioningmodeinstructor' => \auth_plugin_lti::PROVISIONING_MODE_PROMPT_NEW_EXISTING
1140
                ],
1441 ariadna 1141
                'launchdata' => [
1142
                    'user' => self::get_mock_launch_users_with_ids(['1p3_1'])[0],
1 efrain 1143
                    'launch_migration_claim' => null,
1144
                ],
1441 ariadna 1145
                'syncmembers' => [
1 efrain 1146
                    // This user is just an instructor but is also the user who is already linked, via the launch above.
1441 ariadna 1147
                    self::get_mock_members_with_ids(['1p3_1'], null, true, true, true, false, [
1 efrain 1148
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
1149
                    ])[0],
1150
                    // This user is just a learner.
1441 ariadna 1151
                    self::get_mock_members_with_ids(['1p3_2'], null, true, true, true, false, [
1 efrain 1152
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1153
                    ])[0],
1154
                    // This user is also a learner.
1441 ariadna 1155
                    self::get_mock_members_with_ids(['1p3_3'], null, true, true, true, false, [
1 efrain 1156
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1157
                    ])[0],
1158
                    // This user is both an instructor and a learner.
1441 ariadna 1159
                    self::get_mock_members_with_ids(['1p3_4'], null, true, true, true, false, [
1 efrain 1160
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
1161
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1162
                    ])[0],
1163
                ],
1164
                'expected' => [
1165
                    'enrolments' => [
1166
                        '1p3_1' => [
1167
                            'is_enrolled' => true, // Instructor - enrolled because they are also the launch user (already linked).
1168
                            'is_migrated' => false,
1169
                        ],
1170
                        '1p3_2' => [
1171
                            'is_enrolled' => false, // Learner - not enrolled due to 'prompt' provisioning mode.
1172
                            'is_migrated' => false,
1173
                        ],
1174
                        '1p3_3' => [
1175
                            'is_enrolled' => false, // Learner - not enrolled due to 'prompt' provisioning mode.
1176
                            'is_migrated' => false,
1177
                        ],
1178
                        '1p3_4' => [
1179
                            'is_enrolled' => false, // Both roles - not enrolled due to instructor's 'prompt' provisioning mode.
1180
                            'is_migrated' => false,
1181
                        ]
1182
                    ]
1183
                ]
1184
            ],
1185
            'All automatic provisioning, with legacy data and migration claim, mixed bag of users and roles' => [
1441 ariadna 1186
                'legacydata' => [
1 efrain 1187
                    'users' => [
1188
                        ['user_id' => '2'],
1189
                        ['user_id' => '3'],
1190
                        ['user_id' => '4'],
1191
                        ['user_id' => '5']
1192
                    ],
1193
                    'consumer_key' => 'CONSUMER_1',
1194
                    'tools' => [
1195
                        ['secret' => 'toolsecret1'],
1196
                        ['secret' => 'toolsecret2'],
1197
                    ]
1198
                ],
1441 ariadna 1199
                'resourceconfig' => [
1 efrain 1200
                    'provisioningmodelearner' => \auth_plugin_lti::PROVISIONING_MODE_AUTO_ONLY,
1201
                    'provisioningmodeinstructor' => \auth_plugin_lti::PROVISIONING_MODE_AUTO_ONLY
1202
                ],
1441 ariadna 1203
                'launchdata' => [
1204
                    'user' => self::get_mock_launch_users_with_ids(['1p3_1'])[0],
1 efrain 1205
                    'launch_migration_claim' => [
1206
                        'consumer_key' => 'CONSUMER_1',
1207
                        'signing_secret' => 'toolsecret1',
1208
                        'context_id' => 'd345b',
1209
                        'tool_consumer_instance_guid' => '12345-123',
1210
                        'resource_link_id' => '4b6fa'
1211
                    ],
1212
                ],
1441 ariadna 1213
                'syncmembers' => [
1 efrain 1214
                    // This user is just an instructor but is also the user who is already linked, via the launch above.
1441 ariadna 1215
                    self::get_mock_members_with_ids(['1p3_1'], null, true, true, true, false, [
1 efrain 1216
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
1217
                    ])[0],
1218
                    // This user is just a learner.
1441 ariadna 1219
                    self::get_mock_members_with_ids(['1p3_2'], ['2'], true, true, true, false, [
1 efrain 1220
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1221
                    ])[0],
1222
                    // This user is also a learner.
1441 ariadna 1223
                    self::get_mock_members_with_ids(['1p3_3'], ['3'], true, true, true, false, [
1 efrain 1224
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1225
                    ])[0],
1226
                    // This user is both an instructor and a learner.
1441 ariadna 1227
                    self::get_mock_members_with_ids(['1p3_4'], ['4'], true, true, true, false, [
1 efrain 1228
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
1229
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1230
                    ])[0],
1231
                    // This user is just an instructor who hasn't launched before (unlike the first user here).
1441 ariadna 1232
                    self::get_mock_members_with_ids(['1p3_5'], ['5'], true, true, true, false, [
1 efrain 1233
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
1234
                    ])[0],
1235
                ],
1236
                'expected' => [
1237
                    'enrolments' => [
1238
                        '1p3_1' => [
1239
                            'is_enrolled' => true, // Instructor - enrolled because they are also the launch user (already linked).
1240
                            'is_migrated' => false,
1241
                        ],
1242
                        '1p3_2' => [
1243
                            'is_enrolled' => true, // Learner - enrolled due to 'auto' provisioning mode.
1244
                            'is_migrated' => true,
1245
                        ],
1246
                        '1p3_3' => [
1247
                            'is_enrolled' => true, // Learner - enrolled due to 'auto' provisioning mode.
1248
                            'is_migrated' => true,
1249
                        ],
1250
                        '1p3_4' => [
1251
                            'is_enrolled' => true, // Both roles - enrolled due to instructor's 'auto' provisioning mode.
1252
                            'is_migrated' => true
1253
                        ],
1254
                        '1p3_5' => [
1255
                            'is_enrolled' => true, // Instructor role only - enrolled due to instructor's 'auto' provisioning mode.
1256
                            'is_migrated' => true
1257
                        ]
1258
                    ]
1259
                ]
1260
            ],
1261
        ];
1262
    }
1263
}