Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | 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 question_bank;
20
use question_state;
21
 
22
defined('MOODLE_INTERNAL') || die();
23
 
24
global $CFG;
25
require_once(__DIR__ . '/../lib.php');
26
require_once(__DIR__ . '/helpers.php');
27
 
28
/**
29
 * Unit tests for the autosave parts of the {@link question_usage} class.
30
 *
31
 * @package   core_question
32
 * @category  test
33
 * @copyright 2013 The Open University
34
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35
 */
36
class questionusage_autosave_test extends \qbehaviour_walkthrough_test_base {
37
 
38
    public function test_autosave_then_display() {
39
        $this->resetAfterTest();
40
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
41
        $cat = $generator->create_question_category();
42
        $question = $generator->create_question('shortanswer', null,
43
                array('category' => $cat->id));
44
 
45
        // Start attempt at a shortanswer question.
46
        $q = question_bank::load_question($question->id);
47
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
48
 
49
        $this->check_current_state(question_state::$todo);
50
        $this->check_current_mark(null);
51
        $this->check_step_count(1);
52
 
53
        // Process a response and check the expected result.
54
        $this->process_submission(array('answer' => 'first response'));
55
 
56
        $this->check_current_state(question_state::$complete);
57
        $this->check_current_mark(null);
58
        $this->check_step_count(2);
59
        $this->save_quba();
60
 
61
        // Now check how that is re-displayed.
62
        $this->render();
63
        $this->check_output_contains_text_input('answer', 'first response');
64
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
65
 
66
        // Process an autosave.
67
        $this->load_quba();
68
        $this->process_autosave(array('answer' => 'second response'));
69
        $this->check_current_state(question_state::$complete);
70
        $this->check_current_mark(null);
71
        $this->check_step_count(3);
72
        $this->save_quba();
73
 
74
        // Now check how that is re-displayed.
75
        $this->load_quba();
76
        $this->render();
77
        $this->check_output_contains_text_input('answer', 'second response');
78
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
79
 
80
        $this->delete_quba();
81
    }
82
 
83
    public function test_autosave_then_autosave_different_data() {
84
        $this->resetAfterTest();
85
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
86
        $cat = $generator->create_question_category();
87
        $question = $generator->create_question('shortanswer', null,
88
                array('category' => $cat->id));
89
 
90
        // Start attempt at a shortanswer question.
91
        $q = question_bank::load_question($question->id);
92
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
93
 
94
        $this->check_current_state(question_state::$todo);
95
        $this->check_current_mark(null);
96
        $this->check_step_count(1);
97
 
98
        // Process a response and check the expected result.
99
        $this->process_submission(array('answer' => 'first response'));
100
 
101
        $this->check_current_state(question_state::$complete);
102
        $this->check_current_mark(null);
103
        $this->check_step_count(2);
104
        $this->save_quba();
105
 
106
        // Now check how that is re-displayed.
107
        $this->render();
108
        $this->check_output_contains_text_input('answer', 'first response');
109
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
110
 
111
        // Process an autosave.
112
        $this->load_quba();
113
        $this->process_autosave(array('answer' => 'second response'));
114
        $this->check_current_state(question_state::$complete);
115
        $this->check_current_mark(null);
116
        $this->check_step_count(3);
117
        $this->save_quba();
118
 
119
        // Now check how that is re-displayed.
120
        $this->load_quba();
121
        $this->render();
122
        $this->check_output_contains_text_input('answer', 'second response');
123
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
124
 
125
        // Process a second autosave.
126
        $this->load_quba();
127
        $this->process_autosave(array('answer' => 'third response'));
128
        $this->check_current_state(question_state::$complete);
129
        $this->check_current_mark(null);
130
        $this->check_step_count(3);
131
        $this->save_quba();
132
 
133
        // Now check how that is re-displayed.
134
        $this->load_quba();
135
        $this->render();
136
        $this->check_output_contains_text_input('answer', 'third response');
137
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
138
 
139
        $this->delete_quba();
140
    }
141
 
142
    public function test_autosave_then_autosave_same_data() {
143
        $this->resetAfterTest();
144
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
145
        $cat = $generator->create_question_category();
146
        $question = $generator->create_question('shortanswer', null,
147
                array('category' => $cat->id));
148
 
149
        // Start attempt at a shortanswer question.
150
        $q = question_bank::load_question($question->id);
151
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
152
 
153
        $this->check_current_state(question_state::$todo);
154
        $this->check_current_mark(null);
155
        $this->check_step_count(1);
156
 
157
        // Process a response and check the expected result.
158
        $this->process_submission(array('answer' => 'first response'));
159
 
160
        $this->check_current_state(question_state::$complete);
161
        $this->check_current_mark(null);
162
        $this->check_step_count(2);
163
        $this->save_quba();
164
 
165
        // Now check how that is re-displayed.
166
        $this->render();
167
        $this->check_output_contains_text_input('answer', 'first response');
168
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
169
 
170
        // Process an autosave.
171
        $this->load_quba();
172
        $this->process_autosave(array('answer' => 'second response'));
173
        $this->check_current_state(question_state::$complete);
174
        $this->check_current_mark(null);
175
        $this->check_step_count(3);
176
        $this->save_quba();
177
 
178
        // Now check how that is re-displayed.
179
        $this->load_quba();
180
        $this->render();
181
        $this->check_output_contains_text_input('answer', 'second response');
182
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
183
 
184
        $stepid = $this->quba->get_question_attempt($this->slot)->get_last_step()->get_id();
185
 
186
        // Process a second autosave.
187
        $this->load_quba();
188
        $this->process_autosave(array('answer' => 'second response'));
189
        $this->check_current_state(question_state::$complete);
190
        $this->check_current_mark(null);
191
        $this->check_step_count(3);
192
        $this->save_quba();
193
 
194
        // Try to check it is really the same step
195
        $newstepid = $this->quba->get_question_attempt($this->slot)->get_last_step()->get_id();
196
        $this->assertEquals($stepid, $newstepid);
197
 
198
        // Now check how that is re-displayed.
199
        $this->load_quba();
200
        $this->render();
201
        $this->check_output_contains_text_input('answer', 'second response');
202
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
203
 
204
        $this->delete_quba();
205
    }
206
 
207
    public function test_autosave_then_autosave_original_data() {
208
        $this->resetAfterTest();
209
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
210
        $cat = $generator->create_question_category();
211
        $question = $generator->create_question('shortanswer', null,
212
                array('category' => $cat->id));
213
 
214
        // Start attempt at a shortanswer question.
215
        $q = question_bank::load_question($question->id);
216
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
217
 
218
        $this->check_current_state(question_state::$todo);
219
        $this->check_current_mark(null);
220
        $this->check_step_count(1);
221
 
222
        // Process a response and check the expected result.
223
        $this->process_submission(array('answer' => 'first response'));
224
 
225
        $this->check_current_state(question_state::$complete);
226
        $this->check_current_mark(null);
227
        $this->check_step_count(2);
228
        $this->save_quba();
229
 
230
        // Now check how that is re-displayed.
231
        $this->render();
232
        $this->check_output_contains_text_input('answer', 'first response');
233
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
234
 
235
        // Process an autosave.
236
        $this->load_quba();
237
        $this->process_autosave(array('answer' => 'second response'));
238
        $this->check_current_state(question_state::$complete);
239
        $this->check_current_mark(null);
240
        $this->check_step_count(3);
241
        $this->save_quba();
242
 
243
        // Now check how that is re-displayed.
244
        $this->load_quba();
245
        $this->render();
246
        $this->check_output_contains_text_input('answer', 'second response');
247
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
248
 
249
        // Process a second autosave saving the original response.
250
        // This should remove the autosave step.
251
        $this->load_quba();
252
        $this->process_autosave(array('answer' => 'first response'));
253
        $this->check_current_state(question_state::$complete);
254
        $this->check_current_mark(null);
255
        $this->check_step_count(2);
256
        $this->save_quba();
257
 
258
        // Now check how that is re-displayed.
259
        $this->load_quba();
260
        $this->render();
261
        $this->check_output_contains_text_input('answer', 'first response');
262
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
263
 
264
        $this->delete_quba();
265
    }
266
 
267
    public function test_autosave_then_real_save() {
268
        $this->resetAfterTest();
269
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
270
        $cat = $generator->create_question_category();
271
        $question = $generator->create_question('shortanswer', null,
272
                array('category' => $cat->id));
273
 
274
        // Start attempt at a shortanswer question.
275
        $q = question_bank::load_question($question->id);
276
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
277
 
278
        $this->check_current_state(question_state::$todo);
279
        $this->check_current_mark(null);
280
        $this->check_step_count(1);
281
 
282
        // Process a response and check the expected result.
283
        $this->process_submission(array('answer' => 'first response'));
284
 
285
        $this->check_current_state(question_state::$complete);
286
        $this->check_current_mark(null);
287
        $this->check_step_count(2);
288
        $this->save_quba();
289
 
290
        // Now check how that is re-displayed.
291
        $this->render();
292
        $this->check_output_contains_text_input('answer', 'first response');
293
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
294
 
295
        // Process an autosave.
296
        $this->load_quba();
297
        $this->process_autosave(array('answer' => 'second response'));
298
        $this->check_current_state(question_state::$complete);
299
        $this->check_current_mark(null);
300
        $this->check_step_count(3);
301
        $this->save_quba();
302
 
303
        // Now check how that is re-displayed.
304
        $this->load_quba();
305
        $this->render();
306
        $this->check_output_contains_text_input('answer', 'second response');
307
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
308
 
309
        // Now save for real a third response.
310
        $this->process_submission(array('answer' => 'third response'));
311
 
312
        $this->check_current_state(question_state::$complete);
313
        $this->check_current_mark(null);
314
        $this->check_step_count(3);
315
        $this->save_quba();
316
 
317
        // Now check how that is re-displayed.
318
        $this->render();
319
        $this->check_output_contains_text_input('answer', 'third response');
320
        $this->check_output_contains_hidden_input(':sequencecheck', 3);
321
    }
322
 
323
    public function test_autosave_then_real_save_same() {
324
        $this->resetAfterTest();
325
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
326
        $cat = $generator->create_question_category();
327
        $question = $generator->create_question('shortanswer', null,
328
                array('category' => $cat->id));
329
 
330
        // Start attempt at a shortanswer question.
331
        $q = question_bank::load_question($question->id);
332
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
333
 
334
        $this->check_current_state(question_state::$todo);
335
        $this->check_current_mark(null);
336
        $this->check_step_count(1);
337
 
338
        // Process a response and check the expected result.
339
        $this->process_submission(array('answer' => 'first response'));
340
 
341
        $this->check_current_state(question_state::$complete);
342
        $this->check_current_mark(null);
343
        $this->check_step_count(2);
344
        $this->save_quba();
345
 
346
        // Now check how that is re-displayed.
347
        $this->render();
348
        $this->check_output_contains_text_input('answer', 'first response');
349
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
350
 
351
        // Process an autosave.
352
        $this->load_quba();
353
        $this->process_autosave(array('answer' => 'second response'));
354
        $this->check_current_state(question_state::$complete);
355
        $this->check_current_mark(null);
356
        $this->check_step_count(3);
357
        $this->save_quba();
358
 
359
        // Now check how that is re-displayed.
360
        $this->load_quba();
361
        $this->render();
362
        $this->check_output_contains_text_input('answer', 'second response');
363
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
364
 
365
        // Now save for real of the same response.
366
        $this->process_submission(array('answer' => 'second response'));
367
 
368
        $this->check_current_state(question_state::$complete);
369
        $this->check_current_mark(null);
370
        $this->check_step_count(3);
371
        $this->save_quba();
372
 
373
        // Now check how that is re-displayed.
374
        $this->render();
375
        $this->check_output_contains_text_input('answer', 'second response');
376
        $this->check_output_contains_hidden_input(':sequencecheck', 3);
377
    }
378
 
379
    public function test_autosave_then_submit() {
380
        $this->resetAfterTest();
381
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
382
        $cat = $generator->create_question_category();
383
        $question = $generator->create_question('shortanswer', null,
384
                array('category' => $cat->id));
385
 
386
        // Start attempt at a shortanswer question.
387
        $q = question_bank::load_question($question->id);
388
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
389
 
390
        $this->check_current_state(question_state::$todo);
391
        $this->check_current_mark(null);
392
        $this->check_step_count(1);
393
 
394
        // Process a response and check the expected result.
395
        $this->process_submission(array('answer' => 'first response'));
396
 
397
        $this->check_current_state(question_state::$complete);
398
        $this->check_current_mark(null);
399
        $this->check_step_count(2);
400
        $this->save_quba();
401
 
402
        // Now check how that is re-displayed.
403
        $this->render();
404
        $this->check_output_contains_text_input('answer', 'first response');
405
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
406
 
407
        // Process an autosave.
408
        $this->load_quba();
409
        $this->process_autosave(array('answer' => 'second response'));
410
        $this->check_current_state(question_state::$complete);
411
        $this->check_current_mark(null);
412
        $this->check_step_count(3);
413
        $this->save_quba();
414
 
415
        // Now check how that is re-displayed.
416
        $this->load_quba();
417
        $this->render();
418
        $this->check_output_contains_text_input('answer', 'second response');
419
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
420
 
421
        // Now submit a third response.
422
        $this->process_submission(array('answer' => 'third response'));
423
        $this->quba->finish_all_questions();
424
 
425
        $this->check_current_state(question_state::$gradedwrong);
426
        $this->check_current_mark(0);
427
        $this->check_step_count(4);
428
        $this->save_quba();
429
 
430
        // Now check how that is re-displayed.
431
        $this->render();
432
        $this->check_output_contains_text_input('answer', 'third response', false);
433
        $this->check_output_contains_hidden_input(':sequencecheck', 4);
434
    }
435
 
436
    public function test_autosave_and_save_concurrently() {
437
        // This test simulates the following scenario:
438
        // 1. Student looking at a page of the quiz, and edits a field then waits.
439
        // 2. Autosave starts.
440
        // 3. Student immediately clicks Next, which submits the current page.
441
        // In this situation, the real submit should beat the autosave, even
442
        // thought they happen concurrently. We simulate this by opening a
443
        // second db connections.
444
        global $DB;
445
 
446
        // Open second connection
447
        $cfg = $DB->export_dbconfig();
448
        if (!isset($cfg->dboptions)) {
449
            $cfg->dboptions = array();
450
        }
451
        $DB2 = \moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
452
        $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
453
 
454
        // Since we need to commit our transactions in a given order, close the
455
        // standard unit test transaction.
456
        $this->preventResetByRollback();
457
 
458
        $this->resetAfterTest();
459
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
460
        $cat = $generator->create_question_category();
461
        $question = $generator->create_question('shortanswer', null,
462
                array('category' => $cat->id));
463
 
464
        // Start attempt at a shortanswer question.
465
        $q = question_bank::load_question($question->id);
466
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
467
        $this->save_quba();
468
 
469
        $this->check_current_state(question_state::$todo);
470
        $this->check_current_mark(null);
471
        $this->check_step_count(1);
472
 
473
        // Start to process an autosave on $DB.
474
        $transaction = $DB->start_delegated_transaction();
475
        $this->load_quba($DB);
476
        $this->process_autosave(array('answer' => 'autosaved response'));
477
        $this->check_current_state(question_state::$complete);
478
        $this->check_current_mark(null);
479
        $this->check_step_count(2);
480
        $this->save_quba($DB); // Don't commit the transaction yet.
481
 
482
        // Now process a real submit on $DB2 (using a different response).
483
        $transaction2 = $DB2->start_delegated_transaction();
484
        $this->load_quba($DB2);
485
        $this->process_submission(array('answer' => 'real response'));
486
        $this->check_current_state(question_state::$complete);
487
        $this->check_current_mark(null);
488
        $this->check_step_count(2);
489
 
490
        // Now commit the first transaction.
491
        $transaction->allow_commit();
492
 
493
        // Now commit the other transaction.
494
        $this->save_quba($DB2);
495
        $transaction2->allow_commit();
496
 
497
        // Now re-load and check how that is re-displayed.
498
        $this->load_quba();
499
        $this->check_current_state(question_state::$complete);
500
        $this->check_current_mark(null);
501
        $this->check_step_count(2);
502
        $this->render();
503
        $this->check_output_contains_text_input('answer', 'real response');
504
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
505
 
506
        $DB2->dispose();
507
    }
508
 
509
    public function test_concurrent_autosaves() {
510
        // This test simulates the following scenario:
511
        // 1. Student opens  a page of the quiz in two separate browser.
512
        // 2. Autosave starts in both at the same time.
513
        // In this situation, one autosave will work, and the other one will
514
        // get a unique key violation error. This is OK.
515
        global $DB;
516
 
517
        // Open second connection
518
        $cfg = $DB->export_dbconfig();
519
        if (!isset($cfg->dboptions)) {
520
            $cfg->dboptions = array();
521
        }
522
        $DB2 = \moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
523
        $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
524
 
525
        // Since we need to commit our transactions in a given order, close the
526
        // standard unit test transaction.
527
        $this->preventResetByRollback();
528
 
529
        $this->resetAfterTest();
530
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
531
        $cat = $generator->create_question_category();
532
        $question = $generator->create_question('shortanswer', null,
533
                array('category' => $cat->id));
534
 
535
        // Start attempt at a shortanswer question.
536
        $q = question_bank::load_question($question->id);
537
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
538
        $this->save_quba();
539
 
540
        $this->check_current_state(question_state::$todo);
541
        $this->check_current_mark(null);
542
        $this->check_step_count(1);
543
 
544
        // Start to process an autosave on $DB.
545
        $transaction = $DB->start_delegated_transaction();
546
        $this->load_quba($DB);
547
        $this->process_autosave(array('answer' => 'autosaved response 1'));
548
        $this->check_current_state(question_state::$complete);
549
        $this->check_current_mark(null);
550
        $this->check_step_count(2);
551
        $this->save_quba($DB); // Don't commit the transaction yet.
552
 
553
        // Now process a real submit on $DB2 (using a different response).
554
        $transaction2 = $DB2->start_delegated_transaction();
555
        $this->load_quba($DB2);
556
        $this->process_autosave(array('answer' => 'autosaved response 2'));
557
        $this->check_current_state(question_state::$complete);
558
        $this->check_current_mark(null);
559
        $this->check_step_count(2);
560
 
561
        // Now commit the first transaction.
562
        $transaction->allow_commit();
563
 
564
        // Now commit the other transaction.
565
        $this->expectException('dml_write_exception');
566
        $this->save_quba($DB2);
567
        $transaction2->allow_commit();
568
 
569
        // Now re-load and check how that is re-displayed.
570
        $this->load_quba();
571
        $this->check_current_state(question_state::$complete);
572
        $this->check_current_mark(null);
573
        $this->check_step_count(2);
574
        $this->render();
575
        $this->check_output_contains_text_input('answer', 'autosaved response 1');
576
        $this->check_output_contains_hidden_input(':sequencecheck', 1);
577
 
578
        $DB2->dispose();
579
    }
580
 
581
    public function test_autosave_with_wrong_seq_number_ignored() {
582
        $this->resetAfterTest();
583
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
584
        $cat = $generator->create_question_category();
585
        $question = $generator->create_question('shortanswer', null,
586
                array('category' => $cat->id));
587
 
588
        // Start attempt at a shortanswer question.
589
        $q = question_bank::load_question($question->id);
590
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
591
 
592
        $this->check_current_state(question_state::$todo);
593
        $this->check_current_mark(null);
594
        $this->check_step_count(1);
595
 
596
        // Process a response and check the expected result.
597
        $this->process_submission(array('answer' => 'first response'));
598
 
599
        $this->check_current_state(question_state::$complete);
600
        $this->check_current_mark(null);
601
        $this->check_step_count(2);
602
        $this->save_quba();
603
 
604
        // Now check how that is re-displayed.
605
        $this->render();
606
        $this->check_output_contains_text_input('answer', 'first response');
607
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
608
 
609
        // Process an autosave with a sequence number 1 too small (so from the past).
610
        $this->load_quba();
611
        $postdata = $this->response_data_to_post(array('answer' => 'obsolete response'));
612
        $postdata[$this->quba->get_field_prefix($this->slot) . ':sequencecheck'] = $this->get_question_attempt()->get_sequence_check_count() - 1;
613
        $this->quba->process_all_autosaves(null, $postdata);
614
        $this->check_current_state(question_state::$complete);
615
        $this->check_current_mark(null);
616
        $this->check_step_count(2);
617
        $this->save_quba();
618
 
619
        // Now check how that is re-displayed.
620
        $this->load_quba();
621
        $this->render();
622
        $this->check_output_contains_text_input('answer', 'first response');
623
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
624
 
625
        $this->delete_quba();
626
    }
627
 
628
    public function test_finish_with_unhandled_autosave_data() {
629
        $this->resetAfterTest();
630
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
631
        $cat = $generator->create_question_category();
632
        $question = $generator->create_question('shortanswer', null,
633
                array('category' => $cat->id));
634
 
635
        // Start attempt at a shortanswer question.
636
        $q = question_bank::load_question($question->id);
637
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
638
 
639
        $this->check_current_state(question_state::$todo);
640
        $this->check_current_mark(null);
641
        $this->check_step_count(1);
642
 
643
        // Process a response and check the expected result.
644
        $this->process_submission(array('answer' => 'cat'));
645
 
646
        $this->check_current_state(question_state::$complete);
647
        $this->check_current_mark(null);
648
        $this->check_step_count(2);
649
        $this->save_quba();
650
 
651
        // Now check how that is re-displayed.
652
        $this->render();
653
        $this->check_output_contains_text_input('answer', 'cat');
654
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
655
 
656
        // Process an autosave.
657
        $this->load_quba();
658
        $this->process_autosave(array('answer' => 'frog'));
659
        $this->check_current_state(question_state::$complete);
660
        $this->check_current_mark(null);
661
        $this->check_step_count(3);
662
        $this->save_quba();
663
 
664
        // Now check how that is re-displayed.
665
        $this->load_quba();
666
        $this->render();
667
        $this->check_output_contains_text_input('answer', 'frog');
668
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
669
 
670
        // Now finishe the attempt, without having done anything since the autosave.
671
        $this->finish();
672
        $this->save_quba();
673
 
674
        // Now check how that has been graded and is re-displayed.
675
        $this->load_quba();
676
        $this->check_current_state(question_state::$gradedright);
677
        $this->check_current_mark(1);
678
        $this->render();
679
        $this->check_output_contains_text_input('answer', 'frog', false);
680
        $this->check_output_contains_hidden_input(':sequencecheck', 4);
681
 
682
        $this->delete_quba();
683
    }
684
 
685
    /**
686
     * Test that regrading doesn't convert autosave steps to finished steps.
687
     * This can result in students loosing data (due to question_out_of_sequence_exception) if a teacher
688
     * regrades an attempt while it is in progress.
689
     */
690
    public function test_autosave_and_regrade_then_display() {
691
        $this->resetAfterTest();
692
        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
693
        $cat = $generator->create_question_category();
694
        $question = $generator->create_question('shortanswer', null,
695
                array('category' => $cat->id));
696
 
697
        // Start attempt at a shortanswer question.
698
        $q = question_bank::load_question($question->id);
699
        $this->start_attempt_at_question($q, 'deferredfeedback', 1);
700
 
701
        $this->check_current_state(question_state::$todo);
702
        $this->check_current_mark(null);
703
        $this->check_step_count(1);
704
 
705
        // First see if the starting sequence is right.
706
        $this->render();
707
        $this->check_output_contains_hidden_input(':sequencecheck', 1);
708
 
709
        // Add a submission.
710
        $this->process_submission(array('answer' => 'first response'));
711
        $this->save_quba();
712
 
713
        // Check the submission and that the sequence went up.
714
        $this->render();
715
        $this->check_output_contains_text_input('answer', 'first response');
716
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
717
        $this->assertFalse($this->get_question_attempt()->has_autosaved_step());
718
 
719
        // Add a autosave response.
720
        $this->load_quba();
721
        $this->process_autosave(array('answer' => 'second response'));
722
        $this->save_quba();
723
 
724
        // Confirm that the autosave value shows up, but that the sequence hasn't increased.
725
        $this->render();
726
        $this->check_output_contains_text_input('answer', 'second response');
727
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
728
        $this->assertTrue($this->get_question_attempt()->has_autosaved_step());
729
 
730
        // Call regrade.
731
        $this->load_quba();
732
        $this->quba->regrade_all_questions();
733
        $this->save_quba();
734
 
735
        // Check and see if the autosave response is still there, that the sequence didn't increase,
736
        // and that there is an autosave step.
737
        $this->load_quba();
738
        $this->render();
739
        $this->check_output_contains_text_input('answer', 'second response');
740
        $this->check_output_contains_hidden_input(':sequencecheck', 2);
741
        $this->assertTrue($this->get_question_attempt()->has_autosaved_step());
742
 
743
        $this->delete_quba();
744
    }
745
 
746
    protected function tearDown(): void {
747
        // This test relies on the destructor for the second DB connection being called before running the next test.
748
        // Without this change - there will be unit test failures on "some" DBs (MySQL).
749
        gc_collect_cycles();
750
    }
751
}