From 6418f499566d6d3248bbecd8fd4bf14767f57440 Mon Sep 17 00:00:00 2001 From: Edmund Farrow Date: Thu, 28 Nov 2024 16:21:46 +0000 Subject: [PATCH] quiz-data - export whole course unit tests --- classes/create_repo.php | 49 +++++++++++--- classes/export_repo.php | 80 ++++++++++++++++++---- tests/create_repo_test.php | 38 ++++++++++- tests/export_repo_test.php | 131 ++++++++++++++++++++++++++++++++++++- 4 files changed, 275 insertions(+), 23 deletions(-) diff --git a/classes/create_repo.php b/classes/create_repo.php index 4ed7edd..092843a 100644 --- a/classes/create_repo.php +++ b/classes/create_repo.php @@ -241,7 +241,7 @@ public function get_curl_request($wsurl): curl_request { * @param string $scriptdirectory - directory of CLI scripts * @return void */ - public function create_quiz_directories($clihelper, $scriptdirectory) { + public function create_quiz_directories(object $clihelper, string $scriptdirectory): void { $contextinfo = $clihelper->check_context($this, false, true); $arguments = $clihelper->get_arguments(); if ($arguments['directory']) { @@ -251,7 +251,11 @@ public function create_quiz_directories($clihelper, $scriptdirectory) { } $moodleinstance = $arguments['moodleinstance']; $instanceid = $arguments['instanceid']; - $token = $arguments['token'][$moodleinstance]; + if (is_array($arguments['token'])) { + $token = $arguments['token'][$moodleinstance]; + } else { + $token = $arguments['token']; + } $ignorecat = $arguments['ignorecat']; $ignorecat = ($ignorecat) ? ' -x "' . $ignorecat . '"' : ''; $quizlocations = []; @@ -260,15 +264,11 @@ public function create_quiz_directories($clihelper, $scriptdirectory) { $quizdirectory = cli_helper::get_quiz_directory($basedirectory, $quiz->name); $rootdirectory = $clihelper->create_directory($quizdirectory); echo "\nExporting quiz: {$quiz->name} to {$rootdirectory}\n"; - chdir($scriptdirectory); - $output = shell_exec('php createrepo.php -w -r "' . $rootdirectory . '" -i "' . $moodleinstance . - '" -l "module" -n ' . $instanceid . ' -t ' . $token . ' -x ' . $ignorecat); + $output = $this->call_repo_creation($rootdirectory, $moodleinstance, $instanceid, $token, $ignorecat, $scriptdirectory); echo $output; $quizmanifestname = cli_helper::get_manifest_path($moodleinstance, 'module', null, $contextinfo->contextinfo->coursename, $quiz->name, $rootdirectory); - chdir($scriptdirectory); - $output = shell_exec('php exportquizstructurefrommoodle.php -w -r "" -i "' . $moodleinstance . ' -t ' - . $token. ' -p "' . $this->manifestpath . '" -f "' . $quizmanifestname . '"'); + $output = $this->call_export_quiz($moodleinstance, $token, $quizmanifestname, $scriptdirectory); $quizlocation = new \StdClass(); $quizlocation->moduleid = $instanceid; $quizlocation->directory = basename($rootdirectory); @@ -289,4 +289,37 @@ public function create_quiz_directories($clihelper, $scriptdirectory) { exec('git commit -m "Initial Commit - final update"'); } } + + /** + * Separate out exec call for mocking. + * + * @param string $rootdirectory + * @param string $moodleinstance + * @param string $instanceid + * @param string $token + * @param string $ignorecat + * @return string + */ + public function call_repo_creation(string $rootdirectory, string $moodleinstance, string $instanceid, + string $token, string $ignorecat, string $scriptdirectory + ): string { + chdir($scriptdirectory); + return shell_exec('php createrepo.php -w -r "' . $rootdirectory . '" -i "' . $moodleinstance . + '" -l "module" -n ' . $instanceid . ' -t ' . $token . ' -x ' . $ignorecat); + } + + /** + * Separate out exec call for mocking. + * + * @param string $moodleinstance + * @param string $token + * @param string $quizmanifestname + * @return string + */ + public function call_export_quiz(string $moodleinstance, string $token, + string $quizmanifestname, string $scriptdirectory): string { + chdir($scriptdirectory); + return shell_exec('php exportquizstructurefrommoodle.php -w -r "" -i "' . $moodleinstance . ' -t ' + . $token. ' -p "' . $this->manifestpath . '" -f "' . $quizmanifestname . '"'); + } } diff --git a/classes/export_repo.php b/classes/export_repo.php index 23ba6cd..4d1353f 100644 --- a/classes/export_repo.php +++ b/classes/export_repo.php @@ -299,14 +299,18 @@ public function update_quiz_directories($clihelper, $scriptdirectory) { $contextinfo = $clihelper->check_context($this, false, true); $basedirectory = dirname($this->manifestpath); $moodleinstance = $arguments['moodleinstance']; - $token = $arguments['token'][$moodleinstance]; + if (is_array($arguments['token'])) { + $token = $arguments['token'][$moodleinstance]; + } else { + $token = $arguments['token']; + } $ignorecat = $arguments['ignorecat']; $ignorecat = ($ignorecat) ? ' -x "' . $ignorecat . '"' : ''; - $quizlocations = $this->manifestcontents->quizzes; + $quizlocations = isset($this->manifestcontents->quizzes) ? $this->manifestcontents->quizzes : []; $locarray = array_column($quizlocations, null, 'moduleid'); foreach ($contextinfo->quizzes as $quiz) { $instanceid = (int) $quiz->instanceid; - if (!isset($locarray[$instanceid]) || !is_dir(dirname($basedirectory) . '/' . $locarray[$instanceid]->directory)) { + if (!isset($locarray[$instanceid])) { $rootdirectory = $clihelper->create_directory(cli_helper::get_quiz_directory($basedirectory, $quiz->name)); if (!isset($locarray[$instanceid])) { $quizlocation = new \StdClass(); @@ -321,25 +325,77 @@ public function update_quiz_directories($clihelper, $scriptdirectory) { } } echo "\nExporting quiz: {$quiz->name} to {$rootdirectory}\n"; - chdir($scriptdirectory); - $output = shell_exec('php createrepo.php -w -r "' . $rootdirectory . '" -i "' . - $moodleinstance . '" -l "module" -n ' . $instanceid . ' -t ' . $token . ' -x ' . $ignorecat); + $output = $this->call_repo_creation($rootdirectory, $moodleinstance, + $instanceid, $token, $ignorecat, $scriptdirectory); + } else if (!is_dir(dirname($basedirectory) . '/' . $locarray[$instanceid]->directory)) { + $rootdirectory = dirname($basedirectory) . '/' . $locarray[$instanceid]->directory; + mkdir($rootdirectory); + echo "\nExporting quiz: {$quiz->name} to {$rootdirectory}\n"; + $output = $this->call_repo_creation($rootdirectory, $moodleinstance, + $instanceid, $token, $ignorecat, $scriptdirectory); } else { $rootdirectory = dirname($basedirectory) . '/' . $locarray[$instanceid]->directory; echo "\nExporting quiz: {$quiz->name} to {$rootdirectory}\n"; - chdir($scriptdirectory); $quizmanifestname = cli_helper::get_manifest_path($moodleinstance, 'module', null, $contextinfo->contextinfo->coursename, $quiz->name, ''); - $output = shell_exec('php exportrepofrommoodle.php -w -r "' . $rootdirectory . '" -i "' . - $moodleinstance . '" -f "' . $quizmanifestname . '" -t ' . $token); + $output = $this->call_export_repo($rootdirectory, $moodleinstance, $token, + $quizmanifestname, $scriptdirectory); } echo $output; $quizmanifestname = cli_helper::get_manifest_path($moodleinstance, 'module', null, $contextinfo->contextinfo->coursename, $quiz->name, $rootdirectory); - chdir($scriptdirectory); - $output = shell_exec('php exportquizstructurefrommoodle.php -w -r "" -i "' . $moodleinstance . ' -t ' - . $token. ' -p "' . $this->manifestpath. '" -f "' . $quizmanifestname . '"'); + $output = $this->call_export_quiz($moodleinstance, $token, $quizmanifestname, $scriptdirectory); echo $output; } } + + /** + * Separate out exec call for mocking. + * + * @param string $rootdirectory + * @param string $moodleinstance + * @param string $instanceid + * @param string $token + * @param string $ignorecat + * @return string + */ + public function call_repo_creation(string $rootdirectory, string $moodleinstance, string $instanceid, + string $token, string $ignorecat, string $scriptdirectory + ): string { + chdir($scriptdirectory); + return shell_exec('php createrepo.php -w -r "' . $rootdirectory . '" -i "' . $moodleinstance . + '" -l "module" -n ' . $instanceid . ' -t ' . $token . ' -x ' . $ignorecat); + } + + /** + * Separate out exec call for mocking. + * + * @param string $moodleinstance + * @param string $token + * @param string $quizmanifestname + * @return string + */ + public function call_export_quiz(string $moodleinstance, string $token, + string $quizmanifestname, string $scriptdirectory): string { + chdir($scriptdirectory); + return shell_exec('php exportquizstructurefrommoodle.php -w -r "" -i "' . $moodleinstance . ' -t ' + . $token. ' -p "' . $this->manifestpath . '" -f "' . $quizmanifestname . '"'); + } + + /** + * Separate out exec call for mocking. + * + * @param string $rootdirectory + * @param string $moodleinstance + * @param string $token + * @param string $quizmanifestname + * @param string $scriptdirectory + * @return string + */ + public function call_export_repo(string $rootdirectory, string $moodleinstance, string $token, + string $quizmanifestname, string $scriptdirectory): string { + chdir($scriptdirectory); + return shell_exec('php exportrepofrommoodle.php -w -r "' . $rootdirectory . '" -i "' . + $moodleinstance . '" -f "' . $quizmanifestname . '" -t ' . $token); + } } diff --git a/tests/create_repo_test.php b/tests/create_repo_test.php index 56afad9..99a0f25 100644 --- a/tests/create_repo_test.php +++ b/tests/create_repo_test.php @@ -57,7 +57,8 @@ public function setUp(): void { parent::setUp(); global $CFG; $this->moodleinstances = [self::MOODLE => 'fakeurl.com']; - vfsStream::setup(); + $root = vfsStream::setup(); + vfsStream::copyFromFileSystem($CFG->dirroot . '/question/bank/gitsync/testrepoparent/', $root); $this->rootpath = vfsStream::url('root'); // Mock the combined output of command line options and defaults. @@ -75,6 +76,7 @@ public function setUp(): void { 'token' => 'XXXXXX', 'help' => false, 'ignorecat' => null, + 'usegit' => false, ]; $this->clihelper = $this->getMockBuilder(\qbank_gitsync\cli_helper::class)->onlyMethods([ 'get_arguments', 'check_context', @@ -93,7 +95,7 @@ public function setUp(): void { 'execute', ])->setConstructorArgs(['xxxx'])->getMock();; $this->createrepo = $this->getMockBuilder(\qbank_gitsync\create_repo::class)->onlyMethods([ - 'get_curl_request', + 'get_curl_request', 'call_repo_creation', 'call_export_quiz' ])->setConstructorArgs([$this->clihelper, $this->moodleinstances])->getMock(); $this->createrepo->curlrequest = $this->curl; $this->createrepo->listcurlrequest = $this->listcurl; @@ -302,4 +304,36 @@ public function test_process_with_subcategory_id(): void { $this->assertEquals("top/Default-for-Test-1/sub-2", $manifest->context->defaultsubdirectory); $this->assertEquals(123, $manifest->context->defaultsubcategoryid); } + + /** + * Test full course. + */ + public function test_full_course(): void { + $this->options['directory'] = 'testrepo'; + $this->clihelper = $this->getMockBuilder(\qbank_gitsync\cli_helper::class)->onlyMethods([ + 'get_arguments', 'check_context', + ])->setConstructorArgs([[]])->getMock(); + $this->clihelper->expects($this->any())->method('get_arguments')->will($this->returnValue($this->options)); + $this->clihelper->expects($this->exactly(1))->method('check_context')->willReturn( + json_decode('{"contextinfo":{"contextlevel": "module", "categoryname":"", "coursename":"Course 1", + "modulename":"Module 1", "instanceid":"", "qcategoryname":"top", "qcategoryid":123}, + "questions": [], + "quizzes": [{"instanceid":"1", "name":"Quiz 1"}, {"instanceid":"2", "name":"Quiz 2"}]}') + ); + $this->createrepo->create_quiz_directories($this->clihelper, $this->rootpath . '/testrepoparent'); + + // Should have created a directory for each quiz and updated the manifest with locations. + $this->assertEquals(true, is_dir($this->rootpath . "/testrepo_quiz_quiz-1_1")); + $this->assertEquals(true, is_dir($this->rootpath . "/testrepo_quiz_quiz-2")); + $this->assertEquals(false, is_dir($this->rootpath . "/testrepo_quiz_quiz-3")); + + $manifestcontents = json_decode(file_get_contents($this->rootpath . '/fakeexport_system_question_manifest.json')); + $this->assertEquals('1', $manifestcontents->quizzes[0]->moduleid); + $this->assertEquals('2', $manifestcontents->quizzes[1]->moduleid); + $this->assertEquals('testrepo_quiz_quiz-1_1', $manifestcontents->quizzes[0]->directory); + $this->assertEquals('testrepo_quiz_quiz-2', $manifestcontents->quizzes[1]->directory); + $this->expectOutputRegex( + '/^\nExporting quiz: Quiz 1.*testrepo_quiz_quiz-1_1.*Exporting quiz: Quiz 2.*testrepo_quiz_quiz-2.*$/s' + ); + } } diff --git a/tests/export_repo_test.php b/tests/export_repo_test.php index d9b64ce..25f582e 100644 --- a/tests/export_repo_test.php +++ b/tests/export_repo_test.php @@ -114,7 +114,7 @@ public function setUp(): void { 'execute', ])->setConstructorArgs(['xxxx'])->getMock(); $this->exportrepo = $this->getMockBuilder(\qbank_gitsync\export_repo::class)->onlyMethods([ - 'get_curl_request', 'call_exit', + 'get_curl_request', 'call_exit', 'call_repo_creation', 'call_export_quiz', 'call_export_repo' ])->setConstructorArgs([$this->clihelper, $this->moodleinstances])->getMock(); $this->exportrepo->curlrequest = $this->curl; $this->exportrepo->listcurlrequest = $this->listcurl; @@ -423,4 +423,133 @@ public function test_check_content_default_warning(): void { $this->expectOutputRegex('/Using default question category from manifest file./'); } + /** + * Test full course where quizzes are not in manifest. + */ + public function test_full_course_not_in_manifest(): void { + global $CFG; + $root = vfsStream::setup(); + vfsStream::copyFromFileSystem($CFG->dirroot . '/question/bank/gitsync/testrepoparent/', $root); + $this->rootpath = vfsStream::url('root'); + $this->options['rootdirectory'] = $this->rootpath; + $this->options['manifestpath'] = '/testrepo/' . self::MOODLE . '_system' . cli_helper::MANIFEST_FILE; + $this->clihelper = $this->getMockBuilder(\qbank_gitsync\cli_helper::class)->onlyMethods([ + 'get_arguments', 'check_context', + ])->setConstructorArgs([[]])->getMock(); + $this->clihelper->expects($this->any())->method('get_arguments')->will($this->returnValue($this->options)); + $this->clihelper->expects($this->exactly(2))->method('check_context')->willReturn( + json_decode('{"contextinfo":{"contextlevel": "module", "categoryname":"", "coursename":"Course 1", + "modulename":"Module 1", "instanceid":"", "qcategoryname":"top", "qcategoryid":123}, + "questions": [], + "quizzes": [{"instanceid":"1", "name":"Quiz 1"}, {"instanceid":"2", "name":"Quiz 2"}]}') + ); + $this->exportrepo = $this->getMockBuilder(\qbank_gitsync\export_repo::class)->onlyMethods([ + 'get_curl_request', 'call_exit', 'call_repo_creation', 'call_export_quiz', 'call_export_repo' + ])->setConstructorArgs([$this->clihelper, $this->moodleinstances])->getMock(); + + $this->exportrepo->curlrequest = $this->curl; + $this->exportrepo->listcurlrequest = $this->listcurl; + $this->exportrepo->update_quiz_directories($this->clihelper, $this->rootpath . '/testrepoparent'); + + // Should have created a directory for each quiz and updated the manifest with locations. + $this->assertEquals(true, is_dir($this->rootpath . "/testrepo_quiz_quiz-1")); + $this->assertEquals(true, is_dir($this->rootpath . "/testrepo_quiz_quiz-2")); + $this->assertEquals(false, is_dir($this->rootpath . "/testrepo_quiz_quiz-3")); + + $manifestcontents = json_decode(file_get_contents($this->rootpath . '/testrepo/fakeexport_system_question_manifest.json')); + $this->assertEquals('1', $manifestcontents->quizzes[0]->moduleid); + $this->assertEquals('2', $manifestcontents->quizzes[1]->moduleid); + $this->assertEquals('testrepo_quiz_quiz-1_1', $manifestcontents->quizzes[0]->directory); + $this->assertEquals('testrepo_quiz_quiz-2', $manifestcontents->quizzes[1]->directory); + $this->expectOutputRegex( + '/^\nExporting quiz: Quiz 1.*testrepo_quiz_quiz-1_1.*Exporting quiz: Quiz 2.*testrepo_quiz_quiz-2.*$/s' + ); + } + + /** + * Test full course where quizzes are in manifest but there's no directories. + */ + public function test_full_course_in_manifest_no_directories(): void { + global $CFG; + $root = vfsStream::setup(); + vfsStream::copyFromFileSystem($CFG->dirroot . '/question/bank/gitsync/testrepoparent/', $root); + $this->rootpath = vfsStream::url('root'); + $this->options['rootdirectory'] = $this->rootpath; + $this->options['manifestpath'] = '/testrepo/' . self::MOODLE . '_system' . cli_helper::MANIFEST_FILE; + $this->clihelper = $this->getMockBuilder(\qbank_gitsync\cli_helper::class)->onlyMethods([ + 'get_arguments', 'check_context', + ])->setConstructorArgs([[]])->getMock(); + $this->clihelper->expects($this->any())->method('get_arguments')->will($this->returnValue($this->options)); + $this->clihelper->expects($this->exactly(2))->method('check_context')->willReturn( + json_decode('{"contextinfo":{"contextlevel": "module", "categoryname":"", "coursename":"Course 1", + "modulename":"Module 1", "instanceid":"", "qcategoryname":"top", "qcategoryid":123}, + "questions": [], + "quizzes": [{"instanceid":"1", "name":"Quiz 1"}, {"instanceid":"2", "name":"Quiz 2"}]}') + ); + $this->exportrepo = $this->getMockBuilder(\qbank_gitsync\export_repo::class)->onlyMethods([ + 'get_curl_request', 'call_exit', 'call_repo_creation', 'call_export_quiz', 'call_export_repo' + ])->setConstructorArgs([$this->clihelper, $this->moodleinstances])->getMock(); + + $this->exportrepo->curlrequest = $this->curl; + $this->exportrepo->listcurlrequest = $this->listcurl; + $holder1 = new \StdClass(); + $holder1->moduleid = '1'; + $holder1->directory = '/quiz_1_dir'; + $holder2 = new \StdClass(); + $holder2->moduleid = '2'; + $holder2->directory = '/quiz_2_dir'; + $this->exportrepo->manifestcontents->quizzes = [$holder1, $holder2]; + $this->exportrepo->update_quiz_directories($this->clihelper, $this->rootpath . '/testrepoparent'); + + // Should have created a directory for each quiz and but not updated the manifest. + $this->assertEquals(true, is_dir($this->rootpath . "/quiz_1_dir")); + $this->assertEquals(true, is_dir($this->rootpath . "/quiz_2_dir")); + $this->assertEquals(false, is_dir($this->rootpath . "/testrepo_quiz_quiz-2")); + + $manifestcontents = json_decode(file_get_contents($this->exportrepo->manifestpath)); + $this->assertEquals(false, isset($manifestcontents->quizzes)); + $this->expectOutputRegex( + '/^\nExporting quiz: Quiz 1.*quiz_1_dir.*Exporting quiz: Quiz 2.*quiz_2_dir.*$/s' + ); + } + + /** + * Test full course where quizzes are in manifest and directory already exists. + */ + public function test_full_course_in_manifest_existing_directories(): void { + global $CFG; + $root = vfsStream::setup(); + vfsStream::copyFromFileSystem($CFG->dirroot . '/question/bank/gitsync/testrepoparent/', $root); + $this->rootpath = vfsStream::url('root'); + $this->options['rootdirectory'] = $this->rootpath; + $this->options['manifestpath'] = '/testrepo/' . self::MOODLE . '_system' . cli_helper::MANIFEST_FILE; + $this->clihelper = $this->getMockBuilder(\qbank_gitsync\cli_helper::class)->onlyMethods([ + 'get_arguments', 'check_context', + ])->setConstructorArgs([[]])->getMock(); + $this->clihelper->expects($this->any())->method('get_arguments')->will($this->returnValue($this->options)); + $this->clihelper->expects($this->exactly(2))->method('check_context')->willReturn( + json_decode('{"contextinfo":{"contextlevel": "module", "categoryname":"", "coursename":"Course 1", + "modulename":"Module 1", "instanceid":"", "qcategoryname":"top", "qcategoryid":123}, + "questions": [], + "quizzes": [{"instanceid":"1", "name":"Quiz 1"}]}') + ); + $this->exportrepo = $this->getMockBuilder(\qbank_gitsync\export_repo::class)->onlyMethods([ + 'get_curl_request', 'call_exit', 'call_repo_creation', 'call_export_quiz', 'call_export_repo' + ])->setConstructorArgs([$this->clihelper, $this->moodleinstances])->getMock(); + + $this->exportrepo->curlrequest = $this->curl; + $this->exportrepo->listcurlrequest = $this->listcurl; + $holder1 = new \StdClass(); + $holder1->moduleid = '1'; + $holder1->directory = '/testrepo_quiz_quiz-1'; + $this->exportrepo->manifestcontents->quizzes = [$holder1]; + $this->exportrepo->update_quiz_directories($this->clihelper, $this->rootpath . '/testrepoparent'); + + // Should have not updated the manifest. + $manifestcontents = json_decode(file_get_contents($this->exportrepo->manifestpath)); + $this->assertEquals(false, isset($manifestcontents->quizzes)); + $this->expectOutputRegex( + '/^\nExporting quiz: Quiz 1.*testrepo_quiz_quiz-1\n$/s' + ); + } }