2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Unit tests for the core_rating implementation of the Privacy API.
20 * @package core_rating
22 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') ||
die();
29 require_once($CFG->dirroot
. '/rating/lib.php');
31 use \core_rating\privacy\provider
;
32 use \core_privacy\local\request\writer
;
35 * Unit tests for the core_rating implementation of the Privacy API.
37 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 class core_rating_privacy_testcase
extends \core_privacy\tests\provider_testcase
{
43 * Rate something as a user.
46 * @param string $component
47 * @param string $ratingarea
49 * @param \context $context
50 * @param string $score
52 protected function rate_as_user($userid, $component, $ratingarea, $itemid, $context, $score) {
54 $rm = new rating_manager();
55 $ratingoptions = (object) [
56 'component' => $component,
57 'ratingarea' => $ratingarea,
61 // Rate all courses as u1, and the course category too..
62 $ratingoptions->itemid
= $itemid;
63 $ratingoptions->userid
= $userid;
64 $ratingoptions->context
= $context;
65 $rating = new \rating
($ratingoptions);
66 $rating->update_rating($score);
70 * Ensure that the get_sql_join function returns valid SQL which returns the correct list of rated itemids.
72 public function test_get_sql_join() {
74 $this->resetAfterTest();
76 $course1 = $this->getDataGenerator()->create_course();
77 $course2 = $this->getDataGenerator()->create_course();
78 $course3 = $this->getDataGenerator()->create_course();
80 $u1 = $this->getDataGenerator()->create_user();
81 $u2 = $this->getDataGenerator()->create_user();
82 $u3 = $this->getDataGenerator()->create_user();
85 $rm = new rating_manager();
86 $ratingoptions = (object) [
87 'component' => 'core_course',
88 'ratingarea' => 'course',
92 // Rate all courses as u1, and something else in the same context.
93 $this->rate_as_user($u1->id
, 'core_course', 'course', $course1->id
, \context_course
::instance($course1->id
), 25);
94 $this->rate_as_user($u1->id
, 'core_course', 'course', $course2->id
, \context_course
::instance($course2->id
), 50);
95 $this->rate_as_user($u1->id
, 'core_course', 'course', $course3->id
, \context_course
::instance($course3->id
), 75);
96 $this->rate_as_user($u1->id
, 'core_course', 'files', $course3->id
, \context_course
::instance($course3->id
), 99);
98 // Rate course2 as u2, and something else in a different context/component..
99 $this->rate_as_user($u2->id
, 'core_course', 'course', $course2->id
, \context_course
::instance($course2->id
), 90);
100 $this->rate_as_user($u2->id
, 'user', 'user', $u3->id
, \context_user
::instance($u3->id
), 10);
102 // Return any course which the u1 has rated.
103 // u1 rated all three courses.
104 $ratingquery = provider
::get_sql_join('r', 'core_course', 'course', 'c.id', $u1->id
);
105 $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
106 $courses = $DB->get_records_sql($sql, $ratingquery->params
);
108 $this->assertCount(3, $courses);
109 $this->assertTrue(isset($courses[$course1->id
]));
110 $this->assertTrue(isset($courses[$course2->id
]));
111 $this->assertTrue(isset($courses[$course3->id
]));
113 // User u1 rated files in course 3 only.
114 $ratingquery = provider
::get_sql_join('r', 'core_course', 'files', 'c.id', $u1->id
);
115 $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
116 $courses = $DB->get_records_sql($sql, $ratingquery->params
);
118 $this->assertCount(1, $courses);
119 $this->assertFalse(isset($courses[$course1->id
]));
120 $this->assertFalse(isset($courses[$course2->id
]));
121 $this->assertTrue(isset($courses[$course3->id
]));
123 // Return any course which the u2 has rated.
124 // User u2 rated only course 2.
125 $ratingquery = provider
::get_sql_join('r', 'core_course', 'course', 'c.id', $u2->id
);
126 $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
127 $courses = $DB->get_records_sql($sql, $ratingquery->params
);
129 $this->assertCount(1, $courses);
130 $this->assertFalse(isset($courses[$course1->id
]));
131 $this->assertTrue(isset($courses[$course2->id
]));
132 $this->assertFalse(isset($courses[$course3->id
]));
135 $ratingquery = provider
::get_sql_join('r', 'user', 'user', 'u.id', $u2->id
);
136 $sql = "SELECT u.id FROM {user} u {$ratingquery->join} WHERE {$ratingquery->userwhere}";
137 $users = $DB->get_records_sql($sql, $ratingquery->params
);
139 $this->assertCount(1, $users);
140 $this->assertFalse(isset($users[$u1->id
]));
141 $this->assertFalse(isset($users[$u2->id
]));
142 $this->assertTrue(isset($users[$u3->id
]));
144 // Return any course which the u3 has rated.
145 // User u3 did not rate anything.
146 $ratingquery = provider
::get_sql_join('r', 'core_course', 'course', 'c.id', $u3->id
);
147 $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
148 $courses = $DB->get_records_sql($sql, $ratingquery->params
);
150 $this->assertCount(0, $courses);
151 $this->assertFalse(isset($courses[$course1->id
]));
152 $this->assertFalse(isset($courses[$course2->id
]));
153 $this->assertFalse(isset($courses[$course3->id
]));
157 * Ensure that the get_sql_join function returns valid SQL which returns the correct list of rated itemids.
158 * This makes use of the optional inner join argument.
160 public function test_get_sql_join_inner() {
162 $this->resetAfterTest();
164 $course1 = $this->getDataGenerator()->create_course();
165 $course2 = $this->getDataGenerator()->create_course();
166 $course3 = $this->getDataGenerator()->create_course();
168 $u1 = $this->getDataGenerator()->create_user();
169 $u2 = $this->getDataGenerator()->create_user();
170 $u3 = $this->getDataGenerator()->create_user();
173 $rm = new rating_manager();
174 $ratingoptions = (object) [
175 'component' => 'core_course',
176 'ratingarea' => 'course',
180 // Rate all courses as u1, and something else in the same context.
181 $this->rate_as_user($u1->id
, 'core_course', 'course', $course1->id
, \context_course
::instance($course1->id
), 25);
182 $this->rate_as_user($u1->id
, 'core_course', 'course', $course2->id
, \context_course
::instance($course2->id
), 50);
183 $this->rate_as_user($u1->id
, 'core_course', 'course', $course3->id
, \context_course
::instance($course3->id
), 75);
184 $this->rate_as_user($u1->id
, 'core_course', 'files', $course3->id
, \context_course
::instance($course3->id
), 99);
186 // Rate course2 as u2, and something else in a different context/component..
187 $this->rate_as_user($u2->id
, 'core_course', 'course', $course2->id
, \context_course
::instance($course2->id
), 90);
188 $this->rate_as_user($u2->id
, 'user', 'user', $u3->id
, \context_user
::instance($u3->id
), 10);
190 // Return any course which the u1 has rated.
191 // u1 rated all three courses.
192 $ratingquery = provider
::get_sql_join('r', 'core_course', 'course', 'c.id', $u1->id
, true);
193 $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
194 $courses = $DB->get_records_sql($sql, $ratingquery->params
);
196 $this->assertCount(3, $courses);
197 $this->assertTrue(isset($courses[$course1->id
]));
198 $this->assertTrue(isset($courses[$course2->id
]));
199 $this->assertTrue(isset($courses[$course3->id
]));
201 // User u1 rated files in course 3 only.
202 $ratingquery = provider
::get_sql_join('r', 'core_course', 'files', 'c.id', $u1->id
, true);
203 $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
204 $courses = $DB->get_records_sql($sql, $ratingquery->params
);
206 $this->assertCount(1, $courses);
207 $this->assertFalse(isset($courses[$course1->id
]));
208 $this->assertFalse(isset($courses[$course2->id
]));
209 $this->assertTrue(isset($courses[$course3->id
]));
211 // Return any course which the u2 has rated.
212 // User u2 rated only course 2.
213 $ratingquery = provider
::get_sql_join('r', 'core_course', 'course', 'c.id', $u2->id
, true);
214 $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
215 $courses = $DB->get_records_sql($sql, $ratingquery->params
);
217 $this->assertCount(1, $courses);
218 $this->assertFalse(isset($courses[$course1->id
]));
219 $this->assertTrue(isset($courses[$course2->id
]));
220 $this->assertFalse(isset($courses[$course3->id
]));
223 $ratingquery = provider
::get_sql_join('r', 'user', 'user', 'u.id', $u2->id
, true);
224 $sql = "SELECT u.id FROM {user} u {$ratingquery->join} WHERE {$ratingquery->userwhere}";
225 $users = $DB->get_records_sql($sql, $ratingquery->params
);
227 $this->assertCount(1, $users);
228 $this->assertFalse(isset($users[$u1->id
]));
229 $this->assertFalse(isset($users[$u2->id
]));
230 $this->assertTrue(isset($users[$u3->id
]));
232 // Return any course which the u3 has rated.
233 // User u3 did not rate anything.
234 $ratingquery = provider
::get_sql_join('r', 'core_course', 'course', 'c.id', $u3->id
, true);
235 $sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
236 $courses = $DB->get_records_sql($sql, $ratingquery->params
);
238 $this->assertCount(0, $courses);
239 $this->assertFalse(isset($courses[$course1->id
]));
240 $this->assertFalse(isset($courses[$course2->id
]));
241 $this->assertFalse(isset($courses[$course3->id
]));
245 * Ensure that export_area_ratings exports all ratings that a user has made, and all ratings for a users own content.
247 public function test_export_area_ratings() {
249 $this->resetAfterTest();
251 $course1 = $this->getDataGenerator()->create_course();
252 $course2 = $this->getDataGenerator()->create_course();
253 $course3 = $this->getDataGenerator()->create_course();
255 $u1 = $this->getDataGenerator()->create_user();
256 $u2 = $this->getDataGenerator()->create_user();
257 $u3 = $this->getDataGenerator()->create_user();
260 $rm = new rating_manager();
261 $ratingoptions = (object) [
262 'component' => 'core_course',
263 'ratingarea' => 'course',
267 // Rate all courses as u1, and something else in the same context.
268 $this->rate_as_user($u1->id
, 'core_course', 'course', $course1->id
, \context_course
::instance($course1->id
), 25);
269 $this->rate_as_user($u1->id
, 'core_course', 'course', $course2->id
, \context_course
::instance($course2->id
), 50);
270 $this->rate_as_user($u1->id
, 'core_course', 'course', $course3->id
, \context_course
::instance($course3->id
), 75);
271 $this->rate_as_user($u1->id
, 'core_course', 'files', $course3->id
, \context_course
::instance($course3->id
), 99);
272 $this->rate_as_user($u1->id
, 'user', 'user', $u3->id
, \context_user
::instance($u3->id
), 10);
274 // Rate course2 as u2, and something else in a different context/component..
275 $this->rate_as_user($u2->id
, 'core_course', 'course', $course2->id
, \context_course
::instance($course2->id
), 90);
276 $this->rate_as_user($u2->id
, 'user', 'user', $u3->id
, \context_user
::instance($u3->id
), 20);
279 // User 1 rated all three courses, and the core_course, and user 3.
280 // User 1::course1 is stored in [] subcontext.
281 $context = \context_course
::instance($course1->id
);
283 provider
::export_area_ratings($u1->id
, $context, $subcontext, 'core_course', 'course', $course1->id
, true);
285 $writer = writer
::with_context($context);
286 $this->assertTrue($writer->has_any_data());
287 $rating = $writer->get_related_data($subcontext, 'rating');
288 $this->assert_has_rating($u1, 25, $rating);
290 // User 1::course2 is stored in ['foo'] subcontext.
291 $context = \context_course
::instance($course2->id
);
292 $subcontext = ['foo'];
293 provider
::export_area_ratings($u1->id
, $context, $subcontext, 'core_course', 'course', $course2->id
, true);
295 $writer = writer
::with_context($context);
296 $this->assertTrue($writer->has_any_data());
297 $result = $writer->get_related_data($subcontext, 'rating');
298 $this->assertCount(1, $result);
299 $this->assert_has_rating($u1, 50, $result);
301 // User 1::course3 is stored in ['foo'] subcontext.
302 $context = \context_course
::instance($course3->id
);
303 $subcontext = ['foo'];
304 provider
::export_area_ratings($u1->id
, $context, $subcontext, 'core_course', 'course', $course3->id
, true);
306 $writer = writer
::with_context($context);
307 $this->assertTrue($writer->has_any_data());
308 $result = $writer->get_related_data($subcontext, 'rating');
309 $this->assertCount(1, $result);
310 $this->assert_has_rating($u1, 75, $result);
312 // User 1::course3::files is stored in ['foo', 'files'] subcontext.
313 $context = \context_course
::instance($course3->id
);
314 $subcontext = ['foo', 'files'];
315 provider
::export_area_ratings($u1->id
, $context, $subcontext, 'core_course', 'files', $course3->id
, true);
317 $writer = writer
::with_context($context);
318 $this->assertTrue($writer->has_any_data());
319 $result = $writer->get_related_data($subcontext, 'rating');
320 $this->assertCount(1, $result);
321 $this->assert_has_rating($u1, 99, $result);
323 // Both users 1 and 2 rated user 3.
324 // Exporting the data for user 3 should include both of those ratings.
325 $context = \context_user
::instance($u3->id
);
326 $subcontext = ['user'];
327 provider
::export_area_ratings($u3->id
, $context, $subcontext, 'user', 'user', $u3->id
, false);
329 $writer = writer
::with_context($context);
330 $this->assertTrue($writer->has_any_data());
331 $result = $writer->get_related_data($subcontext, 'rating');
332 $this->assertCount(2, $result);
333 $this->assert_has_rating($u1, 10, $result);
334 $this->assert_has_rating($u2, 20, $result);
338 * Test delete_ratings() method.
340 public function test_delete_ratings() {
342 $this->resetAfterTest();
344 $course1 = $this->getDataGenerator()->create_course();
345 $course2 = $this->getDataGenerator()->create_course();
346 $course3 = $this->getDataGenerator()->create_course();
348 $u1 = $this->getDataGenerator()->create_user();
349 $u2 = $this->getDataGenerator()->create_user();
350 $u3 = $this->getDataGenerator()->create_user();
352 // Rate all courses as u1, and something else in the same context.
353 $this->rate_as_user($u1->id
, 'core_course', 'course', $course1->id
, \context_course
::instance($course1->id
), 25);
354 $this->rate_as_user($u1->id
, 'core_course', 'course', $course2->id
, \context_course
::instance($course2->id
), 50);
355 $this->rate_as_user($u1->id
, 'core_course', 'course', $course3->id
, \context_course
::instance($course3->id
), 75);
356 $this->rate_as_user($u1->id
, 'core_course', 'files', $course3->id
, \context_course
::instance($course3->id
), 99);
357 $this->rate_as_user($u1->id
, 'core_user', 'user', $u3->id
, \context_user
::instance($u3->id
), 10);
359 // Rate course2 as u2, and something else in a different context/component..
360 $this->rate_as_user($u2->id
, 'core_course', 'course', $course2->id
, \context_course
::instance($course2->id
), 90);
361 $this->rate_as_user($u2->id
, 'core_user', 'user', $u3->id
, \context_user
::instance($u3->id
), 20);
363 // Delete all ratings in course1.
364 $expectedratingscount = $DB->count_records('rating');
365 core_rating\privacy\provider
::delete_ratings(\context_course
::instance($course1->id
));
366 $expectedratingscount -= 1;
367 $this->assertEquals($expectedratingscount, $DB->count_records('rating'));
369 // Delete ratings in course2 specifying wrong component.
370 core_rating\privacy\provider
::delete_ratings(\context_course
::instance($course2->id
), 'other_component');
371 $this->assertEquals($expectedratingscount, $DB->count_records('rating'));
373 // Delete ratings in course2 specifying correct component.
374 core_rating\privacy\provider
::delete_ratings(\context_course
::instance($course2->id
), 'core_course');
375 $expectedratingscount -= 2;
376 $this->assertEquals($expectedratingscount, $DB->count_records('rating'));
378 // Delete user ratings specifyng all attributes.
379 core_rating\privacy\provider
::delete_ratings(\context_user
::instance($u3->id
), 'core_user', 'user', $u3->id
);
380 $expectedratingscount -= 2;
381 $this->assertEquals($expectedratingscount, $DB->count_records('rating'));
385 * Test delete_ratings_select() method.
387 public function test_delete_ratings_select() {
389 $this->resetAfterTest();
391 $course1 = $this->getDataGenerator()->create_course();
392 $course2 = $this->getDataGenerator()->create_course();
393 $course3 = $this->getDataGenerator()->create_course();
395 $u1 = $this->getDataGenerator()->create_user();
396 $u2 = $this->getDataGenerator()->create_user();
397 $u3 = $this->getDataGenerator()->create_user();
399 // Rate all courses as u1, and something else in the same context.
400 $this->rate_as_user($u1->id
, 'core_course', 'course', $course1->id
, \context_course
::instance($course1->id
), 25);
401 $this->rate_as_user($u1->id
, 'core_course', 'course', $course2->id
, \context_course
::instance($course2->id
), 50);
402 $this->rate_as_user($u1->id
, 'core_course', 'course', $course3->id
, \context_course
::instance($course3->id
), 75);
403 $this->rate_as_user($u1->id
, 'core_course', 'files', $course3->id
, \context_course
::instance($course3->id
), 99);
404 $this->rate_as_user($u1->id
, 'core_user', 'user', $u3->id
, \context_user
::instance($u3->id
), 10);
406 // Rate course2 as u2, and something else in a different context/component..
407 $this->rate_as_user($u2->id
, 'core_course', 'course', $course2->id
, \context_course
::instance($course2->id
), 90);
408 $this->rate_as_user($u2->id
, 'core_user', 'user', $u3->id
, \context_user
::instance($u3->id
), 20);
410 // Delete ratings in course1.
411 list($sql, $params) = $DB->get_in_or_equal([$course1->id
, $course2->id
], SQL_PARAMS_NAMED
);
412 $expectedratingscount = $DB->count_records('rating');
413 core_rating\privacy\provider
::delete_ratings_select(\context_course
::instance($course1->id
),
414 'core_course', 'course', $sql, $params);
415 $expectedratingscount -= 1;
416 $this->assertEquals($expectedratingscount, $DB->count_records('rating'));
420 * Assert that a user has the correct rating.
422 * @param \stdClass $author The user with the rating
423 * @param int $score The rating that was given
424 * @param \stdClass[] The ratings which were found
426 protected function assert_has_rating($author, $score, $actual) {
428 foreach ($actual as $rating) {
429 if ($author->id
== $rating->author
) {
431 $this->assertEquals($score, $rating->rating
);
434 $this->assertTrue($found);