6e81cd710ecc8cebb08db9f2d173764b0ae84896
[moodle.git] / lib / tests / completionlib_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * 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  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->libdir.'/completionlib.php');
32 class completionlib_testcase extends basic_testcase {
34     var $realdb, $realcfg, $realsession, $realuser;
36     protected function setUp() {
37         global $DB, $CFG, $SESSION, $USER;
38         parent::setUp();
40         $this->realdb = $DB;
41         $this->realcfg = $CFG;
42         $this->realsession = $SESSION;
43         $this->prevuser = $USER;
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     }
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;
60         parent::tearDown();
61     }
63     function test_is_enabled() {
64         global $CFG;
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());
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());
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     }
97     function test_update_state() {
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);
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);
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);
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);
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);
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);
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     }
187     function test_internal_get_state() {
188         global $DB;
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);
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));
198         // OK set view not required
199         $cm->completionview = COMPLETION_VIEW_NOT_REQUIRED;
201         // Test not getting module name
202         $cm->modname='label';
203         $this->assertEquals(COMPLETION_COMPLETE, $c->internal_get_state($cm, 123, $current));
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));
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     }
221     function test_set_module_viewed() {
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);
228         // Not tracking completion, should do nothing
229         $cm->completionview = COMPLETION_VIEW_NOT_REQUIRED;
230         $c->set_module_viewed($cm);
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);
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);
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')
260             ->with($cm, 1337)
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     }
271     function test_count_user_data() {
272         global $DB;
274         $course = (object)array('id'=>13);
275         $cm = (object)array('id'=>42);
277         /** @var $DB PHPUnit_Framework_MockObject_MockObject */
278         $DB->expects($this->at(0))
279             ->method('get_field_sql')
280             ->will($this->returnValue(666));
282 /*
283         $DB->expectOnce('get_field_sql',array(new IgnoreWhitespaceExpectation("SELECT
284     COUNT(1)
285 FROM
286     {course_modules_completion}
287 WHERE
288     coursemoduleid=? AND completionstate<>0"),array(42)));
289 */
291         $c = new completion_info($course);
292         $this->assertEquals(666, $c->count_user_data($cm));
293     }
295     function test_delete_all_state() {
296         global $DB, $SESSION;
298         $course = (object)array('id'=>13);
299         $cm = (object)array('id'=>42,'course'=>13);
300         $c = new completion_info($course);
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);
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     }
326     function test_reset_all_state() {
327         global $DB;
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)));
333         $cm = (object)array('id'=>13, 'course'=>42, 'completion'=>COMPLETION_TRACKING_AUTOMATIC);
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)))));
341         $c->expects($this->at(0))
342             ->method('delete_all_state')
343             ->with($cm);
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'))));
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);
361         $c->reset_all_state($cm);
362     }
364     function test_get_data() {
365         global $DB, $SESSION;
367         $c = new completion_info((object)array('id'=>42));
368         $cm = (object)array('id'=>13, 'course'=>42);
370         // 1. Not current user, record exists
371         $sillyrecord = (object)array('frog'=>'kermit');
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));
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));
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);
404         // 4. Current user, 'whole course', but from cache
405         $result = $c->get_data($cm, true);
406         $this->assertEquals($sillyrecord, $result);
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);
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);
427         // 6. Current user, 'whole course' and record not in cache
428         unset($SESSION->completioncache);
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)));
436 /*
437         $DB->expectAt(0,'get_records_sql',array(new IgnoreWhitespaceExpectation("
438 SELECT
439     cmc.*
440 FROM
441     {course_modules} cm
442     INNER JOIN {course_modules_completion} cmc ON cmc.coursemoduleid=cm.id
443 WHERE
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);
451         // Check result
452         $this->assertEquals($basicrecord, $result);
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     }
462     function test_internal_set_data() {
463         global $DB, $SESSION;
465         $cm = (object)array('course' => 42,'id' => 13);
466         $c = new completion_info((object)array('id' => 42));
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))));
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));
479         $DB->expects($this->at(2))
480             ->method('insert_record')
481             ->will($this->returnValue(4));
483         $c->internal_set_data($cm, $data);
484         $this->assertEquals(4, $data->id);
485         $this->assertEquals(array(42 => array(13 => $data)), $SESSION->completioncache);
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));
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     }
517     function test_get_activities() {
518         global $DB;
520         $c = new completion_info((object)array('id'=>42));
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);
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     }
542     // get_tracked_users() cannot easily be tested because it uses
543     // get_role_users, so skipping that
545     function test_get_progress_all() {
546         global $DB;
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)));
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))));
569 /*
570         $DB->expectAt(0, 'get_recordset_sql', array(new IgnoreWhitespaceExpectation("
571 SELECT
572     cmc.*
573 FROM
574     {course_modules} cm
575     INNER JOIN {course_modules_completion} cmc ON cm.id = cmc.coursemoduleid
576 WHERE
577     cm.course = ? AND cmc.userid IN (100, 201)"), array(42)));
578 */
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));
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))));
609 /*
610         $DB->expectAt(1, 'get_recordset_sql', array(new IgnoreWhitespaceExpectation("
611 SELECT
612     cmc.*
613 FROM
614     {course_modules} cm
615     INNER JOIN {course_modules_completion} cmc ON cm.id = cmc.coursemoduleid
616 WHERE
617     cm.course = ? AND cmc.userid IN whatever"), array(42)));
618 */
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))));
628         $result = $c->get_progress_all(true, 3);
629         $resultok = true;
630         $resultok  =  $resultok && ($ids == array_keys($result));
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     }
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)));
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);
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);
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);
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);
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);
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     }
705     function test_internal_get_grade_state() {
706         $item = new stdClass;
707         $grade = new stdClass;
709         $item->gradepass = 4;
710         $item->hidden = 0;
711         $grade->rawgrade = 4.0;
712         $grade->finalgrade = null;
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));
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));
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));
731         // Item is hidden
732         $item->hidden = 1;
733         $this->assertEquals(
734             COMPLETION_COMPLETE,
735             completion_info::internal_get_grade_state($item, $grade));
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     }
747 class completion_test_fake_recordset implements Iterator {
748     var $closed;
749     var $values, $index;
751     function __construct($values) {
752         $this->values = $values;
753         $this->index = 0;
754     }
756     function current() {
757         return $this->values[$this->index];
758     }
760     function key() {
761         return $this->values[$this->index];
762     }
764     function next() {
765         $this->index++;
766     }
768     function rewind() {
769         $this->index = 0;
770     }
772     function valid() {
773         return count($this->values) > $this->index;
774     }
776     function close() {
777         $this->closed = true;
778     }
780     function was_closed() {
781         return $this->closed;
782     }