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 core_question;
18
 
19
use core_question\local\bank\question_version_status;
20
use core_question\output\question_version_info;
21
use question_bank;
22
 
23
/**
24
 * Question version unit tests.
25
 *
26
 * @package    core_question
27
 * @copyright  2021 Catalyst IT Australia Pty Ltd
28
 * @author     Guillermo Gomez Arias <guillermogomez@catalyst-au.net>
29
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30
 * @coversDefaultClass \question_bank
31
 */
1441 ariadna 32
final class version_test extends \advanced_testcase {
1 efrain 33
 
34
    /**
35
     * @var \context_module module context.
36
     */
37
    protected $context;
38
 
39
    /**
40
     * @var \stdClass course object.
41
     */
42
    protected $course;
43
 
44
    /**
45
     * @var \component_generator_base question generator.
46
     */
47
    protected $qgenerator;
48
 
49
    /**
50
     * @var \stdClass quiz object.
51
     */
52
    protected $quiz;
53
 
54
    /**
55
     * Called before every test.
56
     */
57
    protected function setUp(): void {
58
        parent::setUp();
59
        self::setAdminUser();
60
        $this->resetAfterTest();
61
 
62
        $datagenerator = $this->getDataGenerator();
63
        $this->course = $datagenerator->create_course();
64
        $this->quiz = $datagenerator->create_module('quiz', ['course' => $this->course->id]);
65
        $this->qgenerator = $datagenerator->get_plugin_generator('core_question');
66
        $this->context = \context_module::instance($this->quiz->cmid);
67
    }
68
 
69
    protected function tearDown(): void {
70
        question_version_info::$pendingdefinitions = [];
71
        parent::tearDown();
72
    }
73
 
74
    /**
75
     * Test if creating a question a new version and bank entry records are created.
76
     *
77
     * @covers ::load_question
78
     */
11 efrain 79
    public function test_make_question_create_version_and_bank_entry(): void {
1 efrain 80
        global $DB;
81
 
82
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
83
        $question = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategory->id]);
84
 
85
        // Get the question object after creating a question.
86
        $questiondefinition = question_bank::load_question($question->id);
87
 
88
        // The version and bank entry in the object should be the same.
89
        $sql = "SELECT qv.id AS versionid, qv.questionbankentryid
90
                  FROM {question} q
91
                  JOIN {question_versions} qv ON qv.questionid = q.id
92
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
93
                 WHERE q.id = ?";
94
        $questionversion = $DB->get_record_sql($sql, [$questiondefinition->id]);
95
        $this->assertEquals($questionversion->versionid, $questiondefinition->versionid);
96
        $this->assertEquals($questionversion->questionbankentryid, $questiondefinition->questionbankentryid);
97
 
98
        // If a question is updated, a new version should be created.
99
        $question = $this->qgenerator->update_question($question, null, ['name' => 'This is a new version']);
100
        $newquestiondefinition = question_bank::load_question($question->id);
101
        // The version should be 2.
102
        $this->assertEquals('2', $newquestiondefinition->version);
103
 
104
        // Both versions should be in same bank entry.
105
        $this->assertEquals($questiondefinition->questionbankentryid, $newquestiondefinition->questionbankentryid);
106
    }
107
 
108
    /**
109
     * Test if deleting a question the related version and bank entry records are deleted.
110
     *
111
     * @covers ::load_question
112
     * @covers ::question_delete_question
113
     */
11 efrain 114
    public function test_delete_question_delete_versions(): void {
1 efrain 115
        global $DB;
116
 
117
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
118
        $question = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategory->id]);
119
        $questionfirstversionid = $question->id;
120
 
121
        // Create a new version and try to remove it.
122
        $question = $this->qgenerator->update_question($question, null, ['name' => 'This is a new version']);
123
 
124
        // The new version and bank entry record should exist.
125
        $sql = "SELECT q.id, qv.id AS versionid, qv.questionbankentryid
126
                  FROM {question} q
127
                  JOIN {question_versions} qv ON qv.questionid = q.id
128
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
129
                 WHERE q.id = ?";
130
        $questionobject = $DB->get_records_sql($sql, [$question->id]);
131
        $this->assertCount(1, $questionobject);
132
 
133
        // Try to delete new version.
134
        question_delete_question($question->id);
135
 
136
        // The version record should not exist.
137
        $sql = "SELECT qv.*
138
                  FROM {question_versions} qv
139
                 WHERE qv.id = ?";
140
        $questionversion = $DB->get_record_sql($sql, [$questionobject[$question->id]->versionid]);
141
        $this->assertFalse($questionversion);
142
 
143
        // The bank entry record should exist because there is an older version.
144
        $sql = "SELECT qbe.*
145
                  FROM {question_bank_entries} qbe
146
                 WHERE qbe.id = ?";
147
        $questionbankentry = $DB->get_records_sql($sql, [$questionobject[$question->id]->questionbankentryid]);
148
        $this->assertCount(1, $questionbankentry);
149
 
150
        // Now remove the first version.
151
        question_delete_question($questionfirstversionid);
152
        $sql = "SELECT qbe.*
153
                  FROM {question_bank_entries} qbe
154
                 WHERE qbe.id = ?";
155
        $questionbankentry = $DB->get_record_sql($sql, [$questionobject[$question->id]->questionbankentryid]);
156
        // The bank entry record should not exist.
157
        $this->assertFalse($questionbankentry);
158
    }
159
 
160
    /**
161
     * Test if deleting a question will not break a quiz.
162
     *
163
     * @covers ::load_question
164
     * @covers ::quiz_add_quiz_question
165
     * @covers ::question_delete_question
166
     */
11 efrain 167
    public function test_delete_question_in_use(): void {
1 efrain 168
        global $DB;
169
 
170
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
171
        $question = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategory->id]);
172
        $questionfirstversionid = $question->id;
173
 
174
        // Create a new version and try to remove it after adding it to a quiz.
175
        $question = $this->qgenerator->update_question($question, null, ['name' => 'This is a new version']);
176
 
177
        // Add it to the quiz.
178
        quiz_add_quiz_question($question->id, $this->quiz);
179
 
180
        // Try to delete new version.
181
        question_delete_question($question->id);
182
        // Try to delete old version.
183
        question_delete_question($questionfirstversionid);
184
 
185
        // The used question version should exist even after trying to remove it, but now hidden.
186
        $questionversion2 = question_bank::load_question($question->id);
187
        $this->assertEquals($question->id, $questionversion2->id);
188
        $this->assertEquals(question_version_status::QUESTION_STATUS_HIDDEN, $questionversion2->status);
189
 
190
        // The unused version should be completely gone.
191
        $this->assertFalse($DB->record_exists('question', ['id' => $questionfirstversionid]));
192
    }
193
 
194
    /**
195
     * Test if moving a category will not break a quiz.
196
     *
197
     * @covers ::load_question
198
     * @covers ::quiz_add_quiz_question
199
     */
11 efrain 200
    public function test_move_category_with_questions(): void {
1 efrain 201
        global $DB;
202
 
203
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
204
        $qcategorychild = $this->qgenerator->create_question_category(['contextid' => $this->context->id,
205
            'parent' => $qcategory->id]);
1441 ariadna 206
        $qbank = self::getDataGenerator()->create_module('qbank', ['course' => $this->course->id]);
207
        $bankcontext = \context_module::instance($qbank->cmid);
208
        $qcategorysys = $this->qgenerator->create_question_category(['contextid' => $bankcontext->id]);
1 efrain 209
        $question = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategorychild->id]);
210
        $questiondefinition = question_bank::load_question($question->id);
211
 
212
        // Add it to the quiz.
213
        quiz_add_quiz_question($question->id, $this->quiz);
214
 
1441 ariadna 215
        // Move the category to qbank context.
216
        $manager = new category_manager();
217
        $manager->move_questions_and_delete_category($qcategorychild->id, $qcategorysys->id);
1 efrain 218
 
219
        // The bank entry record should point to the new category in order to not break quizzes.
220
        $sql = "SELECT qbe.questioncategoryid
221
                  FROM {question_bank_entries} qbe
222
                 WHERE qbe.id = ?";
223
        $questionbankentry = $DB->get_record_sql($sql, [$questiondefinition->questionbankentryid]);
224
        $this->assertEquals($qcategorysys->id, $questionbankentry->questioncategoryid);
225
    }
226
 
227
    /**
228
     * Test that all versions will have the same bank entry idnumber value.
229
     *
230
     * @covers ::load_question
231
     */
11 efrain 232
    public function test_id_number_in_bank_entry(): void {
1 efrain 233
        global $DB;
234
 
235
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
236
        $question = $this->qgenerator->create_question('shortanswer', null,
237
            [
238
                'category' => $qcategory->id,
239
                'idnumber' => 'id1'
240
            ]);
241
        $questionid1 = $question->id;
242
 
243
        // Create a new version and try to remove it after adding it to a quiz.
244
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id2']);
245
        $questionid2 = $question->id;
246
        // Change the id number and get the question object.
247
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id3']);
248
        $questionid3 = $question->id;
249
 
250
        // The new version and bank entry record should exist.
251
        $questiondefinition = question_bank::load_question($question->id);
252
        $sql = "SELECT q.id AS questionid, qv.id AS versionid, qbe.id AS questionbankentryid, qbe.idnumber
253
                  FROM {question_bank_entries} qbe
254
                  JOIN {question_versions} qv ON qv.questionbankentryid = qbe.id
255
                  JOIN {question} q ON q.id = qv.questionid
256
                 WHERE qbe.id = ?";
257
        $questionbankentry = $DB->get_records_sql($sql, [$questiondefinition->questionbankentryid]);
258
 
259
        // We should have 3 versions and 1 question bank entry with the same idnumber.
260
        $this->assertCount(3, $questionbankentry);
261
        $this->assertEquals($questionbankentry[$questionid1]->idnumber, 'id3');
262
        $this->assertEquals($questionbankentry[$questionid2]->idnumber, 'id3');
263
        $this->assertEquals($questionbankentry[$questionid3]->idnumber, 'id3');
264
    }
265
 
266
    /**
267
     * Test that all the versions are available from the method.
268
     *
269
     * @covers ::get_all_versions_of_question
270
     */
11 efrain 271
    public function test_get_all_versions_of_question(): void {
1 efrain 272
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
273
        $question = $this->qgenerator->create_question('shortanswer', null,
274
            [
275
                'category' => $qcategory->id,
276
                'idnumber' => 'id1'
277
            ]);
278
        $questionid1 = $question->id;
279
 
280
        // Create a new version.
281
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id2']);
282
        $questionid2 = $question->id;
283
        // Change the id number and get the question object.
284
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id3']);
285
        $questionid3 = $question->id;
286
 
287
        $questiondefinition = question_bank::get_all_versions_of_question($question->id);
288
 
289
        // Test the versions are available.
290
        $this->assertEquals(array_slice($questiondefinition, 0, 1)[0]->questionid, $questionid3);
291
        $this->assertEquals(array_slice($questiondefinition, 1, 1)[0]->questionid, $questionid2);
292
        $this->assertEquals(array_slice($questiondefinition, 2, 1)[0]->questionid, $questionid1);
293
    }
294
 
295
    /**
296
     * Test that all the versions of questions are available from the method.
297
     *
298
     * @covers ::get_all_versions_of_questions
299
     */
11 efrain 300
    public function test_get_all_versions_of_questions(): void {
1 efrain 301
        global $DB;
302
 
303
        $questionversions = [];
304
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
305
        $question = $this->qgenerator->create_question('shortanswer', null,
306
            [
307
                'category' => $qcategory->id,
308
                'idnumber' => 'id1'
309
            ]);
310
        $questionversions[1] = $question->id;
311
 
312
        // Create a new version.
313
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id2']);
314
        $questionversions[2] = $question->id;
315
        // Change the id number and get the question object.
316
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id3']);
317
        $questionversions[3] = $question->id;
318
 
319
        $questionbankentryid = $DB->get_record('question_versions', ['questionid' => $question->id], 'questionbankentryid');
320
 
321
        $questionversionsofquestions = question_bank::get_all_versions_of_questions([$question->id]);
322
        $questionbankentryids = array_keys($questionversionsofquestions)[0];
323
        $this->assertEquals($questionbankentryid->questionbankentryid, $questionbankentryids);
324
        $this->assertEquals($questionversions, $questionversionsofquestions[$questionbankentryids]);
325
    }
326
 
327
    /**
1441 ariadna 328
     * Test the get_version_of_questions function.
329
     *
330
     * @covers ::get_version_of_questions
331
     */
332
    public function test_get_version_of_questions(): void {
333
        global $DB;
334
 
335
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
336
        $question = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategory->id]);
337
 
338
        // Update the question to create new versions.
339
        $question = $this->qgenerator->update_question($question, null, ['name' => 'Version 2']);
340
 
341
        // Get the current version of the question.
342
        $currentversions = question_bank::get_version_of_questions([$question->id]);
343
 
344
        // Get questionbankentryid for assertion.
345
        $questionbankentryid = $DB->get_field('question_versions', 'questionbankentryid',
346
            ['questionid' => $question->id]);
347
 
348
        // Assert that the structure matches.
349
        $this->assertArrayHasKey($questionbankentryid, $currentversions);
350
        $this->assertArrayHasKey(2, $currentversions[$questionbankentryid]);
351
        $this->assertEquals($question->id, $currentversions[$questionbankentryid][2]);
352
 
353
        // Update the question to create new versions.
354
        $question = $this->qgenerator->update_question($question, null, ['name' => 'Version 3']);
355
        $currentversions = question_bank::get_version_of_questions([$question->id]);
356
 
357
        // Assert the updated version.
358
        $this->assertArrayHasKey($questionbankentryid, $currentversions);
359
        $this->assertArrayHasKey(3, $currentversions[$questionbankentryid]);
360
        $this->assertEquals($question->id, $currentversions[$questionbankentryid][3]);
361
    }
362
 
363
    /**
1 efrain 364
     * Test population of latestversion field in question_definition objects
365
     *
366
     * When an instance of question_definition is created, it is added to an array of pending definitions which
367
     * do not yet have the latestversion field populated. When one definition has its latestversion property accessed,
368
     * all pending definitions have their latestversion field populated at once.
369
     *
370
     * @covers \core_question\output\question_version_info::populate_latest_versions()
371
     * @return void
372
     */
11 efrain 373
    public function test_populate_definition_latestversions(): void {
1 efrain 374
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
375
        $question1 = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategory->id]);
376
        $question2 = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategory->id]);
377
        $question3 = $this->qgenerator->update_question($question2, null, ['idnumber' => 'id2']);
378
 
379
        $latestversioninspector = new \ReflectionProperty('question_definition', 'latestversion');
380
        $this->assertEmpty(question_version_info::$pendingdefinitions);
381
 
382
        $questiondef1 = question_bank::load_question($question1->id);
383
        $questiondef2 = question_bank::load_question($question2->id);
384
        $questiondef3 = question_bank::load_question($question3->id);
385
 
386
        $this->assertContains($questiondef1, question_version_info::$pendingdefinitions);
387
        $this->assertContains($questiondef2, question_version_info::$pendingdefinitions);
388
        $this->assertContains($questiondef3, question_version_info::$pendingdefinitions);
389
        $this->assertNull($latestversioninspector->getValue($questiondef1));
390
        $this->assertNull($latestversioninspector->getValue($questiondef2));
391
        $this->assertNull($latestversioninspector->getValue($questiondef3));
392
 
393
        // Read latestversion from one definition. This should populate the field in all pending definitions.
394
        $latestversion1 = $questiondef1->latestversion;
395
 
396
        $this->assertEmpty(question_version_info::$pendingdefinitions);
397
        $this->assertNotNull($latestversioninspector->getValue($questiondef1));
398
        $this->assertNotNull($latestversioninspector->getValue($questiondef2));
399
        $this->assertNotNull($latestversioninspector->getValue($questiondef3));
400
        $this->assertEquals($latestversion1, $latestversioninspector->getValue($questiondef1));
401
        $this->assertEquals($questiondef1->version, $questiondef1->latestversion);
402
        $this->assertNotEquals($questiondef2->version, $questiondef2->latestversion);
403
        $this->assertEquals($questiondef3->version, $questiondef3->latestversion);
404
    }
405
}