diff --git a/classes/local/errorover.php b/classes/local/errorover.php index bf44700..ec38500 100644 --- a/classes/local/errorover.php +++ b/classes/local/errorover.php @@ -57,7 +57,69 @@ function exception_grade(\Throwable $exception) throw new nullException('Error in checking user grades: ' . $exception->getMessage(), 0); // exit; } +function progress_warning($errno, $errstr, $errfile, $errline) +{ + // echo"Error stirn --- $errstr"; + // echo"Error number --- $errno"; +//echo"Error errfile --- $errfile"; + // echo"Error errline --- $errline"; + // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean + echo " unable to get name "; + // throw new nullException('Unable to communicate with LRS. Caught exception: ' . $exception->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); + // exit; +} +function progresslrsreq_warning($errno, $errstr, $errfile, $errline) +{ + // echo"Error stirn --- $errstr"; + // echo"Error number --- $errno"; +//echo"Error errfile --- $errfile"; + // echo"Error errline --- $errline"; + // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean + throw new nullException('Unable to communicate with LRS. Caught exception: ' . $exception->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); + // exit; +} +/** + * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ +function exception_progresslrsreq(\Throwable $exception) +{ + + throw new nullException('Unable to communicate with LRS. Caught exception: ' . $exception->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); + // exit; +} +function progresslrs_warning($errno, $errstr, $errfile, $errline) +{ + // echo"Error stirn --- $errstr"; + // echo"Error number --- $errno"; +//echo"Error errfile --- $errfile"; + // echo"Error errline --- $errline"; + // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean + + throw new nullException('Error in retrieving statements from LRS ' . $errstr, 0); + // exit; +} +/** + * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ +function exception_progresslrs(\Throwable $exception) +{ + + throw new nullException('Error in retrieving statements from LRS ' . $exception->getMessage(), 0); + // exit; +} function sifting_data_warning($errno, $errstr, $errfile, $errline) { // echo"Error stirn --- $errstr"; diff --git a/classes/local/progress.php b/classes/local/progress.php index 9394ba0..579633d 100644 --- a/classes/local/progress.php +++ b/classes/local/progress.php @@ -45,6 +45,12 @@ public function cmi5launch_get_request_statements_from_lrs() { * @return array */ public function cmi5launch_request_statements_from_lrs($registrationid, $session) { + + + // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. + set_error_handler('mod_cmi5launch\local\progresslrs_warning', E_WARNING); + set_exception_handler('mod_cmi5launch\local\exception_progresslrs'); + // Array to hold result. $result = array(); @@ -55,14 +61,10 @@ public function cmi5launch_request_statements_from_lrs($registrationid, $session 'since' => $session->createdat, ); - // Try and retrieve statements + // Try and retrieve statements. try { - $statements = $this->cmi5launch_send_request_to_lrs($data, $session->id); -/* - if ($statements === false || $statements == null) { - throw new \Exception ("No statements found."); - } - */ + $statements = $this->cmi5launch_send_request_to_lrs('cmi5launch_stream_and_send', $data, $session->id); + // The results come back as nested array under more then statements. We only want statements, and we want them unique. $statement = array_chunk($statements["statements"], 1); @@ -75,9 +77,21 @@ public function cmi5launch_request_statements_from_lrs($registrationid, $session array_push($result, array($registrationid => $current)); } + // Restore default handlers. + restore_exception_handler(); + restore_error_handler(); + return $result; + } catch (\Throwable $e) { - echo 'Trouble retrieving statements from LRS. Caught exception: ', $e->getMessage(), "\n"; + + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + + // If there is an error, return the error. + throw new nullException('Trouble retrieving statements from LRS. Caught exception: '. $e->getMessage()); + } } @@ -87,18 +101,32 @@ public function cmi5launch_request_statements_from_lrs($registrationid, $session * @param mixed $id * @return mixed */ - public function cmi5launch_send_request_to_lrs($data, $id) { + public function cmi5launch_send_request_to_lrs($cmi5launch_stream_and_send, $data, $id) { $settings = cmi5launch_settings($id); - // Url to request statements from. - $url = $settings['cmi5launchlrsendpoint'] . "statements"; - // Build query with data above. - $url = $url . '?' . http_build_query($data, "", '&', PHP_QUERY_RFC1738); + // Assign passed in function to variable. + $stream = $cmi5launch_stream_and_send; + // Make sure LRS settings are there. + try { + // Url to request statements from. + $url = $settings['cmi5launchlrsendpoint'] . "statements"; + // Build query with data above. + $url = $url . '?' . http_build_query($data, "", '&', PHP_QUERY_RFC1738); + + // LRS username and password. + $user = $settings['cmi5launchlrslogin']; + $pass = $settings['cmi5launchlrspass']; + } + catch (\Throwable $e) { + + // Throw exception if settings are missing. + Throw new nullException('Unable to retrieve LRS settings. Caught exception: '. $e->getMessage() . " Check LRS settings are correct."); + } - // LRS username and password. - $user = $settings['cmi5launchlrslogin']; - $pass = $settings['cmi5launchlrspass']; + // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. + set_error_handler('mod_cmi5launch\local\progresslrsreq_warning', E_WARNING); + set_exception_handler('mod_cmi5launch\local\exception_progresslrsreq'); // Use key 'http' even if you send the request to https://... // There can be multiple headers but as an array under the ONE header. @@ -113,23 +141,29 @@ public function cmi5launch_send_request_to_lrs($data, $id) { ), ), ); - // The options are here placed into a stream to be sent. - $context = stream_context_create($options); - // Sends the stream to the specified URL and stores results. - // The false is use_include_path, which we dont want in this case, we want to go to the url. try { - $result = file_get_contents($url, false, $context); - + //By calling the function this way, it enables encapsulation of the function and allows for testing. + //It is an extra step, but necessary for required PHP Unit testing. + $result = call_user_func($stream, $options, $url); + + // Decode result. $resultdecoded = json_decode($result, true); + + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + return $resultdecoded; + } catch (\Throwable $e) { - echo 'Unable to communicate with LRS. Caught exception: ', $e->getMessage(), "\n"; - echo "
"; - echo " Check LRS is up, username and password are correct, and LRS endpoint is correct."; - } + + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); - + throw new nullException('Unable to communicate with LRS. Caught exception: ' . $e->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); + } } /** @@ -139,9 +173,22 @@ public function cmi5launch_send_request_to_lrs($data, $id) { * @return mixed - actor */ public function cmi5launch_retrieve_actor($resultarray, $registrationid) { + + // If it fails to parse array it should throw an error, this shouldn't stop execution, but catch so we can send a better message to user. + try { + // Actor should be in statement. + $actor = $resultarray[$registrationid][0]["actor"]["account"]["name"]; + + return $actor; + + } catch (\Throwable $e) { + + // If there is an error, echo the error. - $actor = $resultarray[$registrationid][0]["actor"]["account"]["name"]; - return $actor; + echo('Unable to retrieve actor name from LRS. Caught exception: '. $e->getMessage()); + + return "(Actor name not retrieved)"; + } } /** @@ -150,31 +197,41 @@ public function cmi5launch_retrieve_actor($resultarray, $registrationid) { * @param mixed $registrationid - the registration id * @return mixed - verb */ - public function cmi5launch_retrieve_verbs($resultarray, $registrationid) { - - // Some verbs do not have an easy to display 'language' option, we need to check if 'display' is present. - $verbinfo = $resultarray[$registrationid][0]["verb"]; - $display = array_key_exists("display", $verbinfo); - - // If it is null then there is no display, so go by verb id. - if (!$display) { - // Retrieve id. - $verbid = $resultarray[$registrationid][0]["verb"]["id"]; - - // Splits id in two on 'verbs/', we want the end which is the actual verb. - $split = explode('verbs/', $verbid); - $verb = $split[1]; - - } else { - // If it is not null then there is a language easy to read version of verb display, such as 'en' or 'en-us'. - $verblang = $resultarray[$registrationid][0]["verb"]["display"]; - // Retrieve the language. - $lang = array_key_first($verblang); - // Use it to retrieve verb. - $verb = [$verblang][0][$lang]; - } + public function cmi5launch_retrieve_verb($resultarray, $registrationid) + { + + // Encase the whole thing in a try catch block to catch any errors. + // If an array key isn't there it will throw a warning. It will not stop execution but catching it will enable us to send better error messages. + + try { + // Some verbs do not have an easy to display 'language' option, we need to check if 'display' is present. + $verbinfo = $resultarray[$registrationid][0]["verb"]; + $display = array_key_exists("display", $verbinfo); + + // If it is null then there is no display, so go by verb id. + if (!$display) { + // Retrieve id. + $verbid = $resultarray[$registrationid][0]["verb"]["id"]; - return $verb; + // Splits id in two on 'verbs/', we want the end which is the actual verb. + $split = explode('verbs/', $verbid); + $verb = $split[1]; + + } else { + // If it is not null then there is a language easy to read version of verb display, such as 'en' or 'en-us'. + $verblang = $resultarray[$registrationid][0]["verb"]["display"]; + // Retrieve the language. + $lang = array_key_first($verblang); + // Use it to retrieve verb. + $verb = [$verblang][0][$lang]; + } + + return $verb; + } catch (\Throwable $e) { + // If there is an error, echo the error. + echo('Unable to retrieve verb from LRS. Caught exception: '. $e->getMessage()); + return "(Verb not retrieved)"; + } } /** @@ -189,66 +246,58 @@ public function cmi5launch_retrieve_verbs($resultarray, $registrationid) { public function cmi5launch_retrieve_object_name($resultarray, $registrationid) { global $CFG; + // Encase the whole thing in a try catch block to catch any errors. + // If an array key isn't there it will throw a warning. It will not stop execution but catching it will enable us to send better error messages. + try { - // First find the object, it should always be second level of statement (so third level array). - if (array_key_exists("object", $resultarray[$registrationid][0])) { - - if (array_key_exists("definition", $resultarray[$registrationid][0]["object"])) { - - // If 'definition' exists, check if 'name' does. - if (array_key_exists("name", $resultarray[$registrationid][0]["object"]["definition"])) { - - // Retrieve the name. - $objectarray = $resultarray[$registrationid][0]["object"]["definition"]["name"]; - - // There may be more than one languages string to choose from. First we want to - // select the language that matches the language of the course, then if not available, the first key. - // System language setting. - $language = $CFG->lang; - if (array_key_exists($language, $objectarray)) { - $object = $objectarray[$language]; - } else { - $defaultlanguage = array_key_first($objectarray); - $object = $objectarray[$defaultlanguage]; + // First find the object, it should always be second level of statement (so third level array). + if (array_key_exists("object", $resultarray[$registrationid][0])) { + + if (array_key_exists("definition", $resultarray[$registrationid][0]["object"])) { + + // If 'definition' exists, check if 'name' does. + if (array_key_exists("name", $resultarray[$registrationid][0]["object"]["definition"])) { + + // Retrieve the name. + $objectarray = $resultarray[$registrationid][0]["object"]["definition"]["name"]; + + // There may be more than one languages string to choose from. First we want to + // select the language that matches the language of the course, then if not available, the first key. + // System language setting. + $language = $CFG->lang; + + if (array_key_exists($language, $objectarray)) { + $object = $objectarray[$language]; + + } else { + $defaultlanguage = array_key_first($objectarray); + $object = $objectarray[$defaultlanguage]; + + } + return $object; } - return $object; - } - } else if (array_key_exists("id", $resultarray[$registrationid][0]["object"])) { + } else if (array_key_exists("id", $resultarray[$registrationid][0]["object"])) { + + // If name is missing check for id. + // Retrieve id. + $object = $resultarray[$registrationid][0]["object"]["id"]; + + return $object; - // If name is missing check for id. - // Retrieve id. - $object = $resultarray[$registrationid][0]["object"]["id"]; - return $object; + } else { + + return "(Object name not retrieved/there is no object in this statement)"; + } } else { - - // If both name and id are missing throw error. - $this->cmi5launch_statement_retrieval_error("Object name and id "); + + return "(Object name not retrieved/there is no object in this statement)"; } - - } else { - - $this->cmi5launch_statement_retrieval_error("Object "); - } - } - - /** - * Error message for statment retrieval to mark if something is missing - * @param mixed $missingitem - the missing item(s) - * @return void - */ - public function cmi5launch_statement_retrieval_error($missingitem) { - - global $CFG; - - // If admin debugging is enabled. - if ($CFG->debugdeveloper) { - - // Print that it is missing. - echo "
"; - echo $missingitem . "is missing from this statement."; - echo "
"; + } catch (\Throwable $e) { + // If there is an error, echo the error. + echo('Unable to retrieve object name from LRS. Caught exception: '. $e->getMessage()); + return "(Object name not retrieved)"; } } @@ -260,20 +309,28 @@ public function cmi5launch_statement_retrieval_error($missingitem) { */ public function cmi5launch_retrieve_timestamp($resultarray, $registrationid) { - // Verify this statement has a 'timestamp' param. - if (array_key_exists("timestamp", $resultarray[$registrationid][0])) { + // Encase the whole thing in a try catch block to catch any errors. + // If an array key isn't there it will throw a warning. It will not stop execution but catching it will enable us to send better error messages. + try { + // Verify this statement has a 'timestamp' param. + if (array_key_exists("timestamp", $resultarray[$registrationid][0])) { - $date = new \DateTime($resultarray[$registrationid][0]["timestamp"], new \DateTimeZone('US/Eastern')); + $date = new \DateTime($resultarray[$registrationid][0]["timestamp"], new \DateTimeZone('US/Eastern')); - $date->setTimezone(new \DateTimeZone('America/New_York')); + $date->setTimezone(new \DateTimeZone('America/New_York')); - $date = $date->format('d-m-Y' . " " . 'h:i a'); + $date = $date->format('d-m-Y' . " " . 'h:i a'); - return $date; + return $date; - } else { + } else { - $this->cmi5launch_statement_retrieval_error("Timestamp "); + return "(Timestamp not retrieved or not present in statement)"; + } + } catch (\Throwable $e) { + // If there is an error, echo the error. + echo('Unable to retrieve timestamp from LRS. Caught exception: '. $e->getMessage()); + return "(Timestamp not retrieved)"; } } @@ -293,40 +350,48 @@ public function cmi5launch_retrieve_score($resultarray, $registrationid) { // Variable to hold score. $score = null; - // Verify this statement has a 'result' param. - if (array_key_exists("result", $resultarray[$registrationid][0])) { - - // If it exists, retrieve it. - $resultinfo = $resultarray[$registrationid][0]["result"]; - - $score = array_key_exists("score", $resultinfo); + // Encase the whole thing in a try catch block to catch any errors. + // If an array key isn't there it will throw a warning. It will not stop execution but catching it will enable us to send better error messages. + try { + // Verify this statement has a 'result' param. + if (array_key_exists("result", $resultarray[$registrationid][0])) { - // If it is null then the item in question doesn't exist in this statement. - if ($score) { + // If it exists, retrieve it. + $resultinfo = $resultarray[$registrationid][0]["result"]; - $score = $resultarray[$registrationid][0]["result"]["score"]; + // If it is null then the item in question doesn't exist in this statement. + if (array_key_exists("score", $resultinfo)) { - // Raw score preferred to scaled. - if ($score["raw"]) { + $score = $resultarray[$registrationid][0]["result"]["score"]; +; + // Raw score preferred to scaled. + if (array_key_exists("raw", $score)) { - $returnscore = $score["raw"]; - return $returnscore; - } else if ($score["scaled"]) { + $returnscore = $score["raw"]; + + return $returnscore; - $returnscore = round($score["scaled"], 2); - return $returnscore; + } else if (array_key_exists("scaled", $score)) { + + $returnscore = round($score["scaled"], 2); + + return $returnscore; + } } + else { + + return "(Score not retrieved or not present in statement)"; + } + } else { + + return "(Score not retrieved or not present in statement)"; } - } else { - - // If admin debugging is enabled. - if ($CFG->debugdeveloper) { - - // Print that it is missing. - echo "
"; - echo 'No score in statement with id ' .$resultarray[$registrationid][0]['id']; - echo "
"; - } + } catch (\Throwable $e) { + + // If there is an error, echo the error. + echo('Unable to retrieve score from LRS. Caught exception: '. $e->getMessage()); + + return "(Score not retrieved)"; } } @@ -345,95 +410,73 @@ public function cmi5launch_retrieve_statements($registrationid, $session) { // Array to hold score and be returned. $returnscore = 0; - $resultdecoded = $this->cmi5launch_request_statements_from_lrs($registrationid, $session); - - // We need to sort the statements by finding their session id - // parse through array 'ext' to find the one holding session id. - foreach ($resultdecoded as $singlestatement) { - - $code = $session->code; - $currentsessionid = ""; - $ext = $singlestatement[$registrationid][0]["context"]["extensions"]; - foreach ($ext as $key => $value) { + // Wrap in a try catch block to catch any errors. + try { + $resultdecoded = $this->cmi5launch_request_statements_from_lrs($registrationid, $session); + + // We need to sort the statements by finding their session id + // parse through array 'ext' to find the one holding session id. + foreach ($resultdecoded as $singlestatement) { - // If key contains "sessionid" in string. - if (str_contains($key, "sessionid")) { - $currentsessionid = $value; - } - } + $code = $session->code; + $currentsessionid = ""; - // Now if code equals currentsessionid, this is a statement pertaining to this session. - if ($code === $currentsessionid) { + // what is first array key in statement? It is the registration id. + + //// There should always be an extension but in case. + try { + $ext = $singlestatement[$registrationid][0]["context"]["extensions"]; - $actor = $this->cmi5launch_retrieve_actor($singlestatement, $registrationid); - $verb = $this->cmi5launch_retrieve_verbs($singlestatement, $registrationid); - $object = $this->cmi5launch_retrieve_object_name($singlestatement, $registrationid); - $date = $this->cmi5launch_retrieve_timestamp($singlestatement, $registrationid); - $score = $this->cmi5launch_retrieve_score($singlestatement, $registrationid); + foreach ($ext as $key => $value) { - // If a session has more than one score, we only want the highest. - if (!$score == null && $score > $returnscore) { + // If key contains "sessionid" in string. + if (str_contains($key, "sessionid")) { - $returnscore = $score; + $currentsessionid = $value; + } + } + } catch (\Throwable $e) { + // If there is an error, echo the error. + echo('Unable to retrieve session id from LRS. Caught exception: '. $e->getMessage() . ". There may not be an extension key in statement."); } - // Update to return. - $progressupdate[] = "$actor $verb $object on $date"; - - } - - } - - $session->progress = json_encode($progressupdate); - - $session->score = $returnscore; - - return $session; - } + // Now if code equals currentsessionid, this is a statement pertaining to this session. + if ($code === $currentsessionid) { - // An error message catcher. - /** - * Function to test returns from cmi5 player and display error message if found to be false - * // or not 200. - * @param mixed $resulttotest - The result to test. - * @param string $type - The type missing to be added to the error message. - * @return bool - */ - public static function cmi5launch_progress_error_message($resulttotest, $type) { - - // Decode result because if it is not 200 then something went wrong - // If it's a string, decode it. - if (is_string($resulttotest)) { - $resulttest = json_decode($resulttotest, true); - } else { - $resulttest = $resulttotest; - } + $actor = $this->cmi5launch_retrieve_actor($singlestatement, $registrationid); + $verb = $this->cmi5launch_retrieve_verb($singlestatement, $registrationid); + $object = $this->cmi5launch_retrieve_object_name($singlestatement, $registrationid); + $date = $this->cmi5launch_retrieve_timestamp($singlestatement, $registrationid); + $score = $this->cmi5launch_retrieve_score($singlestatement, $registrationid); - // I think splittin these to return two seperate messages deppennnding on whether player is running is better. - // Player cannot return an error if not runnin, - if ($resulttest === false ){ + // If a session has more than one score, we only want the highest. + if (!$score == null && $score > $returnscore) { - echo "
"; + $returnscore = $score; + } - echo "Something went wrong " . $type . ". LRS is not communicating. Is it running?"; + // Update to return. + $progressupdate[] = "$actor $verb $object on $date"; - echo "
"; + } - return false; - } - else if( array_key_exists("statusCode", $resulttest) && $resulttest["statusCode"] != 200) { + } - echo "
"; + $session->progress = json_encode($progressupdate); - echo "Something went wrong " . $type . ". CMI5 Player returned " . $resulttest["statusCode"] . " error. With message '" - . $resulttest["message"] . "'." ; - echo "
"; + $session->score = $returnscore; - return false; - } else { + return $session; - // No errors, continue. - return true; + } catch (\Throwable $e) { + + // If there is an error, echo the error. + echo('Unable to retrieve statements from LRS. Caught exception: '. $e->getMessage()); + + + return "(Statements not retrieved)"; } } + + } diff --git a/cmi5PHP/tests/cmi5TestHelpers.php b/cmi5PHP/tests/cmi5TestHelpers.php index c867dbc..69658ca 100644 --- a/cmi5PHP/tests/cmi5TestHelpers.php +++ b/cmi5PHP/tests/cmi5TestHelpers.php @@ -127,7 +127,7 @@ function deletetestcmi5launch_sessions($ids) foreach ($ids as $id) { // Delete the cmi5launch record - $DB->delete_records('cmi5launch_sessions', array('id' => $id)); + $DB->delete_records('cmi5launch_sessions', array('sessionid' => $id)); } } @@ -173,43 +173,6 @@ function maketestaus ($testcourseid) { global $DB, $cmi5launch, $USER; - // Mock values to make AUs. - /* - $mockvalues = array( - 'id' => 'id', - 'attempt' => 'attempt', - 'url' => 'url', - 'type' => 'type', - 'lmsid' => 'lmsid', - 'grade' => 'grade', - 'scores' => 'scores', - 'title' => array(0 => array('text' => 'title')), - 'moveon' => 'moveon', - 'auindex' => 'auindex', - 'parents' => 'parents', - 'objectives' => 'objectives', - 'description' => array(0 => array('text' => 'description')), - 'activitytype' => 'activitytype', - 'launchmethod' => 'launchmethod', - 'masteryscore' => 'masteryscore', - 'satisfied' => 'satisfied', - 'launchurl' => 'launchurl', - 'sessions' => 'sessions', - 'progress' => 'progress', - 'noattempt' => 'noattempt', - 'completed' => 'completed', - 'passed' => 'passed', - 'inprogress' => 'inprogress', - 'masteryScore' => 'masteryScore', - 'launchMethod' => 'launchMethod', - 'lmsId' => 'lmsId', - 'moveOn' => 'moveOn', - 'auIndex' => 'auIndex', - 'activityType' => 'activityType', - 'moodlecourseid' => 'moodlecourseid', - 'userid' => 'userid' - ); -*/ // Make new AUs, lets make five. $aus = array(); for ($i = 0; $i < 5; $i++) { @@ -273,10 +236,313 @@ function maketestaus ($testcourseid) } /** - * Create fake statements for testing. - * @param mixed $amountomake - amount of statements to make. - * @return array $statements - array of statements - */ + * Create fake statement values for testing. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvalues($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => '2024-' . $i . '0-00T00:00:00.000Z' , + 'actor' => array ( + "firstname" => "firstname" . $i, + "lastname" => "lastname" . $i, + "account" => array ( + "homePage" => "homePage" . $i, + "name" => "actorname" . $i, + ), + ), + 'verb' => array ( + "id" => "verbs/verbid" . $i, + "display" => array( + "en" => ("verbdisplay" . $i), + ), + ), + 'object' => array ( + "id" => "objectid" . $i, + "definition" => array ( + "name" => array ( + + "en" => 'objectname' . $i), + "description" => "description" . $i, + "type" => "type" . $i, + ), + ), + 'context' => array ( + "context" => "context" . $i, + "contexttype" => "contexttype" . $i, + "contextparent" => "contextparent" . $i, + "extensions" => array ( + "extensions" => "extensions" . $i, + "sessionid" => "code" . $i, + ), + ), + "result" => array ( + "result" => "result" . $i, + "score" => array ( + "raw" => 80 + $i, + "scaled" => $i, + ), + ), + 'stored' => 'stored' . $i, + 'authority' => array ( + "authority" => "authority" . $i, + + ), + 'version' => "version" . $i, + 'code' => 'code' . $i, + 'progress' => 'progress' . $i, + 'score' => 'score' . $i, + ), + ) + ); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has no 'display' key in the verb. + * It also has a scaled score for testing in testcmi5launch_retrieve_score_scaled_score + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluesnodisplay($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => 'timestamp' . $i, + 'actor' => array ( + "firstname" => "firstname" . $i, + "lastname" => "lastname" . $i, + "account" => array ( + "homePage" => "homePage" . $i, + "name" => "name" . $i, + ), + ), + 'verb' => array ( + "id" => "verbs/verbid" . $i, + ), + 'version' => "version" . $i, + + "result" => array ( + "result" => "result" . $i, + "score" => array ( + "scaled" => $i, + ), + ), + ) + )); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has no object key. + * // It also has no result and is used in testcmi5launch_retrieve_result_no_result + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluesnoobject($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => 'timestamp' . $i,), + ) + ); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has no object definition key but does have an id. + * // This also has no timestamp so it can be used for testing in testcmi5launch_retrieve_timestamp_no_time + * // And no score in 'result' to be use in testin testcmi5launch_retrieve_score_no_score. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluesnoobjectdef($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'object' => array ( + "id" => "objectid" . $i), + + "result" => array (), + ) + )); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has no object/def/name key.. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluesnoobjectname($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => 'timestamp' . $i,), + 'object' => array ( + "id" => "objectid" . $i, + "definition" => array ( + + "description" => "description" . $i, + "type" => "type" . $i, + ), + + ), + ) + ); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has an object but nothing in it . + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluesnoobjectid($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => 'timestamp' . $i, + 'object' => array () + ) + ) + ); + } + + // Return the statement values. + return $statementvalues; +} + + + /** + * Create fake statement values for testing. + * This one has no matching lanuage string. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluenomatchinglang($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => 'timestamp' . $i, + 'object' => array ( + "id" => "objectid" . $i, + "definition" => array ( + "name" => array( + "fr" => "fr" . $i, + "es" => "name" . $i, + ), + "description" => "description" . $i, + "type" => "type" . $i, + ), + + ), + ) ) + ); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has a matching lanuage value. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluematchinglang($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => '2024-' . $i . '0-00T00:00:00.000Z', + 'object' => array ( + "id" => "objectid" . $i, + "definition" => array ( + "name" => array( + "en" => "en-us" . $i, + "es" => "name" . $i, + ), + "description" => "description" . $i, + "type" => "type" . $i, + ), + + ), + )) + ); + } + + // Return the statement values. + return $statementvalues; +} + +/** + * Create fake statements for testing. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ function maketeststatements($amounttomake) { // Iterate through amount to make, and use i to make different 'registration ids' @@ -294,58 +560,63 @@ function maketeststatements($amounttomake) // Array to hold the statements. // Maybe I need to change the stucture so the $i is first and next to statements instead of now as its nested - /*old - $statement['statements'][] = array( - $i => array(*/ - $statement['statements'][$i] = array( - // 'id' => 'idune' . $i, - 'timestamp' => 'timestamp' . $i, - 'actor' => array ( - "firstname" => "firstname" . $i, - "lastname" => "lastname" . $i, - "account" => array ( - "homePage" => "homePage" . $i, - "name" => "name" . $i, - ), + $newstatement = array( + 'timestamp' => '2024-' . $i . '0-00T00:00:00.000Z' , + 'actor' => array ( + "firstname" => "firstname" . $i, + "lastname" => "lastname" . $i, + "account" => array ( + "homePage" => "homePage" . $i, + "name" => "actorname" . $i, ), - 'verb' => array ( - "id" => "verbid" . $i, - "display" => array( - "en" => "verbdisplay" . $i, - ), - ) - , - 'object' => array ( - "id" => "objectid" . $i, - "definition" => array ( - "name" => "name" . $i, - "description" => "description" . $i, - "type" => "type" . $i, - ), + ), + 'verb' => array ( + "id" => "verbs/verbid" . $i, + "display" => array( + "en" => ("verbdisplay" . $i), ), - 'context' => array ( - "context" => "context" . $i, - "contexttype" => "contexttype" . $i, - "contextparent" => "contextparent" . $i, + ), + 'object' => array ( + "id" => "objectid" . $i, + "definition" => array ( + "name" => array ( + + "en" => 'objectname' . $i), + "description" => "description" . $i, + "type" => "type" . $i, ), - "result" => array ( - "result" => "result" . $i, - "score" => array ( - "raw" => "raw" . $i, - "scaled" => "scaled" . $i, - ), + ), + 'context' => array ( + "context" => "context" . $i, + "contexttype" => "contexttype" . $i, + "contextparent" => "contextparent" . $i, + "extensions" => array ( + "extensions" => "extensions" . $i, + "sessionid" => "code" . $i, ), - 'stored' => 'stored' . $i, - 'authority' => array ( - "authority" => "authority" . $i, - + ), + "result" => array ( + "result" => "result" . $i, + "score" => array ( + "raw" => 80 + $i, + "scaled" => $i, ), - 'version' => "version" . $i, + ), + 'stored' => 'stored' . $i, + 'authority' => array ( + "authority" => "authority" . $i, + + ), + 'version' => "version" . $i, + 'code' => 'code' . $i, + 'progress' => 'progress' . $i, + 'score' => 'score' . $i, ); - $statements[] = $statement; + $statements[] = $newstatement; } + $statement['statements'] = $statements; // Return array of statements return $statement; } @@ -365,16 +636,13 @@ function maketestsessions () for ($i = 0; $i < 5; $i++) { - - //$toaddtostring = strval($i); - // Add i to each value so the AUs are unique. - // Mock values to make sessions. // For some bizarre reason, retrieve sessions from db goes on SESSION id not ID?!?!?? // THIS IS the problem? $mockvalues = array( 'id' => $i, - 'sessionid' => 'sessionid' . $i, + // Can't save string to DB. + 'sessionid' => $i, 'userid' => 'userid' . $i, 'moodlecourseid' => 'moodlecourseid' . $i, 'registrationscoursesausid' => 'registrationscoursesausid' . $i, @@ -400,30 +668,24 @@ function maketestsessions () 'launchurl' => 'launchurl' . $i, ); $sessions[] = new session($mockvalues); - $sessionid[] = ('sessionid' . $i); + $sessionid[] = ( $i); } - // Pass in the au index to retrieve a launchurl and session id. -//$urldecoded = $cmi5launchretrieveurl($cmi5launch->id, $auindex); - -// Retrieve and store session id in the aus table. -//$sessionid = intval($urldecoded['id']); - - -// Retrieve the launch url. -$launchurl = 'testurl'; -// And launch method. -$launchmethod = 'testmethod'; -// Now save the fake sessions to the test database -// Ok so tomorrow make this so there are sessions actually creatd. -$sessionhelper = new session_helpers(); -//$createsession = $sessionhelper->cmi5launch_create_session(); - // For each session id in the list, create a session. - foreach ($sessions as $session) { - // LEts test if it can retrieve here - $newid = $sessionhelper->cmi5launch_create_session($session->id, $launchurl, $launchmethod); - $newids[] = $newid; - //$sessionid[] = $session; + // Retrieve the launch url. + $launchurl = 'testurl'; + // And launch method. + $launchmethod = 'testmethod'; + + // Now save the fake sessions to the test database + // Ok so tomorrow make this so there are sessions actually creatd. + $sessionhelper = new session_helpers(); + //$createsession = $sessionhelper->cmi5launch_create_session(); + // For each session id in the list, create a session. + foreach ($sessions as $session) { + // LEts test if it can retrieve here + $newid = $sessionhelper->cmi5launch_create_session($session->id, $launchurl, $launchmethod); + $newids[] = $newid; + //$sessionid[] = $session; @@ -437,7 +699,7 @@ function maketestsessions () } /** - * Heeelper func that assigns the sessions made to the aus for testing purposes. + * Helper func that assigns the sessions made to the aus for testing purposes. * @param mixed $auids * @param mixed $sessionids * @return array @@ -472,14 +734,6 @@ function assign_sessions_to_aus($auids, $sessionids){ // Tomorrow: no that isnt it. Something else is throwin the problem. // Now the AU will have properties and we want to assign the sessionid array to the 'sessions' property $au->sessions = json_encode($sessionids); - // Update AU in table with new info. - - //echo au here nd check what sessons itr and if id mathces - - - - // what is au record? - // Save the AU back to the DB. $success = $DB->update_record('cmi5launch_aus', $au); @@ -489,13 +743,6 @@ function assign_sessions_to_aus($auids, $sessionids){ $newaus[] = $au; } - // AHA! They are whole damn aus - // now save the new aus back to db - // we dont need to sve twice!!! - // $save_aus($newaus); - // Save us is to make new aus we need to save them back to DB - // Now update to table. - //return $newauids; } @@ -545,36 +792,27 @@ function assign_aus_to_courses($courseids, $auids){ } } - - - - - -/* - function get_file_get_contents() { - return ['file_get_contents']; -} -*/ - global $file_get_contents; - // Ok lets make a file_et_contents for this namespace, we know it should return a json string? - // And what should it receive? just the reular arguments? - /** - * A local file_get_contents to overide the PHP function for testing. - * As we do not want to actually get the file, we just want to test the function calling that function. - * - */ - function file_get_contents($url, $use_include_path = false, $context = null, $offset = 0, $maxlen = null) + // So now all the test has to do is inject THIS which will return as we please + function cmi5launch_test_stream_and_send_pass_lrs($options, $url) { - // Do we wamt to test whats passed in as well? pr ois that not necessary? - // MAybe we can return the args as json encoded string? - return json_encode(func_get_args()); + // Make a fake statement to return from the mock. + $statement = maketeststatements(1); + + // Lets pass in the 'return' value as the option. + // Encode because it would be a string comiiiiing from LRS. + return json_encode($statement); } - // And what should it receive? just the reular arguments? - /** - * A local file_get_contents to overide the PHP function for testing. - * As we do not want to actually get the file, we just want to test the function calling that function. - * - */ + + // So now all the test has to do is inject THIS which will return as we please + function cmi5launch_test_stream_and_send_excep_lrs($options, $url) + { + // Make a fake statement to return from the mock. + $statement = maketeststatements(1); + + // Lets pass in the 'return' value as the option. + // Encode because it would be a string comiiiiing from LRS. + return json_encode($statement); + } // So now all the test has to do is inject THIS which will return as we please function cmi5launch_test_stream_and_send_pass($options, $url) diff --git a/cmi5PHP/tests/grade_helpersTest.php b/cmi5PHP/tests/grade_helpersTest.php index a18304e..1286f4d 100644 --- a/cmi5PHP/tests/grade_helpersTest.php +++ b/cmi5PHP/tests/grade_helpersTest.php @@ -705,7 +705,7 @@ public function testcmi5launch_update_au_for_user_grades_excep() // Functions from other classes. $updateau = $gradehelper->get_cmi5launch_update_au_for_user_grades(); - // The expected is built bby the two messages knowing 'title' is an empty array. + // The expected is built by the two messages knowing 'title' is an empty array. $expected = "Error in updating or checking user grades. Report this error to system administrator: Error in checking user grades:"; // Catch the exception. diff --git a/cmi5PHP/tests/progressTest.php b/cmi5PHP/tests/progressTest.php index 014332f..209ac39 100644 --- a/cmi5PHP/tests/progressTest.php +++ b/cmi5PHP/tests/progressTest.php @@ -3,6 +3,8 @@ use PHPUnit\Framework\TestCase; use mod_cmi5launch\local\cmi5_connectors; +use mod_cmi5launch\local\nullException; +use mod_cmi5launch\local\session; require_once( "cmi5TestHelpers.php"); @@ -51,9 +53,18 @@ protected function setUp(): void { global $sessionids, $DB, $cmi5launch, $cmi5launchid, $USER, $testcourseid, $cmi5launchsettings; - $cmi5launchsettings = array("cmi5launchtenanttoken" => "Testtoken", "cmi5launchplayerurl" => "http://test/launch.php", "cmi5launchcustomacchp" => "http://testhomepage.com"); + $cmi5launchsettings = array( + "cmi5launchlrsendpoint" => "Test LRS point", + "cmi5launchlrslogin" => "Test LRS login", + "cmi5launchlrspass" => "Test LRS password", + "cmi5launchtenanttoken" => "Testtoken", + "cmi5launchplayerurl" => "http://test/launch.php", + "cmi5launchcustomacchp" => "http://testhomepage.com", + "grademethod" => 1 + ); + // Override global variable and function so that it returns test data. $USER = new \stdClass(); $USER->username = "testname"; @@ -68,9 +79,12 @@ protected function setUp(): void protected function tearDown(): void { + global $sessionids; // Restore overridden global variable. unset($GLOBALS['USER']); unset($GLOBALS['cmi5launchsettings']); + + deletetestcmi5launch_sessions($sessionids); } /** @@ -93,66 +107,13 @@ public function testcmi5launch_request_statements_from_lrs_pass() $registrationid = "registrationid"; // Statement values that should be returned from mock. - $statementvalues = array(); + $statementvalues = maketeststatementsvalues($amount, $registrationid); - for($i = 0; $i < $amount; $i++) - { - // Mock statements that should be returned. - $statementvalues[] = array( - $registrationid => array( - // They are nested under 0 since array_chunk is used. - 0 => array( - 'timestamp' => 'timestamp' . $i, - 'actor' => array ( - "firstname" => "firstname" . $i, - "lastname" => "lastname" . $i, - "account" => array ( - "homePage" => "homePage" . $i, - "name" => "name" . $i, - ), - ), - 'verb' => array ( - "id" => "verbid" . $i, - "display" => array( - "en" => "verbdisplay" . $i, - ), - ), - 'object' => array ( - "id" => "objectid" . $i, - "definition" => array ( - "name" => "name" . $i, - "description" => "description" . $i, - "type" => "type" . $i, - ), - ), - 'context' => array ( - "context" => "context" . $i, - "contexttype" => "contexttype" . $i, - "contextparent" => "contextparent" . $i, - ), - "result" => array ( - "result" => "result" . $i, - "score" => array ( - "raw" => "raw" . $i, - "scaled" => "scaled" . $i, - ), - ), - 'stored' => 'stored' . $i, - 'authority' => array ( - "authority" => "authority" . $i, - - ), - 'version' => "version" . $i, - ), - ) - ); - } - // Retrieve a sessionid, we'll just use the first one. $sessionid = $sessionids[0]; // Retrieve a session from the DB as an object. - $session = $DB->get_record('cmi5launch_sessions', array('id' => $sessionid), '*', MUST_EXIST); + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid), '*', MUST_EXIST); // Mock data as it will be passed to stub. $data = array( @@ -160,6 +121,9 @@ public function testcmi5launch_request_statements_from_lrs_pass() 'since' => $session->createdat, ); + // Function that will be called in function under test. + $testfunction = 'cmi5launch_stream_and_send'; + // Mock a cmi5 connector object but only stub ONE method, as we want to test the other methods. // Create a mock of the send_request class as we don't actually want // to create a new course in the player. @@ -169,7 +133,7 @@ public function testcmi5launch_request_statements_from_lrs_pass() $csc->expects($this->once()) ->method('cmi5launch_send_request_to_lrs') - ->with($data, $session->id) + ->with($testfunction, $data, $session->id) ->willReturn($statements); // Call the method under test. @@ -187,78 +151,1008 @@ public function testcmi5launch_request_statements_from_lrs_pass() * Test of the cmi5launch_request_statements_from_lrs with a fail condition. * @return void */ - public function testcmi5launch_request_statements_from_lrs_fail(){ + public function testcmi5launch_request_statements_from_lrs_excep(){ global $DB, $cmi5launch, $cmi5launchid, $sessionids; - // Amount of statements to make for testing. - // The same amount of statements to make for testing will match the amount - // of statement values returned from mock. - $amount = 5; + + // Test registrationid to pass. + $registrationid = "registrationid"; + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[0]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid), '*', MUST_EXIST); - // Make fake statements for testing. - $statements = maketeststatements($amount); + // Mock data as it will be passed to stub. + $data = array( + 'registration' => $registrationid, + 'since' => $session->createdat, + ); + + // Function that will be called in function under test. + $testfunction = 'cmi5launch_stream_and_send'; + + // Mock a cmi5 connector object but only stub ONE method, as we want to test the other methods. + // Create a mock of the send_request class as we don't actually want + // to create a new course in the player. + $csc = $this->getMockBuilder('mod_cmi5launch\local\progress') + ->onlyMethods(array('cmi5launch_send_request_to_lrs' )) + ->getMock(); + + // If this returns null it should trrigger an error in the function. + $csc->expects($this->once()) + ->method('cmi5launch_send_request_to_lrs') + ->with($testfunction, $data, $session->id) + ->willReturn(null); + + // The expected is built by the two messages knowing 'title' is an empty array. + $expected = "Trouble retrieving statements from LRS. Caught exception: "; + + // Catch the exception. + $this->expectException(nullException::class); + $this->expectExceptionMessage($expected); + + // Call the method under test. + $result = $csc->cmi5launch_request_statements_from_lrs($registrationid,$session); + + + } + /** + * Test of the cmi5launch_send_request_to_lrs with a pass condition. + * @return void + */ + public function testcmi5launch_send_request_to_lrs(){ + global $DB, $cmi5launch, $cmi5launchid, $sessionids; + + // Mock data as it will be passed to stub. + $data = array( + 'registration' => "registrationid", + 'since' => "Test time", + ); + + // Retrieve settings like they will be in SUT. + $settings = cmi5launch_settings($cmi5launch->id); + // Url to request statements from. + $url = $settings['cmi5launchlrsendpoint'] . "statements"; + // Build query with data above. + $url = $url . '?' . http_build_query($data, "", '&', PHP_QUERY_RFC1738); + + // LRS username and password. + $user = $settings['cmi5launchlrslogin']; + $pass = $settings['cmi5launchlrspass']; + + // Test registrationid to pass. $registrationid = "registrationid"; + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[0]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid), '*', MUST_EXIST); - // Statement values that should be returned from mock. - $statementvalues = array(); - - - for($i = 0; $i < $amount; $i++) + // Mock data as it will be passed to stub. + // Use key 'http' even if you send the request to https://... + // There can be multiple headers but as an array under the ONE header. + // Content(body) must be JSON encoded here, as that is what CMI5 player accepts. + $options = array( + 'http' => array( + 'method' => 'GET', + 'header' => array( + 'Authorization: Basic ' . base64_encode("$user:$pass"), + "Content-Type: application/json\r\n" . + "X-Experience-API-Version:1.0.3", + ), + ), + ); + + // Function that will be called in function under test. + $testfunction = 'cmi5Test\cmi5launch_test_stream_and_send_pass_lrs'; + + // Make a fake statement to return from the mock. + $statement = maketeststatements(1); + + // The result we expect back is the statment, decoded. So it's original form. + $expected = $statement; + + // New proggress + $progress = new \mod_cmi5launch\local\progress(); + // Call the method under test. + $result = $progress->cmi5launch_send_request_to_lrs($testfunction, $data, $session->id); + + // Check the result is as expected. + $this->assertEquals($expected, $result, "Expected result to match statementvalues "); + // The return should also be an array, since it is decoded with the 'tue' flag. + $this->assertIsArray($result,"Expected retrieved object to be array" ); + } + + /** + * Test of the cmi5launch_send_request_to_lrs with a fail condition. + * Throws an exception at the first try/catch where it is retrievin settings. + * @return void + */ + public function testcmi5launch_send_request_to_lrs_fail_settings(){ + + global $DB, $cmi5launch, $cmi5launchid, $sessionids; + + // Make data that is not array to throw error in settings try/catch + $data = ("Test string to throw error"); + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[0]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid), '*', MUST_EXIST); + + + // Function that will be called in function under test. + $testfunction = 'cmi5Test\cmi5launch_test_stream_and_send_pass_lrs'; + + // New proggress + $progress = new \mod_cmi5launch\local\progress(); + // The expected is built bby the two messages knowing 'title' is an empty array. + $expected = "Unable to retrieve LRS settings. Caught exception: "; + + + $this->expectExceptionMessage($expected); + // Catch the exception. + $this->expectException(nullException::class); + + // Call the method under test. + $result = $progress->cmi5launch_send_request_to_lrs($testfunction, $data, $session->id); + + } + + /** + * Test of the cmi5launch_send_request_to_lrs with a fail condition. + * Throws an exception at the second try/catch, where it tries to communicte with LRS. + * @return void + */ + public function testcmi5launch_send_request_to_lrs_fail_excep(){ + + global $DB, $cmi5launch, $cmi5launchid, $sessionids; + + // Mock data as it will be passed to stub. + $data = array( + 'registration' => "registrationid", + 'since' => "Test time", + ); + + // Retrieve settings like they will be in SUT. + $settings = cmi5launch_settings($cmi5launch->id); + // Url to request statements from. + $url = $settings['cmi5launchlrsendpoint'] . "statements"; + // Build query with data above. + $url = $url . '?' . http_build_query($data, "", '&', PHP_QUERY_RFC1738); + + // LRS username and password. + $user = $settings['cmi5launchlrslogin']; + $pass = $settings['cmi5launchlrspass']; + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[0]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid), '*', MUST_EXIST); + + // Mock data as it will be passed to stub. + // Use key 'http' even if you send the request to https://... + // There can be multiple headers but as an array under the ONE header. + // Content(body) must be JSON encoded here, as that is what CMI5 player accepts. + $options = array( + 'http' => array( + 'method' => 'GET', + 'header' => array( + 'Authorization: Basic ' . base64_encode("$user:$pass"), + "Content-Type: application/json\r\n" . + "X-Experience-API-Version:1.0.3", + ), + ), + ); + + // Function that will be called in function under test. + $testfunction = 'cmi5Test\cmi5launch_stream_and_send_excep_lrs'; + + + // The expected is built bby the two messages knowing 'title' is an empty array. + $expected = 'Unable to communicate with LRS. Caught exception: ' ; + + // Catch the exception. + $this->expectException(nullException::class); + $this->expectExceptionMessage($expected); + + // New proggress + $progress = new \mod_cmi5launch\local\progress(); + // Call the method under test. + $result = $progress->cmi5launch_send_request_to_lrs($testfunction, $data, $session->id); + + } + /** + * Test of the cmi5launch_retrieve_actor with a pass condition. + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_actor_pass() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "actorname0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_actor($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + /** + * Test of the cmi5launch_retrieve_actor with a fail condition. + * Fail at retrieving the actors name. + * @return void + */ + public function testcmi5launch_retrieve_actor_fail() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "(Actor name not retrieved)"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + // Pass a string that is not in the statement to cause an error. + $result = $progress->cmi5launch_retrieve_actor($statement[0], "purple"); + + $expectedoutput = 'Unable to retrieve actor name from LRS. Caught exception: Undefined array key "purple"'; + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + $this->expectOutputString($expectedoutput); + } + /** + * Test of the cmi5launch_retrieve_verb with a pass condition. + * (This one tests if verb has display option). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_verb_pass() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "verbdisplay0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_verb($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } +/** + * Test of the cmi5launch_retrieve_verb with a pass condition. + * (This one tests if verb doesn't have a display option). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_verb_pass_no_display() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnodisplay(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "verbid0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_verb($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_actor with a fail condition. + * Fail at retrieving the verb . + * @return void + */ + public function testcmi5launch_retrieve_verb_fail() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "(Verb not retrieved)"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + // Pass a string that is not in the statement to cause an error. + $result = $progress->cmi5launch_retrieve_verb($statement[0], "purple"); + + $expectedoutput = 'Unable to retrieve verb from LRS. Caught exception: Undefined array key "purple"'; + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + $this->expectOutputString($expectedoutput); + + } + + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if object isn't there). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_pass_no_object_key() + { + // Branch one + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobject(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "(Object name not retrieved/there is no object in this statement)"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if object doesn't have a definition key and no id ). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_pass_no_object_def_key() + { + //Branch 2 + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobjectid(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "(Object name not retrieved/there is no object in this statement)"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if object doesnt have a definition key, + * but does have an object id key ). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_pass_object_id_exists() + { + // Branch 3 + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobjectdef(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "objectid0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if object has everything and languae is specified matches the cfg language (should be 'en')). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_pass_matching_lang() + { + // Branch 5 + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluematchinglang(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "en-us0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if object has everything and languae doesnt match the cfg language). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_pass_no_matching_lang() + + { + // Branch 4. + + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluenomatchinglang(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "fr0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if the exceptions are caught and thrown correctly. + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_excep() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = "String to throw error"; + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "(Object name not retrieved)"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + // There will also be an output message. + $expectedoutput = 'Unable to retrieve object name from LRS. Caught exception: Cannot access offset of type string on string'; + $this->expectOutputString($expectedoutput); + } + + /** + * Test of the cmi5launch_retrieve_timestamp with a pass condition. + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_timestamp() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $faketime = "2024-00-00T00:00:00.000Z"; + + // Turn expected into a date so it matches what's leaving the function. + $expected = new \DateTime($faketime, new \DateTimeZone('US/Eastern')); + + $expected->setTimezone(new \DateTimeZone('America/New_York')); + + $expected = $expected->format('d-m-Y' . " " . 'h:i a'); + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_timestamp($statement[0], $registrationid); + // echo" result . " . $result; + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_timestamp with a pass condition. + * Tests else branch if timestamp is not present. + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_timestamp_no_time() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobjectdef(1, $registrationid); + + $expected = "(Timestamp not retrieved or not present in statement)"; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_timestamp($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + + /** + * Test of the cmi5launch_retrieve_timestamp with a fail condition. + * Tests error try/catch. + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_timestamp_excep() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = "String to throw error"; + + $expected = "(Timestamp not retrieved)"; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_timestamp($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + // There will also be an output message. + $expectedoutput = 'Unable to retrieve timestamp from LRS. Caught exception: Cannot access offset of type string on string'; + $this->expectOutputString($expectedoutput); + } + + + /** + * Test of the cmi5launch_retrieve_score with a pass condition. + * Successfully retrieves a score + * // This one retrieves raw score + * . + * @return void + */ + public function testcmi5launch_retrieve_score_raw_score() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // Make the statement have a score. + $statement[0][$registrationid][0]['result']['score']['raw'] = 10; + $expected = 10; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_score($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsInt($result, "Expected result to be a integer"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_score with a pass condition. + * Successfully retrieves a scaled score + * . + * @return void + */ + public function testcmi5launch_retrieve_score_scaled_score() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnodisplay(1, $registrationid); + + // Make the statement have a score. + // Give it a float to test rounding. + $statement[0][$registrationid][0]['result']['score']['scaled'] = 10.504; + + $expected = 10.5; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_score($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsFloat($result, "Expected result to be a integer"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_score with a pass condition. + * Successfully returns a message there is no score in the statement. + * . + * @return void + */ + public function testcmi5launch_retrieve_score_no_score() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobjectdef(1, $registrationid); + + $expected = "(Score not retrieved or not present in statement)"; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_score($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a integer"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + + /** + * Test of the cmi5launch_retrieve_score with a pass condition. + * Successfully returns a message there is no result in the statement. + * . + * @return void + */ + public function testcmi5launch_retrieve_score_no_result() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobject(1, $registrationid); + + $expected = "(Score not retrieved or not present in statement)"; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_score($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a integer"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_score with a fail condition. + * Successfully returns an exception. + * . + * @return void + */ + public function testcmi5launch_retrieve_score_excep() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = "String to throw error"; + + $expected = "(Score not retrieved)"; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_score($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a integer"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + // There will also be an output message. + $expectedoutput = 'Unable to retrieve score from LRS. Caught exception: Cannot access offset of type string on string'; + $this->expectOutputString($expectedoutput); + } + + /** + * Test of the cmi5launch_retrieve_statements with a pass condition. + * Successfully returns a session object. + * . + * @return void + */ + public function testcmi5launch_retrieve_statements() + { - // Mock statements that should be returned. - $statementvalues[] = array( - $registrationid => array( - // They are nested under 0 since array_chunk is used. - 0 => array( - 'timestamp' => 'timestamp' . $i, - 'actor' => array ( - "firstname" => "firstname" . $i, - "lastname" => "lastname" . $i, - "account" => array ( - "homePage" => "homePage" . $i, - "name" => "name" . $i, - ), - ), - 'verb' => array ( - "id" => "verbid" . $i, - "display" => array( - "en" => "verbdisplay" . $i, - ), - ), - 'object' => array ( - "id" => "objectid" . $i, - "definition" => array ( - "name" => "name" . $i, - "description" => "description" . $i, - "type" => "type" . $i, - ), - ), - 'context' => array ( - "context" => "context" . $i, - "contexttype" => "contexttype" . $i, - "contextparent" => "contextparent" . $i, - ), - "result" => array ( - "result" => "result" . $i, - "score" => array ( - "raw" => "raw" . $i, - "scaled" => "scaled" . $i, - ), - ), - 'stored' => 'stored' . $i, - 'authority' => array ( - "authority" => "authority" . $i, - - ), - 'version' => "version" . $i, - ), - ) - ); + global $CFG, $DB, $sessionids; + + // First create a fake session to pass to the function. + $sessions = maketestsessions(); + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[1]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + // + // add a code to session + $session->code = "code0"; + // add a score to be graded + $session->score = 80; + // add a progress to be graded + + // Fake registration id. + $registrationid = "registrationid"; + // Make test statements to pass through from the mock function; + $statement = (maketeststatementsvalues(1, $registrationid)); + + // Make an array to add information to the session. + $newprogress = json_encode(["actorname0 verbdisplay0 objectname0 on 29-11-2023 07:00 pm"]); + $newscore = (80); + + // Copy the session so that we can tweak it and make how it should be then + // Compare with sessin object returned from the function. + $newsession = $session; + // Now update the session to match the expected session. + // Add the progress and score to the session. + $newsession->progress = $newprogress; + $newsession->score = $newscore; + + $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\progress') + ->onlyMethods(array('cmi5launch_request_statements_from_lrs')) + ->getMock(); + + // Mock returns statements, as it would be from the LRS. + $mockedclass->expects($this->once()) + ->method('cmi5launch_request_statements_from_lrs') + ->with($registrationid, $session) + ->willReturn($statement); + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Call the method under test. + $result = $mockedclass->cmi5launch_retrieve_statements($registrationid, $session); + + // The result should be a session. + $this->assertIsObject($result, "Expected result to be a session"); + // and should equal expected value. + $this->assertEquals($newsession, $result, "Expected result to match expected value"); + } - + + /** + * Test of the cmi5launch_retrieve_statements with a condition. + * Successfully catches an exception thrown when trying to retrieve extension info. + * . + * @return void + */ + public function testcmi5launch_retrieve_statements_excep_ext() + + { + global $CFG, $DB, $sessionids; + + // First create a fake session to pass to the function. + $sessions = maketestsessions(); + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[1]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + // + // add a code to session + $session->code = "code0"; + // add a score to be graded + $session->score = 80; + // add a progress to be graded + + // Fake registration id. + $registrationid = "registrationid"; + // Make test statements to pass through from the mock function; + $statement = maketeststatementsvaluesnoobject(1, $registrationid); + + // Make an array to add information to the session. + $newprogress = json_encode(["actorname0 verbdisplay0 objectname0 on 29-11-2023 07:00 pm"]); + $newscore = (80); + + // Copy the session so that we can tweak it and make how it should be then + // Compare with sessin object returned from the function. + $newsession = $session; + // Now update the session to match the expected session. + // Add the progress and score to the session. + $newsession->progress = $newprogress; + $newsession->score = $newscore; + + $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\progress') + ->onlyMethods(array('cmi5launch_request_statements_from_lrs')) + ->getMock(); + + // Mock returns statements, as it would be from the LRS. + $mockedclass->expects($this->once()) + ->method('cmi5launch_request_statements_from_lrs') + ->with($registrationid, $session) + ->willReturn($statement); + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Call the method under test. + $result = $mockedclass->cmi5launch_retrieve_statements($registrationid, $session); + + // There will also be an output message. + $expectedoutput = 'Unable to retrieve session id from LRS. Caught exception: Undefined array key "context". There may not be an extension key in statement.'; + $this->expectOutputString($expectedoutput); + } + /** + * Test of the cmi5launch_retrieve_statements with a condition. + * Successfully catches an exception thrown. + * . + * @return void + */ + public function testcmi5launch_retrieve_statements_excep() + + { + global $CFG, $DB, $sessionids; + + // First create a fake session to pass to the function. + $sessions = maketestsessions(); + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[1]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + // + // add a code to session + $session->code = "code0"; + // add a score to be graded + $session->score = 80; + // add a progress to be graded + + // Fake registration id. + $registrationid = "registrationid"; + // Make test statements to pass through from the mock function; + $statement = "String to throw error"; + + // Make an array to add information to the session. + $newprogress = json_encode(["actorname0 verbdisplay0 objectname0 on 29-11-2023 07:00 pm"]); + $newscore = (80); + + // Copy the session so that we can tweak it and make how it should be then + // Compare with sessin object returned from the function. + $newsession = $session; + // Now update the session to match the expected session. + // Add the progress and score to the session. + $newsession->progress = $newprogress; + $newsession->score = $newscore; + + $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\progress') + ->onlyMethods(array('cmi5launch_request_statements_from_lrs')) + ->getMock(); + + // Mock returns statements, as it would be from the LRS. + $mockedclass->expects($this->once()) + ->method('cmi5launch_request_statements_from_lrs') + ->with($registrationid, $session) + ->willReturn($statement); + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Call the method under test. + $result = $mockedclass->cmi5launch_retrieve_statements($registrationid, $session); + + // There will also be an output message. + $expectedoutput = 'Unable to retrieve statements from LRS. Caught exception: foreach() argument must be of type array|object, string given'; + $this->expectOutputString($expectedoutput); + + } } \ No newline at end of file