From fac7142034fc8dcce96abb4e92e488c41b841f66 Mon Sep 17 00:00:00 2001 From: Markus Heck Date: Sat, 11 May 2024 03:39:14 +0200 Subject: [PATCH] refactor code - a lot more code now uses the repository pattern - added tests for lib.php --- .../restore_adleradaptivity_stepslib.php | 41 ++-- .../db/adleradaptivity_attempt_repository.php | 34 +++- .../adleradaptivity_question_repository.php | 14 ++ .../local/db/adleradaptivity_repository.php | 20 ++ .../db/adleradaptivity_task_repository.php | 22 +++ classes/local/db/moodle_core_repository.php | 41 ++++ classes/local/helpers.php | 62 +----- classes/local/output/pages/view_page.php | 5 +- lib.php | 43 +++-- tests/external/external_test_helpers.php | 4 +- tests/generator/lib.php | 7 + tests/lib_test.php | 177 ++++++++++++++++++ 12 files changed, 379 insertions(+), 91 deletions(-) create mode 100644 classes/local/db/moodle_core_repository.php create mode 100644 tests/lib_test.php diff --git a/backup/moodle2/restore_adleradaptivity_stepslib.php b/backup/moodle2/restore_adleradaptivity_stepslib.php index 7ad3da8..571d59b 100644 --- a/backup/moodle2/restore_adleradaptivity_stepslib.php +++ b/backup/moodle2/restore_adleradaptivity_stepslib.php @@ -1,12 +1,31 @@ adleradaptivity_repository = new adleradaptivity_repository(); + $this->adleradaptivity_task_repository = new adleradaptivity_task_repository(); + $this->adleradaptivity_question_repository = new adleradaptivity_question_repository(); + $this->adleradaptivity_attempt_repository = new adleradaptivity_attempt_repository(); + $this->moodle_core_repository = new moodle_core_repository(); + } + protected function define_structure() { $paths = []; $userinfo = $this->get_setting_value('userinfo'); @@ -33,8 +52,6 @@ protected function define_structure() { } protected function process_adleradaptivity($data) { - global $DB; - $data = (object)$data; $data->course = $this->get_courseid(); @@ -44,32 +61,27 @@ protected function process_adleradaptivity($data) { } // insert the adleradaptivity record - $newitemid = $DB->insert_record('adleradaptivity', $data); + $newitemid = $this->adleradaptivity_repository->create_adleradaptivity($data); // immediately after inserting "activity" record, call this $this->apply_activity_instance($newitemid); } protected function process_task($data) { - global $DB; - $data = (object)$data; $oldid = $data->id; $data->adleradaptivity_id = $this->get_new_parentid('adleradaptivity'); - $newitemid = $DB->insert_record('adleradaptivity_tasks', $data); + $newitemid = $this->adleradaptivity_task_repository->create_task($data); $this->set_mapping('task', $oldid, $newitemid); } protected function process_question($data) { - global $DB; - $data = (object)$data; $data->adleradaptivity_task_id = $this->get_new_parentid("task"); - - $newitemid = $DB->insert_record('adleradaptivity_questions', $data); + $newitemid = $this->adleradaptivity_question_repository->create_question($data); $this->set_mapping('question', $data->id, $newitemid); } @@ -81,14 +93,13 @@ protected function process_question($data) { * @param array $data the data from the XML file. */ public function process_question_reference($data) { - global $DB; $data = (object)$data; $data->usingcontextid = $this->get_mappingid('context', $data->usingcontextid); $data->itemid = $this->get_new_parentid('question'); if ($entry = $this->get_mappingid('question_bank_entry', $data->questionbankentryid)) { $data->questionbankentryid = $entry; } - $DB->insert_record('question_references', $data); + $this->moodle_core_repository->create_question_reference($data); } protected function after_execute() { @@ -119,8 +130,6 @@ protected function process_adleradaptivity_attempt($data) { * @param int $newusageid the id of the newly created question usage. */ protected function inform_new_usage_id($newusageid) { - global $DB; - $data = $this->current_adleradaptivity_attempt; if ($data === null) { return; @@ -129,7 +138,7 @@ protected function inform_new_usage_id($newusageid) { $oldid = $data->id; $data->attempt_id = $newusageid; - $newitemid = $DB->insert_record('adleradaptivity_attempts', $data); + $newitemid = $this->adleradaptivity_attempt_repository->create_adleradaptivity_attempt($data); // Save adleradaptivity attempt id mapping $this->set_mapping('adleradaptivity', $oldid, $newitemid); diff --git a/classes/local/db/adleradaptivity_attempt_repository.php b/classes/local/db/adleradaptivity_attempt_repository.php index 81652fd..849c82a 100644 --- a/classes/local/db/adleradaptivity_attempt_repository.php +++ b/classes/local/db/adleradaptivity_attempt_repository.php @@ -2,8 +2,9 @@ namespace mod_adleradaptivity\local\db; +use context_module; use dml_exception; -use moodle_database; +use dml_missing_record_exception; use stdClass; class adleradaptivity_attempt_repository extends base_repository { @@ -18,6 +19,13 @@ public function get_adleradaptivity_attempt_by_quba_id(int $attempt_id): stdClas return $this->db->get_record('adleradaptivity_attempts', ['attempt_id' => $attempt_id], '*', MUST_EXIST); } + /** + * @throws dml_exception + */ + public function delete_adleradaptivity_attempt_by_question_usage_id(int $question_usage_id): bool { + return $this->db->delete_records('adleradaptivity_attempts', ['attempt_id' => $question_usage_id]); + } + /** * Create adleradaptivity_attempt in database * @@ -28,4 +36,28 @@ public function get_adleradaptivity_attempt_by_quba_id(int $attempt_id): stdClas public function create_adleradaptivity_attempt(stdClass $adleradaptivity_attempt): int { return $this->db->insert_record('adleradaptivity_attempts', $adleradaptivity_attempt); } + + /** Load adleradaptivity_attempts by cmid + * - Get context by cmid + * - with the context id get the question_usage + * - question_usage and adleradaptivity_attempts are a 1:1 relation + * + * @param int $cmid The course module ID of the adleradaptivity element. + * @return array adleradaptivity_attempt The question usage object. + * @throws dml_exception + * @throws dml_missing_record_exception If the expected records are not found. + */ + public function get_adleradaptivity_attempt_by_cmid($cmid) { + // Get the context for the provided course module ID. + $modulecontext = context_module::instance($cmid); + + // Create SQL to join adleradaptivity_attempts with question_usages based on context ID + $sql = " + SELECT aa.* + FROM {adleradaptivity_attempts} AS aa + JOIN {question_usages} AS qu ON qu.id = aa.attempt_id + WHERE qu.contextid = ? + "; + return $this->db->get_records_sql($sql, [$modulecontext->id]); + } } \ No newline at end of file diff --git a/classes/local/db/adleradaptivity_question_repository.php b/classes/local/db/adleradaptivity_question_repository.php index fca18af..2ba0ee9 100644 --- a/classes/local/db/adleradaptivity_question_repository.php +++ b/classes/local/db/adleradaptivity_question_repository.php @@ -7,6 +7,20 @@ use stdClass; class adleradaptivity_question_repository extends base_repository { + /** + * @throws dml_exception + */ + public function create_question(stdClass $question): bool|int { + return $this->db->insert_record('adleradaptivity_questions', $question); + } + + /** + * @throws dml_exception + */ + public function delete_question_by_id(int $question_id): bool { + return $this->db->delete_records('adleradaptivity_questions', ['id' => $question_id]); + } + /** * Get adleradaptivity question by question_bank_entries_id * diff --git a/classes/local/db/adleradaptivity_repository.php b/classes/local/db/adleradaptivity_repository.php index 2e98d9b..e1a587c 100644 --- a/classes/local/db/adleradaptivity_repository.php +++ b/classes/local/db/adleradaptivity_repository.php @@ -2,7 +2,27 @@ namespace mod_adleradaptivity\local\db; +use dml_exception; +use stdClass; + class adleradaptivity_repository extends base_repository { + /** + * @throws dml_exception + */ + public function create_adleradaptivity(stdClass $module_instance): bool|int { + return $this->db->insert_record('adleradaptivity', $module_instance); + } + + /** + * @throws dml_exception + */ + public function delete_adleradaptivity_by_id(int $instance_id): bool { + return $this->db->delete_records('adleradaptivity', ['id' => $instance_id]); + } + + /** + * @throws dml_exception + */ public function get_instance_by_instance_id(int $instance_id) { return $this->db->get_record('adleradaptivity', ['id' => $instance_id], '*', MUST_EXIST); } diff --git a/classes/local/db/adleradaptivity_task_repository.php b/classes/local/db/adleradaptivity_task_repository.php index d64e9e7..9c56f18 100644 --- a/classes/local/db/adleradaptivity_task_repository.php +++ b/classes/local/db/adleradaptivity_task_repository.php @@ -2,11 +2,26 @@ namespace mod_adleradaptivity\local\db; +use dml_exception; use moodle_database; use moodle_exception; use stdClass; class adleradaptivity_task_repository extends base_repository { + /** + * @throws dml_exception + */ + public function create_task(stdClass $task): bool|int { + return $this->db->insert_record('adleradaptivity_tasks', $task); + } + + /** + * @throws dml_exception + */ + public function delete_task_by_id(int $task_id): bool { + return $this->db->delete_records('adleradaptivity_tasks', ['id' => $task_id]); + } + /** * Get task by question uuid * @@ -41,4 +56,11 @@ public function get_task_by_question_uuid($question_uuid, $instance_id) { MUST_EXIST ); } + + /** + * @throws dml_exception + */ + public function get_tasks_by_adleradaptivity_id($adleradaptivity_instance_id): array { + return $this->db->get_records('adleradaptivity_tasks', ['adleradaptivity_id' => $adleradaptivity_instance_id]); + } } \ No newline at end of file diff --git a/classes/local/db/moodle_core_repository.php b/classes/local/db/moodle_core_repository.php new file mode 100644 index 0000000..11c036a --- /dev/null +++ b/classes/local/db/moodle_core_repository.php @@ -0,0 +1,41 @@ +db->insert_record('question_references', $question_reference); + } + + /** + * Retrieves the course module ID (cmid) for a given question usage ID. + * + * @param int $quid The ID of the question usage. + * @return int The course module ID (cmid) associated with the question usage. + * @throws dml_exception If there's an error with the database query. + * @throws dml_missing_record_exception If the expected records are not found. + */ + public function get_cmid_by_question_usage_id(int $quid): int { + // First, retrieve the contextid from the question_usages table using the question usage ID + $contextid = $this->db->get_field('question_usages', 'contextid', ['id' => $quid], MUST_EXIST); + + if (!$contextid) { + throw new dml_missing_record_exception('context not found for the provided question usage ID'); + } + + // Now, use the contextid to find the corresponding cmid in the context table + // Note: CONTEXT_MODULE is a constant equal to 80, representing the context level for course modules in Moodle. + // instance id refers to the instance of the context level. For CONTEXT_MODULE, this is the course module ID. + $cmid = $this->db->get_field_select('context', 'instanceid', "contextlevel = ? AND id = ?", [CONTEXT_MODULE, $contextid]); + + if (!$cmid) { + throw new dml_missing_record_exception('course module (cmid) not found for the provided context ID'); + } + + return $cmid; + } +} diff --git a/classes/local/helpers.php b/classes/local/helpers.php index ae980b2..6a66034 100644 --- a/classes/local/helpers.php +++ b/classes/local/helpers.php @@ -2,11 +2,11 @@ namespace mod_adleradaptivity\local; +global $CFG; require_once($CFG->libdir . '/questionlib.php'); use context_module; use dml_exception; -use dml_missing_record_exception; use mod_adleradaptivity\local\db\adleradaptivity_attempt_repository; use moodle_exception; use question_bank; @@ -15,61 +15,6 @@ use stdClass; class helpers { - /** Load adleradaptivity_attempts by cmid - * - Get context by cmid - * - with the context id get the question_usage - * - question_usage and adleradaptivity_attempts are a 1:1 relation - * - * @param int $cmid The course module ID of the adleradaptivity element. - * @return array adleradaptivity_attempt The question usage object. - * @throws dml_exception - * @throws dml_missing_record_exception If the expected records are not found. - */ - public static function load_adleradaptivity_attempt_by_cmid($cmid) { - global $DB; - - // Get the context for the provided course module ID. - $modulecontext = context_module::instance($cmid); - - // Create SQL to join adleradaptivity_attempts with question_usages based on context ID - $sql = " - SELECT aa.* - FROM {adleradaptivity_attempts} AS aa - JOIN {question_usages} AS qu ON qu.id = aa.attempt_id - WHERE qu.contextid = ? - "; - return $DB->get_records_sql($sql, [$modulecontext->id]); - } - - /** - * Retrieves the course module ID (cmid) for a given question usage ID. - * - * @param int $quid The ID of the question usage. - * @return int The course module ID (cmid) associated with the question usage. - * @throws dml_exception If there's an error with the database query. - * @throws dml_missing_record_exception If the expected records are not found. - */ - public static function get_cmid_for_question_usage($quid) { - global $DB; - - // First, retrieve the contextid from the question_usages table using the question usage ID - $contextid = $DB->get_field('question_usages', 'contextid', ['id' => $quid], MUST_EXIST); - - if (!$contextid) { - throw new dml_missing_record_exception('context not found for the provided question usage ID'); - } - - // Now, use the contextid to find the corresponding cmid in the context table - // Note: CONTEXT_MODULE is a constant equal to 80, representing the context level for course modules in Moodle. - $cmid = $DB->get_field_select('context', 'instanceid', "contextlevel = ? AND id = ?", [CONTEXT_MODULE, $contextid]); - - if (!$cmid) { - throw new dml_missing_record_exception('course module (cmid) not found for the provided context ID'); - } - - return $cmid; - } - /** Gets the attempt object (question usage aka $quba) for the given cm and given user. * If there is no attempt object for the given cm and user, a new attempt object is created. * If there is more than one attempt object for the given cm and user, an exception is thrown. @@ -82,14 +27,15 @@ public static function get_cmid_for_question_usage($quid) { * @throws moodle_exception If multiple question usages are found for the given criteria. */ public static function load_or_create_question_usage(int $cmid, int|null $userid = null, bool $create_new_attempt = true): false|question_usage_by_activity { - global $DB, $USER; + global $USER; + $adleradaptivity_attempt_repository = new adleradaptivity_attempt_repository(); if (!isset($userid)) { $userid = $USER->id; } // Fetch existing question usages for the given cmid and userid - $adleradaptivity_attempts_all_users = static::load_adleradaptivity_attempt_by_cmid($cmid); + $adleradaptivity_attempts_all_users = $adleradaptivity_attempt_repository->get_adleradaptivity_attempt_by_cmid($cmid); // filter the results by userid $adleradaptivity_attempts = array_filter($adleradaptivity_attempts_all_users, function ($attempt) use ($userid) { return $attempt->user_id == $userid; diff --git a/classes/local/output/pages/view_page.php b/classes/local/output/pages/view_page.php index 9c1fa0b..7281c1b 100644 --- a/classes/local/output/pages/view_page.php +++ b/classes/local/output/pages/view_page.php @@ -13,6 +13,7 @@ use invalid_parameter_exception; use local_logging\logger; use mod_adleradaptivity\local\db\adleradaptivity_repository; +use mod_adleradaptivity\local\db\moodle_core_repository; use moodle_exception; use moodle_page; use question_engine; @@ -38,6 +39,7 @@ class view_page { private adleradaptivity_question_repository $question_repository; private adleradaptivity_attempt_repository $adleradaptivity_attempt_repository; private adleradaptivity_repository $adleradaptivity_repository; + private moodle_core_repository $moodle_core_repository; private logger $logger; /** @@ -90,6 +92,7 @@ private function setup_instance_variables(): void { $this->question_repository = new adleradaptivity_question_repository(); $this->adleradaptivity_attempt_repository = new adleradaptivity_attempt_repository(); $this->adleradaptivity_repository = new adleradaptivity_repository(); + $this->moodle_core_repository = new moodle_core_repository(); $this->logger = new logger('mod_adleradaptivity', 'view_page.php'); } @@ -158,7 +161,7 @@ private function load_or_create_question_usage_by_attempt_id(int $attempt_id, st } else { // Load the attempt $this->logger->trace('Loading existing attempt. Attempt ID: ' . $attempt_id); - if ($cm->id == helpers::get_cmid_for_question_usage($attempt_id)) { + if ($cm->id == $this->moodle_core_repository->get_cmid_by_question_usage_id($attempt_id)) { // can only happen if attempt id was specified, otherwise only a valid one will be loaded $quba = question_engine::load_questions_usage_by_activity($attempt_id); } else { diff --git a/lib.php b/lib.php index deef282..909764c 100644 --- a/lib.php +++ b/lib.php @@ -2,6 +2,10 @@ use core_completion\api as completion_api; use local_logging\logger; +use mod_adleradaptivity\local\db\adleradaptivity_attempt_repository; +use mod_adleradaptivity\local\db\adleradaptivity_question_repository; +use mod_adleradaptivity\local\db\adleradaptivity_repository; +use mod_adleradaptivity\local\db\adleradaptivity_task_repository; use mod_adleradaptivity\local\helpers; @@ -35,13 +39,12 @@ function adleradaptivity_supports($feature) { * @param $instancedata * @param $mform * @return int + * @throws dml_exception */ function adleradaptivity_add_instance($instancedata, $mform = null): int { - global $DB; - $instancedata->timemodified = time(); - $id = $DB->insert_record("adleradaptivity", $instancedata); + $id = (new adleradaptivity_repository())->create_adleradaptivity($instancedata); // Update completion date event. This is a default feature activated for all modules (create module -> Activity completion). $completiontimeexpected = !empty($instancedata->completionexpected) ? $instancedata->completionexpected : null; @@ -53,9 +56,10 @@ function adleradaptivity_add_instance($instancedata, $mform = null): int { /** The [modname]_update_instance() function is called when the activity * editing form is submitted. * - * @param $instancedata - * @param $mform + * @param $moduleinstance + * @param null $mform * @return bool + * @throws moodle_exception */ function adleradaptivity_update_instance($moduleinstance, $mform = null): bool { throw new moodle_exception('unsupported', 'adleradaptivity', '', 'update_instance() is not supported'); @@ -72,45 +76,54 @@ function adleradaptivity_update_instance($moduleinstance, $mform = null): bool { * @throws dml_exception */ function adleradaptivity_delete_instance(int $instance_id): bool { + global $DB; + $logger = new logger('mod_adleradaptivity', 'lib.php'); + $adleradaptivity_attempt_repository = new adleradaptivity_attempt_repository(); + $adleradaptivity_tasks_repository = new adleradaptivity_task_repository(); + $adleradaptivity_question_repository = new adleradaptivity_question_repository(); + $adleradaptivity_repository = new adleradaptivity_repository(); - global $DB; $transaction = $DB->start_delegated_transaction(); try { // first ensure that the module instance exists - $DB->get_record('adleradaptivity', array('id' => $instance_id), '*', MUST_EXIST); + $adleradaptivity_repository->get_instance_by_instance_id($instance_id); // load all attempts related to $instance_id $cm = get_coursemodule_from_instance('adleradaptivity', $instance_id, 0, false, MUST_EXIST); - $attempts = helpers::load_adleradaptivity_attempt_by_cmid($cm->id); + $attempts = $adleradaptivity_attempt_repository->get_adleradaptivity_attempt_by_cmid($cm->id); // delete all attempts foreach ($attempts as $attempt) { - $DB->delete_records('adleradaptivity_attempts', array('attempt_id' => $attempt->attempt_id)); + $adleradaptivity_attempt_repository->delete_adleradaptivity_attempt_by_question_usage_id($attempt->attempt_id); question_engine::delete_questions_usage_by_activity($attempt->attempt_id); } // delete the module itself and all related tasks and questions // load required data - $adler_tasks = $DB->get_records('adleradaptivity_tasks', array('adleradaptivity_id' => $instance_id)); + $adler_tasks = $adleradaptivity_tasks_repository->get_tasks_by_adleradaptivity_id($instance_id); $adler_questions = []; foreach ($adler_tasks as $task) { $adler_questions = array_merge($adler_questions, helpers::load_questions_by_task_id($task->id, true)); } // perform deletion foreach ($adler_questions as $question) { - $DB->delete_records('adleradaptivity_questions', array('id' => $question->id)); + $adleradaptivity_question_repository->delete_question_by_id($question->id); } foreach ($adler_tasks as $task) { - $DB->delete_records('adleradaptivity_tasks', array('id' => $task->id)); + $adleradaptivity_tasks_repository->delete_task_by_id($task->id); } - $DB->delete_records('adleradaptivity', array('id' => $instance_id)); + $adleradaptivity_repository->delete_adleradaptivity_by_id($instance_id); $transaction->allow_commit(); } catch (Exception $e) { $logger->error('Could not delete adleradaptivity instance with id ' . $instance_id); - $transaction->rollback($e); + try { + $transaction->rollback($e); + } catch (Exception $e) { + // rollback triggers an exception, but I don't care. This method is expected to return false in case of an error. + } return false; } @@ -118,6 +131,7 @@ function adleradaptivity_delete_instance(int $instance_id): bool { } +// TODO: maybe test /** * Add a get_coursemodule_info function to add 'extra' information * @@ -127,6 +141,7 @@ function adleradaptivity_delete_instance(int $instance_id): bool { * @param stdClass $coursemodule The coursemodule object (record). * @return cached_cm_info An object on information that the courses * will know about (most noticeably, an icon). + * @throws dml_exception */ function adleradaptivity_get_coursemodule_info($coursemodule) { global $DB; diff --git a/tests/external/external_test_helpers.php b/tests/external/external_test_helpers.php index a375893..0d969dd 100644 --- a/tests/external/external_test_helpers.php +++ b/tests/external/external_test_helpers.php @@ -7,6 +7,8 @@ use stdClass; use testing_data_generator; +// TODO: this should be in data generator + class external_test_helpers { /** * This function creates a course with test questions, enrolls a user, and sets up tasks in the context of a Moodle course. @@ -18,7 +20,7 @@ class external_test_helpers { * @param bool $q2 If true, a second question will be created. If false, a second question will not be created. * @return array Returns an array containing the user, the first question (q1), and the second question (q2) if it was created. */ - public static function create_course_with_test_questions(testing_data_generator $generator, $task_required = true, $singlechoice = false, $q2 = false) { + public static function create_course_with_test_questions(testing_data_generator $generator, $task_required = true, $singlechoice = false, $q2 = false): array { $adleradaptivity_generator = $generator->get_plugin_generator('mod_adleradaptivity'); $uuid = '75c248df-562f-40f7-9819-ebbeb078954b'; diff --git a/tests/generator/lib.php b/tests/generator/lib.php index 0d315b4..93b2dfb 100644 --- a/tests/generator/lib.php +++ b/tests/generator/lib.php @@ -3,6 +3,13 @@ global $CFG; class mod_adleradaptivity_generator extends testing_module_generator { + /** This method is called by $this->getDataGenerator()->create_module('adleradaptivity', ... + * + * @param $record + * @param array|null $options + * @return stdClass + * @throws coding_exception + */ public function create_instance($record = null, array $options = null) { $default_params = [ 'name' => 'name', diff --git a/tests/lib_test.php b/tests/lib_test.php new file mode 100644 index 0000000..fe952d7 --- /dev/null +++ b/tests/lib_test.php @@ -0,0 +1,177 @@ +dirroot . '/mod/adleradaptivity/tests/lib/adler_testcase.php'); +require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); + + +class lib_test extends adler_testcase { + public function test_add_instance() { + global $DB; + $generator = $this->getDataGenerator(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); + + $this->assertCount(0, $DB->get_records('adleradaptivity')); + + $adleradaptivity_module = (object)[ + 'name' => 'name', + 'intro' => 'intro', + 'adaptivity_element_intro' => 'adaptivity_element_intro', + 'course' => $course->id, + 'coursemodule' => 0, + ]; + + adleradaptivity_add_instance($adleradaptivity_module); + + $this->assertCount(1, $DB->get_records('adleradaptivity')); + } + + public function test_update_instance() { + // Arrange + $moduleinstance = new stdClass(); + + // Act and Assert + $this->expectException(moodle_exception::class); + $this->expectExceptionMessage('update_instance() is not supported'); + adleradaptivity_update_instance($moduleinstance, null); + } + + public function test_delete_instance() { + global $DB; + $generator = $this->getDataGenerator(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); + + $adleradaptivity_module1 = $generator->create_module('adleradaptivity', ['course' => $course->id, 'completion' => 2]); + $adleradaptivity_module2 = $generator->create_module('adleradaptivity', ['course' => $course->id, 'completion' => 2]); + + + // Ensure that the instances were created. + $this->assertCount(2, $DB->get_records('adleradaptivity')); + + // Delete the first instance. + $result = adleradaptivity_delete_instance($adleradaptivity_module1->id); + + // Ensure that the deletion was successful. + $this->assertTrue($result); + + // Ensure that the instance was deleted. + $this->assertCount(1, $DB->get_records('adleradaptivity')); + + // Ensure that the second instance still exists. + $remainingInstance = $DB->get_record('adleradaptivity', ['id' => $adleradaptivity_module2->id]); + $this->assertNotEmpty($remainingInstance); + } + + /** + * Data provider for test_delete_complex_instance. + */ + public function data_provider_for_test_delete_complex_instance() { + return [ + 'Test case 1: Without attempt' => [false], + 'Test case 2: With attempt' => [true], + ]; + } + + /** + * @dataProvider data_provider_for_test_delete_complex_instance + */ + public function test_delete_complex_instance($withAttempt) { + global $DB; + $generator = $this->getDataGenerator(); + + // Create a complex module instance with test questions. + $complex_adleradaptivity_module = external_test_helpers::create_course_with_test_questions($generator); + + // Create a second, trivial module instance. + $trivial_adleradaptivity_module = $generator->create_module('adleradaptivity', ['course' => $complex_adleradaptivity_module['course']->id, 'completion' => 2]); + + // If withAttempt is true, create an attempt. + if ($withAttempt) { + // Sign in as user. + $this->setUser($complex_adleradaptivity_module['user']); + + // Generate answer data. + $answerdata = external_test_helpers::generate_answer_question_parameters('correct', false, $complex_adleradaptivity_module); + + // Create an attempt. + $answer_question_result = answer_questions::execute($answerdata[0], $answerdata[1]); + } + + // Ensure that the instances were created. + $this->assertCount(2, $DB->get_records('adleradaptivity')); + + // Delete the complex instance. + $result = adleradaptivity_delete_instance($complex_adleradaptivity_module['module']->id); + + // Ensure that the deletion was successful. + $this->assertTrue($result); + $this->assertCount(1, $DB->get_records('adleradaptivity')); + $this->assertCount(0, $DB->get_records('adleradaptivity_questions')); + $this->assertCount(0, $DB->get_records('adleradaptivity_tasks')); + + // Ensure that the trivial instance still exists. + $remainingInstance = $DB->get_record('adleradaptivity', ['id' => $trivial_adleradaptivity_module->id]); + $this->assertNotEmpty($remainingInstance); + } + + public function test_delete_instance_failure() { + global $DB; + + // Try to delete a non-existing instance. + $nonExistingId = 999999; // This ID should be non-existing. + + $result = adleradaptivity_delete_instance($nonExistingId); + + // Check that the method returned false. + $this->assertFalse($result); + + // Ensure that no instances were deleted. + $this->assertCount(0, $DB->get_records('adleradaptivity')); + } + + + /** + * @runInSeparateProcess + */ + public function test_delete_instance_failure_due_to_question_deletion_failure() { + global $DB; + $generator = $this->getDataGenerator(); + + // Create a complex module instance with test questions. + $complex_adleradaptivity_module = external_test_helpers::create_course_with_test_questions($generator); + + // Sign in as user. + $this->setUser($complex_adleradaptivity_module['user']); + + // Generate answer data. + $answerdata = external_test_helpers::generate_answer_question_parameters('correct', false, $complex_adleradaptivity_module); + + // Create an attempt. + $answer_question_result = answer_questions::execute($answerdata[0], $answerdata[1]); + + // Mock the repository object + $mockRepo = Mockery::mock('alias:mod_adleradaptivity\local\db\adleradaptivity_question_repository'); + // Make the method throw an exception + $mockRepo->shouldReceive('delete_question_by_id')->andThrow(new Exception('Could not delete')); + + // Try to delete the complex instance. + $result = adleradaptivity_delete_instance($complex_adleradaptivity_module['module']->id); + + // Check that the method returned false. + $this->assertFalse($result); + + // Ensure that no instances were deleted. + $this->assertCount(1, $DB->get_records('adleradaptivity')); + $this->assertCount(2, $DB->get_records('adleradaptivity_tasks')); + $this->assertCount(1, $DB->get_records('adleradaptivity_questions')); + $this->assertCount(1, $DB->get_records('adleradaptivity_attempts')); + } +} \ No newline at end of file