Proyectos de Subversion Moodle

Rev

Rev 1 | | 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
 */
32
class version_test extends \advanced_testcase {
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]);
206
        $systemcontext = \context_system::instance();
207
        $qcategorysys = $this->qgenerator->create_question_category(['contextid' => $systemcontext->id]);
208
        $question = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategorychild->id]);
209
        $questiondefinition = question_bank::load_question($question->id);
210
 
211
        // Add it to the quiz.
212
        quiz_add_quiz_question($question->id, $this->quiz);
213
 
214
        // Move the category to system context.
215
        $contexts = new \core_question\local\bank\question_edit_contexts($systemcontext);
216
        $qcobject = new \qbank_managecategories\question_category_object(null,
217
            new \moodle_url('/question/bank/managecategories/category.php', ['courseid' => SITEID]),
218
            $contexts->having_one_edit_tab_cap('categories'), 0, null, 0,
219
            $contexts->having_cap('moodle/question:add'));
220
        $qcobject->move_questions_and_delete_category($qcategorychild->id, $qcategorysys->id);
221
 
222
        // The bank entry record should point to the new category in order to not break quizzes.
223
        $sql = "SELECT qbe.questioncategoryid
224
                  FROM {question_bank_entries} qbe
225
                 WHERE qbe.id = ?";
226
        $questionbankentry = $DB->get_record_sql($sql, [$questiondefinition->questionbankentryid]);
227
        $this->assertEquals($qcategorysys->id, $questionbankentry->questioncategoryid);
228
    }
229
 
230
    /**
231
     * Test that all versions will have the same bank entry idnumber value.
232
     *
233
     * @covers ::load_question
234
     */
11 efrain 235
    public function test_id_number_in_bank_entry(): void {
1 efrain 236
        global $DB;
237
 
238
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
239
        $question = $this->qgenerator->create_question('shortanswer', null,
240
            [
241
                'category' => $qcategory->id,
242
                'idnumber' => 'id1'
243
            ]);
244
        $questionid1 = $question->id;
245
 
246
        // Create a new version and try to remove it after adding it to a quiz.
247
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id2']);
248
        $questionid2 = $question->id;
249
        // Change the id number and get the question object.
250
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id3']);
251
        $questionid3 = $question->id;
252
 
253
        // The new version and bank entry record should exist.
254
        $questiondefinition = question_bank::load_question($question->id);
255
        $sql = "SELECT q.id AS questionid, qv.id AS versionid, qbe.id AS questionbankentryid, qbe.idnumber
256
                  FROM {question_bank_entries} qbe
257
                  JOIN {question_versions} qv ON qv.questionbankentryid = qbe.id
258
                  JOIN {question} q ON q.id = qv.questionid
259
                 WHERE qbe.id = ?";
260
        $questionbankentry = $DB->get_records_sql($sql, [$questiondefinition->questionbankentryid]);
261
 
262
        // We should have 3 versions and 1 question bank entry with the same idnumber.
263
        $this->assertCount(3, $questionbankentry);
264
        $this->assertEquals($questionbankentry[$questionid1]->idnumber, 'id3');
265
        $this->assertEquals($questionbankentry[$questionid2]->idnumber, 'id3');
266
        $this->assertEquals($questionbankentry[$questionid3]->idnumber, 'id3');
267
    }
268
 
269
    /**
270
     * Test that all the versions are available from the method.
271
     *
272
     * @covers ::get_all_versions_of_question
273
     */
11 efrain 274
    public function test_get_all_versions_of_question(): void {
1 efrain 275
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
276
        $question = $this->qgenerator->create_question('shortanswer', null,
277
            [
278
                'category' => $qcategory->id,
279
                'idnumber' => 'id1'
280
            ]);
281
        $questionid1 = $question->id;
282
 
283
        // Create a new version.
284
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id2']);
285
        $questionid2 = $question->id;
286
        // Change the id number and get the question object.
287
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id3']);
288
        $questionid3 = $question->id;
289
 
290
        $questiondefinition = question_bank::get_all_versions_of_question($question->id);
291
 
292
        // Test the versions are available.
293
        $this->assertEquals(array_slice($questiondefinition, 0, 1)[0]->questionid, $questionid3);
294
        $this->assertEquals(array_slice($questiondefinition, 1, 1)[0]->questionid, $questionid2);
295
        $this->assertEquals(array_slice($questiondefinition, 2, 1)[0]->questionid, $questionid1);
296
    }
297
 
298
    /**
299
     * Test that all the versions of questions are available from the method.
300
     *
301
     * @covers ::get_all_versions_of_questions
302
     */
11 efrain 303
    public function test_get_all_versions_of_questions(): void {
1 efrain 304
        global $DB;
305
 
306
        $questionversions = [];
307
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
308
        $question = $this->qgenerator->create_question('shortanswer', null,
309
            [
310
                'category' => $qcategory->id,
311
                'idnumber' => 'id1'
312
            ]);
313
        $questionversions[1] = $question->id;
314
 
315
        // Create a new version.
316
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id2']);
317
        $questionversions[2] = $question->id;
318
        // Change the id number and get the question object.
319
        $question = $this->qgenerator->update_question($question, null, ['idnumber' => 'id3']);
320
        $questionversions[3] = $question->id;
321
 
322
        $questionbankentryid = $DB->get_record('question_versions', ['questionid' => $question->id], 'questionbankentryid');
323
 
324
        $questionversionsofquestions = question_bank::get_all_versions_of_questions([$question->id]);
325
        $questionbankentryids = array_keys($questionversionsofquestions)[0];
326
        $this->assertEquals($questionbankentryid->questionbankentryid, $questionbankentryids);
327
        $this->assertEquals($questionversions, $questionversionsofquestions[$questionbankentryids]);
328
    }
329
 
330
    /**
331
     * Test population of latestversion field in question_definition objects
332
     *
333
     * When an instance of question_definition is created, it is added to an array of pending definitions which
334
     * do not yet have the latestversion field populated. When one definition has its latestversion property accessed,
335
     * all pending definitions have their latestversion field populated at once.
336
     *
337
     * @covers \core_question\output\question_version_info::populate_latest_versions()
338
     * @return void
339
     */
11 efrain 340
    public function test_populate_definition_latestversions(): void {
1 efrain 341
        $qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
342
        $question1 = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategory->id]);
343
        $question2 = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategory->id]);
344
        $question3 = $this->qgenerator->update_question($question2, null, ['idnumber' => 'id2']);
345
 
346
        $latestversioninspector = new \ReflectionProperty('question_definition', 'latestversion');
347
        $this->assertEmpty(question_version_info::$pendingdefinitions);
348
 
349
        $questiondef1 = question_bank::load_question($question1->id);
350
        $questiondef2 = question_bank::load_question($question2->id);
351
        $questiondef3 = question_bank::load_question($question3->id);
352
 
353
        $this->assertContains($questiondef1, question_version_info::$pendingdefinitions);
354
        $this->assertContains($questiondef2, question_version_info::$pendingdefinitions);
355
        $this->assertContains($questiondef3, question_version_info::$pendingdefinitions);
356
        $this->assertNull($latestversioninspector->getValue($questiondef1));
357
        $this->assertNull($latestversioninspector->getValue($questiondef2));
358
        $this->assertNull($latestversioninspector->getValue($questiondef3));
359
 
360
        // Read latestversion from one definition. This should populate the field in all pending definitions.
361
        $latestversion1 = $questiondef1->latestversion;
362
 
363
        $this->assertEmpty(question_version_info::$pendingdefinitions);
364
        $this->assertNotNull($latestversioninspector->getValue($questiondef1));
365
        $this->assertNotNull($latestversioninspector->getValue($questiondef2));
366
        $this->assertNotNull($latestversioninspector->getValue($questiondef3));
367
        $this->assertEquals($latestversion1, $latestversioninspector->getValue($questiondef1));
368
        $this->assertEquals($questiondef1->version, $questiondef1->latestversion);
369
        $this->assertNotEquals($questiondef2->version, $questiondef2->latestversion);
370
        $this->assertEquals($questiondef3->version, $questiondef3->latestversion);
371
    }
372
}