|
| 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 tool_corruptpdfdetector; |
| 18 | + |
| 19 | +use tool_corruptpdfdetector\task\fix_assignments; |
| 20 | +/** |
| 21 | + * Unit tests for the fix_assignments scheduled task. |
| 22 | + * |
| 23 | + * @covers \tool_corruptpdfdetector\task\fix_assignments |
| 24 | + * @package tool_corruptpdfdetector |
| 25 | + * @copyright Catalyst IT |
| 26 | + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
| 27 | + */ |
| 28 | +final class task_fix_assignments_test extends \advanced_testcase { |
| 29 | + /** |
| 30 | + * Helper: insert a minimal record into tool_corruptpdfdetector_assigns. |
| 31 | + * |
| 32 | + * @param string $contenthash SHA-1 hash stored in the filename column. |
| 33 | + * @param bool $fixed Whether the record is already fixed. |
| 34 | + * @return int Inserted record ID. |
| 35 | + */ |
| 36 | + private function insert_assigns_record(string $contenthash, bool $fixed = false): int { |
| 37 | + global $DB; |
| 38 | + |
| 39 | + $record = (object)[ |
| 40 | + 'assignid' => 1, |
| 41 | + 'submissionid' => random_int(1, 99999), |
| 42 | + 'coursename' => 'Test course', |
| 43 | + 'assignname' => 'Test assignment', |
| 44 | + 'userfullname' => 'Test User', |
| 45 | + 'email' => 'test@example.com', |
| 46 | + 'filename' => $contenthash, |
| 47 | + 'message' => 'Corrupt PDF detected', |
| 48 | + 'submitted' => time(), |
| 49 | + 'detected' => time(), |
| 50 | + 'fixed' => (int) $fixed, |
| 51 | + ]; |
| 52 | + |
| 53 | + return $DB->insert_record('tool_corruptpdfdetector_assigns', $record); |
| 54 | + } |
| 55 | + |
| 56 | + /** |
| 57 | + * Helper: insert a minimal record into the files table with filename = 'combined.pdf'. |
| 58 | + * |
| 59 | + * @param string $contenthash Content hash matching the filename column in assigns. |
| 60 | + * @param string $filearea 'combined' or 'partial'. |
| 61 | + * @return int Inserted record ID. |
| 62 | + */ |
| 63 | + private function insert_file_record(string $contenthash, string $filearea = 'combined'): int { |
| 64 | + global $DB; |
| 65 | + |
| 66 | + $record = (object)[ |
| 67 | + 'contenthash' => $contenthash, |
| 68 | + 'pathnamehash' => sha1(uniqid('', true)), |
| 69 | + 'contextid' => \context_system::instance()->id, |
| 70 | + 'component' => 'assignfeedback_editpdf', |
| 71 | + 'filearea' => $filearea, |
| 72 | + 'itemid' => 1, |
| 73 | + 'filepath' => '/', |
| 74 | + 'filename' => 'combined.pdf', |
| 75 | + 'userid' => 0, |
| 76 | + 'filesize' => 12345, |
| 77 | + 'mimetype' => 'application/pdf', |
| 78 | + 'status' => 0, |
| 79 | + 'source' => null, |
| 80 | + 'author' => null, |
| 81 | + 'license' => null, |
| 82 | + 'timecreated' => time(), |
| 83 | + 'timemodified' => time(), |
| 84 | + 'sortorder' => 0, |
| 85 | + 'referencefileid' => null, |
| 86 | + ]; |
| 87 | + |
| 88 | + return $DB->insert_record('files', $record); |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * Verify that get_name() returns the expected localised string. |
| 93 | + */ |
| 94 | + public function test_task_name(): void { |
| 95 | + $task = new fix_assignments(); |
| 96 | + $this->assertEquals( |
| 97 | + get_string('task_fix_assignments', 'tool_corruptpdfdetector'), |
| 98 | + $task->get_name() |
| 99 | + ); |
| 100 | + } |
| 101 | + |
| 102 | + /** |
| 103 | + * execute() should complete silently when there are no unfixed records. |
| 104 | + */ |
| 105 | + public function test_execute_does_nothing_when_table_is_empty(): void { |
| 106 | + global $DB; |
| 107 | + $this->resetAfterTest(); |
| 108 | + |
| 109 | + $task = new fix_assignments(); |
| 110 | + $task->execute(); |
| 111 | + |
| 112 | + $this->assertEquals(0, $DB->count_records('tool_corruptpdfdetector_assigns')); |
| 113 | + } |
| 114 | + |
| 115 | + /** |
| 116 | + * execute() should mark every unfixed record as fixed after running. |
| 117 | + */ |
| 118 | + public function test_execute_marks_unfixed_records_as_fixed(): void { |
| 119 | + global $DB; |
| 120 | + $this->resetAfterTest(); |
| 121 | + |
| 122 | + $id1 = $this->insert_assigns_record(sha1('content1'), false); |
| 123 | + $id2 = $this->insert_assigns_record(sha1('content2'), false); |
| 124 | + |
| 125 | + $task = new fix_assignments(); |
| 126 | + $task->execute(); |
| 127 | + |
| 128 | + $record1 = $DB->get_record('tool_corruptpdfdetector_assigns', ['id' => $id1]); |
| 129 | + $record2 = $DB->get_record('tool_corruptpdfdetector_assigns', ['id' => $id2]); |
| 130 | + |
| 131 | + $this->assertEquals(1, $record1->fixed, 'First record should be marked fixed.'); |
| 132 | + $this->assertEquals(1, $record2->fixed, 'Second record should be marked fixed.'); |
| 133 | + } |
| 134 | + |
| 135 | + /** |
| 136 | + * execute() should delete the combined.pdf file (filearea = 'combined') for each unfixed record. |
| 137 | + */ |
| 138 | + public function test_execute_deletes_combined_pdf_file(): void { |
| 139 | + global $DB; |
| 140 | + $this->resetAfterTest(); |
| 141 | + |
| 142 | + $contenthash = sha1('combinedcontent'); |
| 143 | + $this->insert_assigns_record($contenthash, false); |
| 144 | + $fileid = $this->insert_file_record($contenthash, 'combined'); |
| 145 | + |
| 146 | + $this->assertTrue( |
| 147 | + $DB->record_exists('files', ['id' => $fileid]), |
| 148 | + 'File record should exist before task runs.' |
| 149 | + ); |
| 150 | + |
| 151 | + $task = new fix_assignments(); |
| 152 | + $task->execute(); |
| 153 | + |
| 154 | + $this->assertFalse( |
| 155 | + $DB->record_exists('files', ['id' => $fileid]), |
| 156 | + 'Combined PDF file record should be deleted after task runs.' |
| 157 | + ); |
| 158 | + } |
| 159 | + |
| 160 | + /** |
| 161 | + * execute() should also delete the partial combined.pdf file (filearea = 'partial'). |
| 162 | + */ |
| 163 | + public function test_execute_deletes_partial_pdf_file(): void { |
| 164 | + global $DB; |
| 165 | + $this->resetAfterTest(); |
| 166 | + |
| 167 | + $contenthash = sha1('partialcontent'); |
| 168 | + $this->insert_assigns_record($contenthash, false); |
| 169 | + $fileid = $this->insert_file_record($contenthash, 'partial'); |
| 170 | + |
| 171 | + $task = new fix_assignments(); |
| 172 | + $task->execute(); |
| 173 | + |
| 174 | + $this->assertFalse( |
| 175 | + $DB->record_exists('files', ['id' => $fileid]), |
| 176 | + 'Partial PDF file record should be deleted after task runs.' |
| 177 | + ); |
| 178 | + } |
| 179 | + |
| 180 | + /** |
| 181 | + * execute() must NOT delete files unrelated to the corrupt submission |
| 182 | + * (different contenthash, or a different filename in the same filearea). |
| 183 | + */ |
| 184 | + public function test_execute_does_not_delete_unrelated_files(): void { |
| 185 | + global $DB; |
| 186 | + $this->resetAfterTest(); |
| 187 | + |
| 188 | + $contenthash = sha1('targetcontent'); |
| 189 | + $otherhash = sha1('othercontent'); |
| 190 | + $this->insert_assigns_record($contenthash, false); |
| 191 | + |
| 192 | + // File with a different contenthash – must be preserved. |
| 193 | + $differenthashfileid = $this->insert_file_record($otherhash); |
| 194 | + |
| 195 | + // File with the right hash but a different filename – must be preserved. |
| 196 | + $differentnamerecord = (object)[ |
| 197 | + 'contenthash' => $contenthash, |
| 198 | + 'pathnamehash' => sha1(uniqid('', true)), |
| 199 | + 'contextid' => \context_system::instance()->id, |
| 200 | + 'component' => 'assignfeedback_editpdf', |
| 201 | + 'filearea' => 'combined', |
| 202 | + 'itemid' => 1, |
| 203 | + 'filepath' => '/', |
| 204 | + 'filename' => 'other.pdf', |
| 205 | + 'userid' => 0, |
| 206 | + 'filesize' => 100, |
| 207 | + 'mimetype' => 'application/pdf', |
| 208 | + 'status' => 0, |
| 209 | + 'source' => null, |
| 210 | + 'author' => null, |
| 211 | + 'license' => null, |
| 212 | + 'timecreated' => time(), |
| 213 | + 'timemodified' => time(), |
| 214 | + 'sortorder' => 0, |
| 215 | + 'referencefileid' => null, |
| 216 | + ]; |
| 217 | + $differentnamefileid = $DB->insert_record('files', $differentnamerecord); |
| 218 | + |
| 219 | + $task = new fix_assignments(); |
| 220 | + $task->execute(); |
| 221 | + |
| 222 | + $this->assertTrue( |
| 223 | + $DB->record_exists('files', ['id' => $differenthashfileid]), |
| 224 | + 'File with a different hash should not be deleted.' |
| 225 | + ); |
| 226 | + $this->assertTrue( |
| 227 | + $DB->record_exists('files', ['id' => $differentnamefileid]), |
| 228 | + 'File with a different filename should not be deleted.' |
| 229 | + ); |
| 230 | + } |
| 231 | + |
| 232 | + /** |
| 233 | + * execute() should leave already-fixed records (fixed = 1) completely untouched. |
| 234 | + */ |
| 235 | + public function test_execute_skips_already_fixed_records(): void { |
| 236 | + global $DB; |
| 237 | + $this->resetAfterTest(); |
| 238 | + |
| 239 | + $contenthash = sha1('fixedcontent'); |
| 240 | + $fixedid = $this->insert_assigns_record($contenthash, true); |
| 241 | + $fileid = $this->insert_file_record($contenthash); |
| 242 | + |
| 243 | + $task = new fix_assignments(); |
| 244 | + $task->execute(); |
| 245 | + |
| 246 | + $this->assertTrue( |
| 247 | + $DB->record_exists('files', ['id' => $fileid]), |
| 248 | + 'File for an already-fixed record should not be deleted.' |
| 249 | + ); |
| 250 | + |
| 251 | + $record = $DB->get_record('tool_corruptpdfdetector_assigns', ['id' => $fixedid]); |
| 252 | + $this->assertEquals(1, $record->fixed); |
| 253 | + } |
| 254 | + |
| 255 | + /** |
| 256 | + * execute() processes unfixed records while leaving already-fixed records intact. |
| 257 | + */ |
| 258 | + public function test_execute_mixed_fixed_and_unfixed_records(): void { |
| 259 | + global $DB; |
| 260 | + $this->resetAfterTest(); |
| 261 | + |
| 262 | + $unfixedhash = sha1('unfixedcontent'); |
| 263 | + $fixedhash = sha1('alreadyfixedcontent'); |
| 264 | + |
| 265 | + $unfixedid = $this->insert_assigns_record($unfixedhash); |
| 266 | + $fixedid = $this->insert_assigns_record($fixedhash, true); |
| 267 | + |
| 268 | + $unfixedfileid = $this->insert_file_record($unfixedhash); |
| 269 | + $fixedfileid = $this->insert_file_record($fixedhash); |
| 270 | + |
| 271 | + $task = new fix_assignments(); |
| 272 | + $task->execute(); |
| 273 | + |
| 274 | + // Unfixed record – should now be fixed, its file deleted. |
| 275 | + $unfixedrecord = $DB->get_record('tool_corruptpdfdetector_assigns', ['id' => $unfixedid]); |
| 276 | + $this->assertEquals(1, $unfixedrecord->fixed, 'Unfixed record should now be marked fixed.'); |
| 277 | + $this->assertFalse( |
| 278 | + $DB->record_exists('files', ['id' => $unfixedfileid]), |
| 279 | + 'File for the unfixed record should be deleted.' |
| 280 | + ); |
| 281 | + |
| 282 | + // Already-fixed record – unchanged, file retained. |
| 283 | + $fixedrecord = $DB->get_record('tool_corruptpdfdetector_assigns', ['id' => $fixedid]); |
| 284 | + $this->assertEquals(1, $fixedrecord->fixed, 'Already-fixed record should remain fixed.'); |
| 285 | + $this->assertTrue( |
| 286 | + $DB->record_exists('files', ['id' => $fixedfileid]), |
| 287 | + 'File for the already-fixed record should not be deleted.' |
| 288 | + ); |
| 289 | + } |
| 290 | +} |
0 commit comments