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 qubaid_join;
20
use qubaid_list;
21
use question_bank;
22
use question_engine;
23
use question_engine_data_mapper;
24
 
25
defined('MOODLE_INTERNAL') || die();
26
 
27
global $CFG;
28
require_once(__DIR__ . '/../lib.php');
29
require_once(__DIR__ . '/helpers.php');
30
 
31
/**
32
 * Unit tests for parts of {@link question_engine_data_mapper}.
33
 *
34
 * Note that many of the methods used when attempting questions, like
35
 * load_questions_usage_by_activity, insert_question_*, delete_steps are
36
 * tested elsewhere, e.g. by {@link question_usage_autosave_test}. We do not
37
 * re-test them here.
38
 *
39
 * @package   core_question
40
 * @category  test
41
 * @copyright 2014 The Open University
42
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43
 * @covers \question_engine_data_mapper
44
 */
45
class datalib_test extends \qbehaviour_walkthrough_test_base {
46
 
47
    /**
48
     * We create two usages, each with two questions, a short-answer marked
49
     * out of 5, and and essay marked out of 10. We just start these attempts.
50
     *
51
     * Then we change the max mark for the short-answer question in one of the
52
     * usages to 20, using a qubaid_list, and verify.
53
     *
54
     * Then we change the max mark for the essay question in the other
55
     * usage to 2, using a qubaid_join, and verify.
56
     */
11 efrain 57
    public function test_set_max_mark_in_attempts(): void {
1 efrain 58
 
59
        // Set up some things the tests will need.
60
        $this->resetAfterTest();
61
        $dm = new question_engine_data_mapper();
62
 
63
        // Create the questions.
64
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
65
        $cat = $generator->create_question_category();
66
        $sa = $generator->create_question('shortanswer', null,
67
                array('category' => $cat->id));
68
        $essay = $generator->create_question('essay', null,
69
                array('category' => $cat->id));
70
 
71
        // Create the first usage.
72
        $q = question_bank::load_question($sa->id);
73
        $this->start_attempt_at_question($q, 'interactive', 5);
74
 
75
        $q = question_bank::load_question($essay->id);
76
        $this->start_attempt_at_question($q, 'interactive', 10);
77
 
78
        $this->finish();
79
        $this->save_quba();
80
        $usage1id = $this->quba->get_id();
81
 
82
        // Create the second usage.
83
        $this->quba = question_engine::make_questions_usage_by_activity('unit_test',
84
                \context_system::instance());
85
 
86
        $q = question_bank::load_question($sa->id);
87
        $this->start_attempt_at_question($q, 'interactive', 5);
88
        $this->process_submission(array('answer' => 'fish'));
89
 
90
        $q = question_bank::load_question($essay->id);
91
        $this->start_attempt_at_question($q, 'interactive', 10);
92
 
93
        $this->finish();
94
        $this->save_quba();
95
        $usage2id = $this->quba->get_id();
96
 
97
        // Test set_max_mark_in_attempts with a qubaid_list.
98
        $usagestoupdate = new qubaid_list(array($usage1id));
99
        $dm->set_max_mark_in_attempts($usagestoupdate, 1, 20.0);
100
        $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
101
        $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
102
        $this->assertEquals(20, $quba1->get_question_max_mark(1));
103
        $this->assertEquals(10, $quba1->get_question_max_mark(2));
104
        $this->assertEquals( 5, $quba2->get_question_max_mark(1));
105
        $this->assertEquals(10, $quba2->get_question_max_mark(2));
106
 
107
        // Test set_max_mark_in_attempts with a qubaid_join.
108
        $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id',
109
                'qu.id = :usageid', array('usageid' => $usage2id));
110
        $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
111
        $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
112
        $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
113
        $this->assertEquals(20, $quba1->get_question_max_mark(1));
114
        $this->assertEquals(10, $quba1->get_question_max_mark(2));
115
        $this->assertEquals( 5, $quba2->get_question_max_mark(1));
116
        $this->assertEquals( 2, $quba2->get_question_max_mark(2));
117
 
118
        // Test the nothing to do case.
119
        $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id',
120
                'qu.id = :usageid', array('usageid' => -1));
121
        $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
122
        $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
123
        $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
124
        $this->assertEquals(20, $quba1->get_question_max_mark(1));
125
        $this->assertEquals(10, $quba1->get_question_max_mark(2));
126
        $this->assertEquals( 5, $quba2->get_question_max_mark(1));
127
        $this->assertEquals( 2, $quba2->get_question_max_mark(2));
128
    }
129
 
11 efrain 130
    public function test_load_used_variants(): void {
1 efrain 131
        $this->resetAfterTest();
132
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
133
 
134
        $cat = $generator->create_question_category();
135
        $questiondata1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
136
        $questiondata2 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
137
        $questiondata3 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
138
 
139
        $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance());
140
        $quba->set_preferred_behaviour('deferredfeedback');
141
        $question1 = question_bank::load_question($questiondata1->id);
142
        $question3 = question_bank::load_question($questiondata3->id);
143
        $quba->add_question($question1);
144
        $quba->add_question($question1);
145
        $quba->add_question($question3);
146
        $quba->start_all_questions();
147
        question_engine::save_questions_usage_by_activity($quba);
148
 
149
        $this->assertEquals(array(
150
                    $questiondata1->id => array(1 => 2),
151
                    $questiondata2->id => array(),
152
                    $questiondata3->id => array(1 => 1),
153
                ), question_engine::load_used_variants(
154
                    array($questiondata1->id, $questiondata2->id, $questiondata3->id),
155
                    new qubaid_list(array($quba->get_id()))));
156
    }
157
 
11 efrain 158
    public function test_repeated_usage_saving_new_usage(): void {
1 efrain 159
        global $DB;
160
 
161
        $this->resetAfterTest();
162
 
163
        $initialqurows = $DB->count_records('question_usages');
164
        $initialqarows = $DB->count_records('question_attempts');
165
        $initialqasrows = $DB->count_records('question_attempt_steps');
166
 
167
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
168
        $cat = $generator->create_question_category();
169
        $questiondata1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
170
 
171
        $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance());
172
        $quba->set_preferred_behaviour('deferredfeedback');
173
        $quba->add_question(question_bank::load_question($questiondata1->id));
174
        $quba->start_all_questions();
175
        question_engine::save_questions_usage_by_activity($quba);
176
 
177
        // Check one usage, question_attempts and step added.
178
        $firstid = $quba->get_id();
179
        $this->assertEquals(1, $DB->count_records('question_usages') - $initialqurows);
180
        $this->assertEquals(1, $DB->count_records('question_attempts') - $initialqarows);
181
        $this->assertEquals(1, $DB->count_records('question_attempt_steps') - $initialqasrows);
182
 
183
        $quba->finish_all_questions();
184
        question_engine::save_questions_usage_by_activity($quba);
185
 
186
        // Check usage id not changed.
187
        $this->assertEquals($firstid, $quba->get_id());
188
 
189
        // Check still one usage, question_attempts, but now two steps.
190
        $this->assertEquals(1, $DB->count_records('question_usages') - $initialqurows);
191
        $this->assertEquals(1, $DB->count_records('question_attempts') - $initialqarows);
192
        $this->assertEquals(2, $DB->count_records('question_attempt_steps') - $initialqasrows);
193
    }
194
 
11 efrain 195
    public function test_repeated_usage_saving_existing_usage(): void {
1 efrain 196
        global $DB;
197
 
198
        $this->resetAfterTest();
199
 
200
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
201
        $cat = $generator->create_question_category();
202
        $questiondata1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
203
 
204
        $initquba = question_engine::make_questions_usage_by_activity('test', \context_system::instance());
205
        $initquba->set_preferred_behaviour('deferredfeedback');
206
        $slot = $initquba->add_question(question_bank::load_question($questiondata1->id));
207
        $initquba->start_all_questions();
208
        question_engine::save_questions_usage_by_activity($initquba);
209
 
210
        $quba = question_engine::load_questions_usage_by_activity($initquba->get_id());
211
 
212
        $initialqurows = $DB->count_records('question_usages');
213
        $initialqarows = $DB->count_records('question_attempts');
214
        $initialqasrows = $DB->count_records('question_attempt_steps');
215
 
216
        $quba->process_all_actions(time(), $quba->prepare_simulated_post_data(
217
                [$slot => ['answer' => 'Frog']]));
218
        question_engine::save_questions_usage_by_activity($quba);
219
 
220
        // Check one usage, question_attempts and step added.
221
        $this->assertEquals(0, $DB->count_records('question_usages') - $initialqurows);
222
        $this->assertEquals(0, $DB->count_records('question_attempts') - $initialqarows);
223
        $this->assertEquals(1, $DB->count_records('question_attempt_steps') - $initialqasrows);
224
 
225
        $quba->finish_all_questions();
226
        question_engine::save_questions_usage_by_activity($quba);
227
 
228
        // Check still one usage, question_attempts, but now two steps.
229
        $this->assertEquals(0, $DB->count_records('question_usages') - $initialqurows);
230
        $this->assertEquals(0, $DB->count_records('question_attempts') - $initialqarows);
231
        $this->assertEquals(2, $DB->count_records('question_attempt_steps') - $initialqasrows);
232
    }
233
 
234
    /**
235
     * Test that database operations on an empty usage work without errors.
236
     */
11 efrain 237
    public function test_save_and_load_an_empty_usage(): void {
1 efrain 238
        $this->resetAfterTest();
239
 
240
        // Create a new usage.
241
        $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance());
242
        $quba->set_preferred_behaviour('deferredfeedback');
243
 
244
        // Save it.
245
        question_engine::save_questions_usage_by_activity($quba);
246
 
247
        // Reload it.
248
        $reloadedquba = question_engine::load_questions_usage_by_activity($quba->get_id());
249
        $this->assertCount(0, $quba->get_slots());
250
 
251
        // Delete it.
252
        question_engine::delete_questions_usage_by_activity($quba->get_id());
253
    }
254
 
255
    public function test_cannot_save_a_step_with_a_missing_state(): void {
256
        global $DB;
257
 
258
        $this->resetAfterTest();
259
 
260
        // Create a question.
261
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
262
        $cat = $generator->create_question_category();
263
        $questiondata = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
264
 
265
        // Create a usage.
266
        $quba = question_engine::make_questions_usage_by_activity('test', \context_system::instance());
267
        $quba->set_preferred_behaviour('deferredfeedback');
268
        $slot = $quba->add_question(question_bank::load_question($questiondata->id));
269
        $quba->start_all_questions();
270
 
271
        // Add a step with a bad state.
272
        $newstep = new \question_attempt_step();
273
        $newstep->set_state(null);
274
        $addstepmethod = new \ReflectionMethod('question_attempt', 'add_step');
275
        $addstepmethod->invoke($quba->get_question_attempt($slot), $newstep);
276
 
277
        // Verify that trying to save this throws an exception.
278
        $this->expectException(\dml_write_exception::class);
279
        question_engine::save_questions_usage_by_activity($quba);
280
    }
281
 
282
    /**
283
     * Test cases for {@see test_get_file_area_name()}.
284
     *
285
     * @return array test cases
286
     */
287
    public function get_file_area_name_cases(): array {
288
        return [
289
            'simple variable' => ['response_attachments', 'response_attachments'],
290
            'behaviour variable' => ['response_5:answer', 'response_5answer'],
291
            'variable with special character' => ['response_5:answer', 'response_5answer'],
292
            'multiple underscores in different places' => ['response_weird____variable__name', 'response_weird_variable_name'],
293
        ];
294
    }
295
 
296
    /**
297
     * Test get_file_area_name.
298
     *
299
     * @covers \question_file_saver::clean_file_area_name
300
     * @dataProvider get_file_area_name_cases
301
     *
302
     * @param string $uncleanedfilearea
303
     * @param string $expectedfilearea
304
     */
305
    public function test_clean_file_area_name(string $uncleanedfilearea, string $expectedfilearea): void {
306
        $this->assertEquals($expectedfilearea, \question_file_saver::clean_file_area_name($uncleanedfilearea));
307
    }
308
}