MDL-32386 completion: Fix incorrect method parameters
[moodle.git] / lib / tests / completionlib_test.php
CommitLineData
4059b645
PS
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/**
18 * Completion tests
19 *
20 * @package core_completion
21 * @category phpunit
22 * @copyright 2008 Sam Marshall
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
28global $CFG;
29require_once($CFG->libdir.'/completionlib.php');
30
31
32class completionlib_testcase extends basic_testcase {
33
34 var $realdb, $realcfg, $realsession, $realuser;
35
36 protected function setUp() {
37 global $DB, $CFG, $SESSION, $USER;
38 parent::setUp();
39
40 $this->realdb = $DB;
41 $this->realcfg = $CFG;
42 $this->realsession = $SESSION;
43 $this->prevuser = $USER;
44
45 $DB = $this->getMock(get_class($DB));
46 $CFG = clone($this->realcfg);
47 $CFG->prefix = 'test_';
48 $CFG->enablecompletion = COMPLETION_ENABLED;
49 $SESSION = new stdClass();
50 $USER = (object)array('id' =>314159);
51 }
52
53 protected function tearDown() {
54 global $DB,$CFG,$SESSION,$USER;
55 $DB = $this->realdb;
56 $CFG = $this->realcfg;
57 $SESSION = $this->realsession;
58 $USER = $this->prevuser;
59
60 parent::tearDown();
61 }
62
63 function test_is_enabled() {
64 global $CFG;
65
66 // Config alone
67 $CFG->enablecompletion = COMPLETION_DISABLED;
68 $this->assertEquals(COMPLETION_DISABLED, completion_info::is_enabled_for_site());
69 $CFG->enablecompletion = COMPLETION_ENABLED;
70 $this->assertEquals(COMPLETION_ENABLED, completion_info::is_enabled_for_site());
71
72 // Course
73 $course = (object)array('id' =>13);
74 $c = new completion_info($course);
75 $course->enablecompletion = COMPLETION_DISABLED;
76 $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled());
77 $course->enablecompletion = COMPLETION_ENABLED;
78 $this->assertEquals(COMPLETION_ENABLED, $c->is_enabled());
79 $CFG->enablecompletion = COMPLETION_DISABLED;
80 $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled());
81
82 // Course and CM
83 $cm = new stdClass();
84 $cm->completion = COMPLETION_TRACKING_MANUAL;
85 $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled($cm));
86 $CFG->enablecompletion = COMPLETION_ENABLED;
87 $course->enablecompletion = COMPLETION_DISABLED;
88 $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled($cm));
89 $course->enablecompletion = COMPLETION_ENABLED;
90 $this->assertEquals(COMPLETION_TRACKING_MANUAL, $c->is_enabled($cm));
91 $cm->completion = COMPLETION_TRACKING_NONE;
92 $this->assertEquals(COMPLETION_TRACKING_NONE, $c->is_enabled($cm));
93 $cm->completion = COMPLETION_TRACKING_AUTOMATIC;
94 $this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $c->is_enabled($cm));
95 }
96
97 function test_update_state() {
98
99 $c = $this->getMock('completion_info', array('is_enabled','get_data','internal_get_state','internal_set_data'), array((object)array('id'=>42)));
100 $cm = (object)array('id'=>13, 'course'=>42);
101
102 // Not enabled, should do nothing
103 $c->expects($this->at(0))
104 ->method('is_enabled')
105 ->with($cm)
106 ->will($this->returnValue(false));
107 $c->update_state($cm);
108
109 // Enabled, but current state is same as possible result, do nothing
110 $current = (object)array('completionstate'=>COMPLETION_COMPLETE);
111 $c->expects($this->at(0))
112 ->method('is_enabled')
113 ->with($cm)
114 ->will($this->returnValue(true));
115 $c->expects($this->at(1))
116 ->method('get_data')
117 ->with($cm, false, 0)
118 ->will($this->returnValue($current));
119 $c->update_state($cm, COMPLETION_COMPLETE);
120
121 // Enabled, but current state is a specific one and new state is just
122 // complete, so do nothing
123 $current->completionstate = COMPLETION_COMPLETE_PASS;
124 $c->expects($this->at(0))
125 ->method('is_enabled')
126 ->with($cm)
127 ->will($this->returnValue(true));
128 $c->expects($this->at(1))
129 ->method('get_data')
130 ->with($cm, false, 0)
131 ->will($this->returnValue($current));
132 $c->update_state($cm, COMPLETION_COMPLETE);
133
134 // Manual, change state (no change)
135 $cm = (object)array('id'=>13,'course'=>42, 'completion'=>COMPLETION_TRACKING_MANUAL);
136 $current->completionstate=COMPLETION_COMPLETE;
137 $c->expects($this->at(0))
138 ->method('is_enabled')
139 ->with($cm)
140 ->will($this->returnValue(true));
141 $c->expects($this->at(1))
142 ->method('get_data')
143 ->with($cm, false, 0)
144 ->will($this->returnValue($current));
145 $c->update_state($cm, COMPLETION_COMPLETE);
146
147 // Manual, change state (change)
148 $c->expects($this->at(0))
149 ->method('is_enabled')
150 ->with($cm)
151 ->will($this->returnValue(true));
152 $c->expects($this->at(1))
153 ->method('get_data')
154 ->with($cm, false, 0)
155 ->will($this->returnValue($current));
156 $changed = clone($current);
157 $changed->timemodified = time();
158 $changed->completionstate = COMPLETION_INCOMPLETE;
159 $c->expects($this->at(2))
160 ->method('internal_set_data')
161 ->with($cm, $changed);
162 $c->update_state($cm, COMPLETION_INCOMPLETE);
163
164 // Auto, change state
165 $cm = (object)array('id'=>13,'course'=>42, 'completion'=>COMPLETION_TRACKING_AUTOMATIC);
166 $current = (object)array('completionstate'=>COMPLETION_COMPLETE);
167 $c->expects($this->at(0))
168 ->method('is_enabled')
169 ->with($cm)
170 ->will($this->returnValue(true));
171 $c->expects($this->at(1))
172 ->method('get_data')
173 ->with($cm, false, 0)
174 ->will($this->returnValue($current));
175 $c->expects($this->at(2))
176 ->method('internal_get_state')
177 ->will($this->returnValue(COMPLETION_COMPLETE_PASS));
178 $changed = clone($current);
179 $changed->timemodified = time();
180 $changed->completionstate = COMPLETION_COMPLETE_PASS;
181 $c->expects($this->at(3))
182 ->method('internal_set_data')
183 ->with($cm, $changed);
184 $c->update_state($cm, COMPLETION_COMPLETE_PASS);
185 }
186
187 function test_internal_get_state() {
188 global $DB;
189
190 $c = $this->getMock('completion_info', array('internal_get_grade_state'), array((object)array('id'=>42)));
191 $cm = (object)array('id'=>13, 'course'=>42, 'completiongradeitemnumber'=>null);
192
193 // If view is required, but they haven't viewed it yet
194 $cm->completionview = COMPLETION_VIEW_REQUIRED;
195 $current = (object)array('viewed'=>COMPLETION_NOT_VIEWED);
196 $this->assertEquals(COMPLETION_INCOMPLETE, $c->internal_get_state($cm, 123, $current));
197
198 // OK set view not required
199 $cm->completionview = COMPLETION_VIEW_NOT_REQUIRED;
200
201 // Test not getting module name
202 $cm->modname='label';
203 $this->assertEquals(COMPLETION_COMPLETE, $c->internal_get_state($cm, 123, $current));
204
205 // Test getting module name
206 $cm->module = 13;
207 unset($cm->modname);
208 /** @var $DB PHPUnit_Framework_MockObject_MockObject */
209 $DB->expects($this->once())
210 ->method('get_field')
211 ->with('modules', 'name', array('id'=>13))
212 ->will($this->returnValue('lable'));
213 $this->assertEquals(COMPLETION_COMPLETE, $c->internal_get_state($cm, 123, $current));
214
215 // Note: This function is not fully tested (including kind of the main
216 // part) because:
217 // * the grade_item/grade_grade calls are static and can't be mocked
218 // * the plugin_supports call is static and can't be mocked
219 }
220
221 function test_set_module_viewed() {
222
223 $c = $this->getMock('completion_info',
224 array('delete_all_state', 'get_tracked_users', 'update_state', 'internal_get_grade_state', 'is_enabled', 'get_data', 'internal_get_state', 'internal_set_data'),
225 array((object)array('id'=>42)));
226 $cm = (object)array('id'=>13, 'course'=>42);
227
228 // Not tracking completion, should do nothing
229 $cm->completionview = COMPLETION_VIEW_NOT_REQUIRED;
230 $c->set_module_viewed($cm);
231
232 // Tracking completion but completion is disabled, should do nothing
233 $cm->completionview = COMPLETION_VIEW_REQUIRED;
234 $c->expects($this->at(0))
235 ->method('is_enabled')
236 ->with($cm)
237 ->will($this->returnValue(false));
238 $c->set_module_viewed($cm);
239
240 // Now it's enabled, we expect it to get data. If data already has
241 // viewed, still do nothing
242 $c->expects($this->at(0))
243 ->method('is_enabled')
244 ->with($cm)
245 ->will($this->returnValue(true));
246 $c->expects($this->at(1))
247 ->method('get_data')
248 ->with($cm, 0)
249 ->will($this->returnValue((object)array('viewed'=>COMPLETION_VIEWED)));
250 $c->set_module_viewed($cm);
251
252 // OK finally one that hasn't been viewed, now it should set it viewed
253 // and update state
254 $c->expects($this->at(0))
255 ->method('is_enabled')
256 ->with($cm)
257 ->will($this->returnValue(true));
258 $c->expects($this->at(1))
259 ->method('get_data')
1caeb4b4 260 ->with($cm, false, 1337)
4059b645
PS
261 ->will($this->returnValue((object)array('viewed'=>COMPLETION_NOT_VIEWED)));
262 $c->expects($this->at(2))
263 ->method('internal_set_data')
264 ->with($cm, (object)array('viewed'=>COMPLETION_VIEWED));
265 $c->expects($this->at(3))
266 ->method('update_state')
267 ->with($cm, COMPLETION_COMPLETE, 1337);
268 $c->set_module_viewed($cm, 1337);
269 }
270
271 function test_count_user_data() {
272 global $DB;
273
274 $course = (object)array('id'=>13);
275 $cm = (object)array('id'=>42);
276
277 /** @var $DB PHPUnit_Framework_MockObject_MockObject */
278 $DB->expects($this->at(0))
279 ->method('get_field_sql')
280 ->will($this->returnValue(666));
281
282/*
283 $DB->expectOnce('get_field_sql',array(new IgnoreWhitespaceExpectation("SELECT
284 COUNT(1)
285FROM
286 {course_modules_completion}
287WHERE
288 coursemoduleid=? AND completionstate<>0"),array(42)));
289*/
290
291 $c = new completion_info($course);
292 $this->assertEquals(666, $c->count_user_data($cm));
293 }
294
295 function test_delete_all_state() {
296 global $DB, $SESSION;
297
298 $course = (object)array('id'=>13);
299 $cm = (object)array('id'=>42,'course'=>13);
300 $c = new completion_info($course);
301
302 // Check it works ok without data in session
303 /** @var $DB PHPUnit_Framework_MockObject_MockObject */
304 $DB->expects($this->at(0))
305 ->method('delete_records')
306 ->with('course_modules_completion', array('coursemoduleid'=>42))
307 ->will($this->returnValue(true));
308 $c->delete_all_state($cm);
309
310 // Build up a session to check it deletes the right bits from it
311 // (and not other bits)
312 $SESSION->completioncache=array();
313 $SESSION->completioncache[13]=array();
314 $SESSION->completioncache[13][42]='foo';
315 $SESSION->completioncache[13][43]='foo';
316 $SESSION->completioncache[14]=array();
317 $SESSION->completioncache[14][42]='foo';
318 $DB->expects($this->at(0))
319 ->method('delete_records')
320 ->with('course_modules_completion', array('coursemoduleid'=>42))
321 ->will($this->returnValue(true));
322 $c->delete_all_state($cm);
323 $this->assertEquals(array(13=>array(43=>'foo'), 14=>array(42=>'foo')), $SESSION->completioncache);
324 }
325
326 function test_reset_all_state() {
327 global $DB;
328
329 $c = $this->getMock('completion_info',
330 array('delete_all_state', 'get_tracked_users','update_state', 'internal_get_grade_state', 'is_enabled', 'get_data', 'internal_get_state', 'internal_set_data'),
331 array((object)array('id'=>42)));
332
333 $cm = (object)array('id'=>13, 'course'=>42, 'completion'=>COMPLETION_TRACKING_AUTOMATIC);
334
335 /** @var $DB PHPUnit_Framework_MockObject_MockObject */
336 $DB->expects($this->at(0))
337 ->method('get_recordset')
338 ->will($this->returnValue(
339 new completion_test_fake_recordset(array((object)array('id'=>1, 'userid'=>100),(object)array('id'=>2, 'userid'=>101)))));
340
341 $c->expects($this->at(0))
342 ->method('delete_all_state')
343 ->with($cm);
344
345 $c->expects($this->at(1))
346 ->method('get_tracked_users')
347 ->will($this->returnValue(array(
348 (object)array('id'=>100,'firstname'=>'Woot','lastname'=>'Plugh'),
349 (object)array('id'=>201,'firstname'=>'Vroom','lastname'=>'Xyzzy'))));
350
351 $c->expects($this->at(2))
352 ->method('update_state')
353 ->with($cm,COMPLETION_UNKNOWN, 100);
354 $c->expects($this->at(3))
355 ->method('update_state')
356 ->with($cm,COMPLETION_UNKNOWN, 101);
357 $c->expects($this->at(4))
358 ->method('update_state')
359 ->with($cm,COMPLETION_UNKNOWN, 201);
360
361 $c->reset_all_state($cm);
362 }
363
364 function test_get_data() {
365 global $DB, $SESSION;
366
367 $c = new completion_info((object)array('id'=>42));
368 $cm = (object)array('id'=>13, 'course'=>42);
369
370 // 1. Not current user, record exists
371 $sillyrecord = (object)array('frog'=>'kermit');
372
373 /** @var $DB PHPUnit_Framework_MockObject_MockObject */
374 $DB->expects($this->at(0))
375 ->method('get_record')
376 ->with('course_modules_completion', array('coursemoduleid'=>13,'userid'=>123))
377 ->will($this->returnValue($sillyrecord));
378 $result = $c->get_data($cm,false,123);
379 $this->assertEquals($sillyrecord, $result);
380 $this->assertTrue(empty($SESSION->completioncache));
381
382 // 2. Not current user, default record, wholecourse (ignored)
383 $DB->expects($this->at(0))
384 ->method('get_record')
385 ->with('course_modules_completion', array('coursemoduleid'=>13,'userid'=>123))
386 ->will($this->returnValue(false));
387 $result=$c->get_data($cm,true,123);
388 $this->assertEquals((object)array(
389 'id'=>'0','coursemoduleid'=>13,'userid'=>123,'completionstate'=>0,
390 'viewed'=>0,'timemodified'=>0),$result);
391 $this->assertTrue(empty($SESSION->completioncache));
392
393 // 3. Current user, single record, not from cache
394 $DB->expects($this->at(0))
395 ->method('get_record')
396 ->with('course_modules_completion', array('coursemoduleid'=>13,'userid'=>314159))
397 ->will($this->returnValue($sillyrecord));
398 $result = $c->get_data($cm);
399 $this->assertEquals($sillyrecord, $result);
400 $this->assertEquals($sillyrecord, $SESSION->completioncache[42][13]);
401 // When checking time(), allow for second overlaps
402 $this->assertTrue(time()-$SESSION->completioncache[42]['updated']<2);
403
404 // 4. Current user, 'whole course', but from cache
405 $result = $c->get_data($cm, true);
406 $this->assertEquals($sillyrecord, $result);
407
408 // 5. Current user, single record, cache expired
409 $SESSION->completioncache[42]['updated']=37; // Quite a long time ago
410 $now = time();
411 $SESSION->completioncache[17]['updated']=$now;
412 $SESSION->completioncache[39]['updated']=72; // Also a long time ago
413 $DB->expects($this->at(0))
414 ->method('get_record')
415 ->with('course_modules_completion', array('coursemoduleid'=>13,'userid'=>314159))
416 ->will($this->returnValue($sillyrecord));
417 $result = $c->get_data($cm, false);
418 $this->assertEquals($sillyrecord, $result);
419
420 // Check that updated value is right, then fudge it to make next compare
421 // work
422 $this->assertTrue(time()-$SESSION->completioncache[42]['updated']<2);
423 $SESSION->completioncache[42]['updated']=$now;
424 // Check things got expired from cache
425 $this->assertEquals(array(42=>array(13=>$sillyrecord, 'updated'=>$now), 17=>array('updated'=>$now)), $SESSION->completioncache);
426
427 // 6. Current user, 'whole course' and record not in cache
428 unset($SESSION->completioncache);
429
430 // Scenario: Completion data exists for one CMid
431 $basicrecord = (object)array('coursemoduleid'=>13);
432 $DB->expects($this->at(0))
433 ->method('get_records_sql')
434 ->will($this->returnValue(array('1'=>$basicrecord)));
435
436/*
437 $DB->expectAt(0,'get_records_sql',array(new IgnoreWhitespaceExpectation("
438SELECT
439 cmc.*
440FROM
441 {course_modules} cm
442 INNER JOIN {course_modules_completion} cmc ON cmc.coursemoduleid=cm.id
443WHERE
444 cm.course=? AND cmc.userid=?"),array(42,314159)));
445*/
446 // There are two CMids in total, the one we had data for and another one
447 $modinfo = new stdClass();
448 $modinfo->cms = array((object)array('id'=>13), (object)array('id'=>14));
449 $result = $c->get_data($cm, true, 0, $modinfo);
450
451 // Check result
452 $this->assertEquals($basicrecord, $result);
453
454 // Check the cache contents
455 $this->assertTrue(time()-$SESSION->completioncache[42]['updated']<2);
456 $SESSION->completioncache[42]['updated'] = $now;
457 $this->assertEquals(array(42=>array(13=>$basicrecord, 14=>(object)array(
458 'id'=>'0', 'coursemoduleid'=>14, 'userid'=>314159, 'completionstate'=>0,
459 'viewed'=>0, 'timemodified'=>0), 'updated'=>$now)), $SESSION->completioncache);
460 }
461
462 function test_internal_set_data() {
463 global $DB, $SESSION;
464
465 $cm = (object)array('course' => 42,'id' => 13);
466 $c = new completion_info((object)array('id' => 42));
467
468 // 1) Test with new data
469 $data = (object)array('id'=>0, 'userid' => 314159, 'coursemoduleid' => 99);
470 $DB->expects($this->at(0))
471 ->method('start_delegated_transaction')
472 ->will($this->returnValue($this->getMock('moodle_transaction', array(), array($DB))));
473
474 $DB->expects($this->at(1))
475 ->method('get_field')
476 ->with('course_modules_completion', 'id', array('coursemoduleid'=>99, 'userid'=>314159))
477 ->will($this->returnValue(false));
478
479 $DB->expects($this->at(2))
480 ->method('insert_record')
481 ->will($this->returnValue(4));
482
483 $c->internal_set_data($cm, $data);
484 $this->assertEquals(4, $data->id);
485 $this->assertEquals(array(42 => array(13 => $data)), $SESSION->completioncache);
486
487 // 2) Test with existing data and for different user (not cached)
488 unset($SESSION->completioncache);
489 $d2 = (object)array('id' => 7, 'userid' => 17, 'coursemoduleid' => 66);
490 $DB->expects($this->at(0))
491 ->method('start_delegated_transaction')
492 ->will($this->returnValue($this->getMock('moodle_transaction', array(), array($DB))));
493 $DB->expects($this->at(1))
494 ->method('update_record')
495 ->with('course_modules_completion', $d2);
496 $c->internal_set_data($cm, $d2);
497 $this->assertFalse(isset($SESSION->completioncache));
498
499 // 3) Test where it THINKS the data is new (from cache) but actually
500 // in the database it has been set since
501 // 1) Test with new data
502 $data = (object)array('id'=>0, 'userid' => 314159, 'coursemoduleid' => 99);
503 $d3 = (object)array('id' => 13, 'userid' => 314159, 'coursemoduleid' => 99);
504 $DB->expects($this->at(0))
505 ->method('start_delegated_transaction')
506 ->will($this->returnValue($this->getMock('moodle_transaction', array(), array($DB))));
507 $DB->expects($this->at(1))
508 ->method('get_field')
509 ->with('course_modules_completion', 'id', array('coursemoduleid' => 99, 'userid' => 314159))
510 ->will($this->returnValue(13));
511 $DB->expects($this->at(2))
512 ->method('update_record')
513 ->with('course_modules_completion', $d3);
514 $c->internal_set_data($cm, $data);
515 }
516
517 function test_get_activities() {
518 global $DB;
519
520 $c = new completion_info((object)array('id'=>42));
521
522 // Try with no activities
523 $DB->expects($this->at(0))
524 ->method('get_records_select')
525 ->with('course_modules', 'course=42 AND completion<>'.COMPLETION_TRACKING_NONE)
526 ->will($this->returnValue(array()));
527 $result = $c->get_activities();
528 $this->assertEquals(array(), $result);
529
530 // Try with an activity (need to fake up modinfo for it as well)
531 $DB->expects($this->at(0))
532 ->method('get_records_select')
533 ->with('course_modules', 'course=42 AND completion<>'.COMPLETION_TRACKING_NONE)
534 ->will($this->returnValue(array(13=>(object)array('id'=>13))));
535 $modinfo = new stdClass;
536 $modinfo->sections = array(array(1, 2, 3), array(12, 13, 14));
537 $modinfo->cms[13] = (object)array('modname'=>'frog', 'name'=>'kermit');
538 $result = $c->get_activities($modinfo);
539 $this->assertEquals(array(13=>(object)array('id'=>13, 'modname'=>'frog', 'name'=>'kermit')), $result);
540 }
541
542 // get_tracked_users() cannot easily be tested because it uses
543 // get_role_users, so skipping that
544
545 function test_get_progress_all() {
546 global $DB;
547
548 $c = $this->getMock('completion_info',
549 array('delete_all_state', 'get_tracked_users', 'update_state', 'internal_get_grade_state', 'is_enabled', 'get_data', 'internal_get_state', 'internal_set_data'),
550 array((object)array('id'=>42)));
551
552 // 1) Basic usage
553 $c->expects($this->at(0))
554 ->method('get_tracked_users')
555 ->with(false, array(), 0, '', '', '', null)
556 ->will($this->returnValue(array(
557 (object)array('id'=>100, 'firstname'=>'Woot', 'lastname'=>'Plugh'),
558 (object)array('id'=>201, 'firstname'=>'Vroom', 'lastname'=>'Xyzzy'))));
559 $DB->expects($this->at(0))
560 ->method('get_in_or_equal')
561 ->with(array(100, 201))
562 ->will($this->returnValue(array(' IN (100, 201)', array())));
563 $progress1 = (object)array('userid'=>100, 'coursemoduleid'=>13);
564 $progress2 = (object)array('userid'=>201, 'coursemoduleid'=>14);
565 $DB->expects($this->at(1))
566 ->method('get_recordset_sql')
567 ->will($this->returnValue(new completion_test_fake_recordset(array($progress1, $progress2))));
568
569/*
570 $DB->expectAt(0, 'get_recordset_sql', array(new IgnoreWhitespaceExpectation("
571SELECT
572 cmc.*
573FROM
574 {course_modules} cm
575 INNER JOIN {course_modules_completion} cmc ON cm.id = cmc.coursemoduleid
576WHERE
577 cm.course = ? AND cmc.userid IN (100, 201)"), array(42)));
578*/
579
580 $this->assertEquals(array(
581 100 => (object)array('id'=>100, 'firstname'=>'Woot', 'lastname'=>'Plugh',
582 'progress'=>array(13=>$progress1)),
583 201 => (object)array('id'=>201, 'firstname'=>'Vroom', 'lastname'=>'Xyzzy',
584 'progress'=>array(14=>$progress2)),
585 ), $c->get_progress_all(false));
586
587 // 2) With more than 1, 000 results
588 $tracked = array();
589 $ids = array();
590 $progress = array();
591 for($i = 100;$i<2000;$i++) {
592 $tracked[] = (object)array('id'=>$i, 'firstname'=>'frog', 'lastname'=>$i);
593 $ids[] = $i;
594 $progress[] = (object)array('userid'=>$i, 'coursemoduleid'=>13);
595 $progress[] = (object)array('userid'=>$i, 'coursemoduleid'=>14);
596 }
597 $c->expects($this->at(0))
598 ->method('get_tracked_users')
599 ->with(true, 3, 0, '', '', '', null)
600 ->will($this->returnValue($tracked));
601 $DB->expects($this->at(0))
602 ->method('get_in_or_equal')
603 ->with(array_slice($ids, 0, 1000))
604 ->will($this->returnValue(array(' IN whatever', array())));
605 $DB->expects($this->at(1))
606 ->method('get_recordset_sql')
607 ->will($this->returnValue(new completion_test_fake_recordset(array_slice($progress, 0, 1000))));
608
609/*
610 $DB->expectAt(1, 'get_recordset_sql', array(new IgnoreWhitespaceExpectation("
611SELECT
612 cmc.*
613FROM
614 {course_modules} cm
615 INNER JOIN {course_modules_completion} cmc ON cm.id = cmc.coursemoduleid
616WHERE
617 cm.course = ? AND cmc.userid IN whatever"), array(42)));
618*/
619
620 $DB->expects($this->at(2))
621 ->method('get_in_or_equal')
622 ->with(array_slice($ids, 1000))
623 ->will($this->returnValue(array(' IN whatever2', array())));
624 $DB->expects($this->at(3))
625 ->method('get_recordset_sql')
626 ->will($this->returnValue(new completion_test_fake_recordset(array_slice($progress, 1000))));
627
628 $result = $c->get_progress_all(true, 3);
629 $resultok = true;
630 $resultok = $resultok && ($ids == array_keys($result));
631
632 foreach($result as $userid => $data) {
633 $resultok = $resultok && $data->firstname == 'frog';
634 $resultok = $resultok && $data->lastname == $userid;
635 $resultok = $resultok && $data->id == $userid;
636 $cms = $data->progress;
637 $resultok = $resultok && (array(13, 14) == array_keys($cms));
638 $resultok = $resultok && ((object)array('userid'=>$userid, 'coursemoduleid'=>13) == $cms[13]);
639 $resultok = $resultok && ((object)array('userid'=>$userid, 'coursemoduleid'=>14) == $cms[14]);
640 }
641 $this->assertTrue($resultok);
642 }
643
644 function test_inform_grade_changed() {
645 $c = $this->getMock('completion_info',
646 array('delete_all_state', 'get_tracked_users', 'update_state', 'internal_get_grade_state', 'is_enabled', 'get_data', 'internal_get_state', 'internal_set_data'),
647 array((object)array('id'=>42)));
648
649 $cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>null);
650 $item = (object)array('itemnumber'=>3, 'gradepass'=>1, 'hidden'=>0);
651 $grade = (object)array('userid'=>31337, 'finalgrade'=>0, 'rawgrade'=>0);
652
653 // Not enabled (should do nothing)
654 $c->expects($this->at(0))
655 ->method('is_enabled')
656 ->with($cm)
657 ->will($this->returnValue(false));
658 $c->inform_grade_changed($cm, $item, $grade, false);
659
660 // Enabled but still no grade completion required, should still do nothing
661 $c->expects($this->at(0))
662 ->method('is_enabled')
663 ->with($cm)
664 ->will($this->returnValue(true));
665 $c->inform_grade_changed($cm, $item, $grade, false);
666
667 // Enabled and completion required but item number is wrong, does nothing
668 $cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>7);
669 $c->expects($this->at(0))
670 ->method('is_enabled')
671 ->with($cm)
672 ->will($this->returnValue(true));
673 $c->inform_grade_changed($cm, $item, $grade, false);
674
675 // Enabled and completion required and item number right. It is supposed
676 // to call update_state with the new potential state being obtained from
677 // internal_get_grade_state.
678 $cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>3);
679 $grade = (object)array('userid'=>31337, 'finalgrade'=>1, 'rawgrade'=>0);
680 $c->expects($this->at(0))
681 ->method('is_enabled')
682 ->with($cm)
683 ->will($this->returnValue(true));
684 $c->expects($this->at(1))
685 ->method('update_state')
686 ->with($cm, COMPLETION_COMPLETE_PASS, 31337)
687 ->will($this->returnValue(true));
688 $c->inform_grade_changed($cm, $item, $grade, false);
689
690 // Same as above but marked deleted. It is supposed to call update_state
691 // with new potential state being COMPLETION_INCOMPLETE
692 $cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>3);
693 $grade = (object)array('userid'=>31337, 'finalgrade'=>1, 'rawgrade'=>0);
694 $c->expects($this->at(0))
695 ->method('is_enabled')
696 ->with($cm)
697 ->will($this->returnValue(true));
698 $c->expects($this->at(1))
699 ->method('update_state')
700 ->with($cm, COMPLETION_INCOMPLETE, 31337)
701 ->will($this->returnValue(true));
702 $c->inform_grade_changed($cm, $item, $grade, true);
703 }
704
705 function test_internal_get_grade_state() {
706 $item = new stdClass;
707 $grade = new stdClass;
708
709 $item->gradepass = 4;
710 $item->hidden = 0;
711 $grade->rawgrade = 4.0;
712 $grade->finalgrade = null;
713
714 // Grade has pass mark and is not hidden, user passes
715 $this->assertEquals(
716 COMPLETION_COMPLETE_PASS,
717 completion_info::internal_get_grade_state($item, $grade));
718
719 // Same but user fails
720 $grade->rawgrade = 3.9;
721 $this->assertEquals(
722 COMPLETION_COMPLETE_FAIL,
723 completion_info::internal_get_grade_state($item, $grade));
724
725 // User fails on raw grade but passes on final
726 $grade->finalgrade = 4.0;
727 $this->assertEquals(
728 COMPLETION_COMPLETE_PASS,
729 completion_info::internal_get_grade_state($item, $grade));
730
731 // Item is hidden
732 $item->hidden = 1;
733 $this->assertEquals(
734 COMPLETION_COMPLETE,
735 completion_info::internal_get_grade_state($item, $grade));
736
737 // Item isn't hidden but has no pass mark
738 $item->hidden = 0;
739 $item->gradepass = 0;
740 $this->assertEquals(
741 COMPLETION_COMPLETE,
742 completion_info::internal_get_grade_state($item, $grade));
743 }
744}
745
746
747class completion_test_fake_recordset implements Iterator {
748 var $closed;
749 var $values, $index;
750
751 function __construct($values) {
752 $this->values = $values;
753 $this->index = 0;
754 }
755
756 function current() {
757 return $this->values[$this->index];
758 }
759
760 function key() {
761 return $this->values[$this->index];
762 }
763
764 function next() {
765 $this->index++;
766 }
767
768 function rewind() {
769 $this->index = 0;
770 }
771
772 function valid() {
773 return count($this->values) > $this->index;
774 }
775
776 function close() {
777 $this->closed = true;
778 }
779
780 function was_closed() {
781 return $this->closed;
782 }
783}