diff --git a/.github/workflows/behat.yaml b/.github/workflows/behat.yaml new file mode 100644 index 0000000..2066feb --- /dev/null +++ b/.github/workflows/behat.yaml @@ -0,0 +1,106 @@ +on: + push: + branches: + - master + - develop + - tests/behat + pull_request: + branches: + - master + - develop + workflow_dispatch: +name: Run behat tests to verify changes +jobs: + behat: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + #php: [ 8.1, 8.2, 8.3 ] + php: [ 8.3 ] + steps: + - name: Split repository name to get owner and repository name + id: split + run: | + IFS='/' read -r owner repo <<< "${{ github.repository }}" + echo "OWNER=$owner" >> $GITHUB_ENV + echo "REPO=$repo" >> $GITHUB_ENV + echo "OWNER_REPO=$owner\_$repo" >> $GITHUB_ENV + echo "REPO=$repo" >> $GITHUB_OUTPUT + echo "OWNER=$owner" >> $GITHUB_OUTPUT + - name: Checkout EasyEngine repository + uses: actions/checkout@v4 + with: + repository: EasyEngine/easyengine + path: easyengine + lfs: true + submodules: true + - name: Checkout This repository + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }} + path: ${{ steps.split.outputs.REPO }} + lfs: true + submodules: true + + - uses: shivammathur/setup-php@v2 + with: + php-version: '${{ matrix.php }}' + coverage: none + tools: composer + extensions: pcntl, curl, sqlite3, zip, dom, mbstring, json + - name: Print PHP version and Modules + run: | + php -v + php -m + composer -V + + - name: Install composer dependencies + run: | + cd easyengine + composer install --no-interaction --no-progress --no-suggest --prefer-dist + cd ${{ github.workspace }}/${{ steps.split.outputs.REPO }} + composer install --no-interaction --no-progress --no-suggest --prefer-dist + cd ${{ github.workspace }} + + - name: Replace easyengine dependency with local path + run: | + cd ${{ github.workspace }} + rm -rf easyengine/vendor/${{ steps.split.outputs.OWNER }}/${{ steps.split.outputs.REPO }} + mkdir -p easyengine/vendor/${{ steps.split.outputs.OWNER }} + ln -s $(pwd)/${{ steps.split.outputs.REPO }} easyengine/vendor/${{ steps.split.outputs.OWNER }}/${{ steps.split.outputs.REPO }} + cd easyengine + composer dump-autoload + cd ${{ github.workspace }} + + - name: Update docker + run: | + sudo apt purge nginx nginx-common docker docker-engine docker.io docker-ce containerd runc + curl -fsSL https://get.docker.com/ | sudo bash + sudo systemctl restart docker.service + + - name: Install docker-compose + run: | + sudo curl -L https://github.com/docker/compose/releases/download/v2.27.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + + - name: build easyengine phar + run: | + cd ${{ github.workspace }}/easyengine + composer dump-autoload --optimize --no-dev --no-interaction + php -dphar.readonly=0 utils/make-phar.php easyengine.phar + mv easyengine.phar /usr/local/bin/ee + chmod +x /usr/local/bin/ee + cd ${{ github.workspace }} + + - name: Add easyengine and behat to PATH + run: | + echo "${{ github.workspace }}/easyengine/bin" >> $GITHUB_PATH + echo "${{ github.workspace }}/easyengine/vendor/bin" >> $GITHUB_PATH + - name: Run behat tests + run: | + cd ${{ github.workspace }}/easyengine + composer dump-autoload --no-interaction + cd ${{ github.workspace }}/${{ steps.split.outputs.REPO }} + sudo ${{ github.workspace }}/easyengine/vendor/bin/behat --strict --no-interaction --no-colors + cd - diff --git a/behat.yml b/behat.yml new file mode 100644 index 0000000..247bafc --- /dev/null +++ b/behat.yml @@ -0,0 +1,32 @@ +default: + suites: + list: + paths: + - "%paths.base%/features/list.feature" + contexts: + - "SharedContext" + - "ListContext" + create: + paths: + - "%paths.base%/features/create.feature" + contexts: + - "SharedContext" + - "CreateContext" + delete: + paths: + - "%paths.base%/features/delete.feature" + contexts: + - "SharedContext" + - "DeleteContext" + run-now: + paths: + - "%paths.base%/features/run-now.feature" + contexts: + - "SharedContext" + - "RunNowContext" + update: + paths: + - "%paths.base%/features/update.feature" + contexts: + - "SharedContext" + - "UpdateContext" diff --git a/features/bootstrap/CreateContext.php b/features/bootstrap/CreateContext.php new file mode 100644 index 0000000..da17831 --- /dev/null +++ b/features/bootstrap/CreateContext.php @@ -0,0 +1,81 @@ +shared_context = $scope->getEnvironment()->getContext(SharedContext::class); + } + + /** + * @When I create a cron for site `:site` with schedule `:schedule` and command `:command` + */ + function create_cron(string $site, string $schedule, string $command) + { + $this->site = $site; + $this->schedule = $schedule; + $this->cronCommand = $command; + $this->shared_context->command = "ee cron create $site --schedule=\"$schedule\" --command=\"$command\""; + exec($this->shared_context->command, $output, $return_status); + $this->shared_context->output = implode($output); + $this->shared_context->return_status = $return_status; + } + + /** + * @Then I should see the relavant cron job in list of crons for site `:site` + * @throws Exception: If the cron job is not found + */ + function cron_job_exists(string $site) + { + exec("ee cron list $site", $output, $return_status); + if ($return_status !== 0) { + throw new Exception("Could not list crons for site $site. Got a return status code of $return_status with the following output:\n\n" . implode($output)); + } + if ( + // All three of these should be present in the output, otherwise the cron job was not created properly + strpos(implode($output), $this->cronCommand) === false || + strpos(implode($output), $this->schedule) === false || + strpos(implode($output), $this->site) === false + ) { + throw new Exception("Could not find the cron job in the list of crons for site $site. The cron job was supposed to be:\n\n" . $this->cronCommand . "\n\nBut the list of crons for site $site was:\n\n" . implode($output)); + } + } + + /** + * @Then Cron creation should show an error message + * @throws Exception: If the cron creation does not fail + */ + function cron_creation_errors() + { + if ( trim($this->shared_context->output) === "" ) { + # Error message is thrown directly to stderr, so there shouldn't be any output + return; + } + throw new Exception("Cron creation did not fail as expected. The output was:\n\n" . $this->shared_context->output); + } +} diff --git a/features/bootstrap/DeleteContext.php b/features/bootstrap/DeleteContext.php new file mode 100644 index 0000000..288aca6 --- /dev/null +++ b/features/bootstrap/DeleteContext.php @@ -0,0 +1,96 @@ +shared_context = $scope->getEnvironment()->getContext(SharedContext::class); + } + + /** + * @When I delete that cron job + */ + function delete_created_cron() + { + $this->shared_context->command = "ee cron delete {$this->shared_context->cron_created}"; + exec($this->shared_context->command, $output, $return_status); + $this->shared_context->output = implode($output); + $this->shared_context->return_status = $return_status; + } + + /** + * @Then I should see success message for deleting cron job + * @throws Exception: If the output is not as expected + */ + function success_message_for_deleting_cron() + { + $id_to_delete = $this->shared_context->cron_created; + if (false === strpos($this->shared_context->output, "Success: Deleted cron with id $id_to_delete")) { + throw new Exception("Expected output to contain `Deleted cron job` but got:\n\n" . $this->shared_context->output); + } + } + + /** + * @Then I should not see the cron job in the list of crons + * @throws Exception: If the output is not as expected + */ + function cron_deleted() + { + exec("ee cron list --all | awk '{print $1}'", $output, $return_status); + $output = array_map( + function ($line) { + return trim($line); + }, + $output + ); + if (in_array((string) $this->shared_context->cron_created, $output)) { + throw new Exception("Cron job was not deleted. Expected output to not contain `{$this->shared_context->sites_created[0]}` but got:\n\n" . $output); + } + } + + /** + * @When I delete a cron job that does not exist + * @throws Exception: If the randomness is not random? + */ + function delete_non_existent_cron() { + $this->shared_context->cron_created = random_int(1, 65535); + $this->shared_context->command = "ee cron delete {$this->shared_context->cron_created}"; + exec($this->shared_context->command, $output, $return_status); + $this->shared_context->output = implode($output); + $this->shared_context->return_status = $return_status; + } + + /** + * @Then I should see an error message for deleting cron job + * @throws Exception: If the output is not empty + */ + function error_message_for_deleting_non_existent_cron() { + if ("" === trim($this->shared_context->output)) { + // Error is directly thrown to stderr + return; + } + throw new Exception("Expected an error message for deleting a non-existent cron job but got an empty output."); + } +} diff --git a/features/bootstrap/ListContext.php b/features/bootstrap/ListContext.php new file mode 100644 index 0000000..45fa37b --- /dev/null +++ b/features/bootstrap/ListContext.php @@ -0,0 +1,97 @@ +shared_context = $scope->getEnvironment()->getContext(SharedContext::class); + } + + /** + * @When I list all of the cron entries + */ + function list_cron() + { + $this->shared_context->command = "ee cron list --all"; + exec($this->shared_context->command, $output, $return_status); + $this->shared_context->output = implode($output); + $this->shared_context->return_status = $return_status; + } + + + /** + * @Then I get an error message for no cron jobs + * @throws Exception: If the output is not empty + */ + function no_cron_error() + { + if ("" !== trim($this->shared_context->output)) { + throw new Exception(unexpectedOutput($this->shared_context->command, $this->shared_context->output, $this->shared_context->return_status)); + } + } + + /** + * @Then I should see a list of cron jobs for those sites + * @throws Exception: If the site name is not found in the output + */ + function crons_for_all_sites() { + $this->shared_context->command = "ee cron list --all"; + exec($this->shared_context->command, $output, $return_status); + $this->shared_context->output = implode($output); + $this->shared_context->return_status = $return_status; + + foreach ($this->shared_context->sites_created as $site) { + if (strpos($this->shared_context->output, $site) === false) { + throw new Exception("Could not find cron job for site $site in the output of the command: " . $this->shared_context->output); + } + } + } + + + /** + * @When I list cron jobs for the site `:site_name` + */ + function list_cron_for_site(string $site_name) { + $this->shared_context->command = "ee cron list $site_name"; + exec($this->shared_context->command, $output, $return_status); + $this->shared_context->output = implode($output); + $this->shared_context->return_status = $return_status; + } + + /** + * @Then I should see a list of cron jobs for the site `:site_name` + * @throws Exception: If the site name is not found in the output + */ + function crons_for_site(string $site_name) { + if (strpos($this->shared_context->output, $site_name) === false) { + throw new Exception("Could not find cron job for site $site_name in the output of the command: " . $this->shared_context->output); + } + } + +} diff --git a/features/bootstrap/RunNowContext.php b/features/bootstrap/RunNowContext.php new file mode 100644 index 0000000..7ac3cca --- /dev/null +++ b/features/bootstrap/RunNowContext.php @@ -0,0 +1,67 @@ +shared_context = $scope->getEnvironment()->getContext(SharedContext::class); + } + + /** + * @When I run that cron job immediately + */ + function run_cron_immediately() + { + $this->shared_context->command = "ee cron run-now {$this->shared_context->cron_created}"; + exec($this->shared_context->command, $output, $return_status); + $this->shared_context->output = implode($output); + $this->shared_context->return_status = $return_status; + } + + /** + + * @When I run a cron job that does not exist + * @throws Exception: If the randomness is not random? + */ + function delete_non_existent_cron() { + $this->shared_context->cron_created = random_int(1, 65535); + $this->shared_context->command = "ee cron run-now {$this->shared_context->cron_created}"; + exec($this->shared_context->command, $output, $return_status); + $this->shared_context->output = implode($output); + $this->shared_context->return_status = $return_status; + } + + /** + * @Then I should see an error message for cron job not found + * @throws Exception: If the output is not empty + */ + function error_message_for_deleting_non_existent_cron() { + if ("" === trim($this->shared_context->output)) { + // Error is directly thrown to stderr + return; + } + throw new Exception(unexpectedOutput($this->shared_context->command, $this->shared_context->output, $this->shared_context->return_status)); + } +} diff --git a/features/bootstrap/SharedContext.php b/features/bootstrap/SharedContext.php new file mode 100644 index 0000000..90986f4 --- /dev/null +++ b/features/bootstrap/SharedContext.php @@ -0,0 +1,151 @@ +sites_created[] = $site_name; + exec("ee site create $site_name --type=$site_type", $output, $return_status); + if ($return_status !== 0) { + throw new Exception("Could not create site $site_name with type $site_type. Output: " . implode($output)); + } + } + + /** + * @Then exit code must be 0 + * @throws Exception: If the return status is not 0 + */ + function zero_status() { + if ($this->return_status !== 0) { + throw new Exception(unexpectedOutput($this->command, $this->output, $this->return_status)); + } + } + + /** + * @Then Exit Code must not be 0 + * @throws Exception: If the return status is 0 + */ + function non_zero_status() + { + if ($this->return_status === 0) { + throw new Exception(unexpectedOutput($this->command, $this->output, $this->return_status)); + } + } + + /** + * @Then I should see `:message` + * @throws Exception: If the output does not contain the success message + */ + function see_message(string $message) + { + if (strpos(trim($this->output), $message) === false) { + throw new Exception("Could not find the message '$message' in the output of the command: " . $this->output); + } + } + + /** + * @Given I created cron for site `:site_name` with schedule `:schedule` and command `:command` + */ + function create_cron(string $site_name, string $schedule, string $command) { + $to_exec = "ee cron create $site_name --schedule=\"$schedule\" --command=\"$command\""; + exec($to_exec, $_, $return_status); + if ($return_status !== 0) { + throw new Exception("Could not create cron job for site $site_name with schedule $schedule and command $command. Output:\n" . $output); + } + // Cron is created, now we need to find the cron id + exec( + "ee cron list $site_name | grep \"$site_name\" | grep \"$schedule\" | grep \"$command\" | awk '{print $1}'", + $_output, $return_status + ); + if ($return_status !== 0) { + throw new Exception("Could not find the cron job in the list of crons. Output:\n" . $output); + } + $this->cron_created = (int) $_output[0]; // The cron id + } + + + /** + * After Scenario Cleanup Hook + * + * @AfterScenario + */ + function after_scenario_cleanup() { + $this->command = ""; + $this->output = ""; + $this->return_status = 0; + $this->cron_created = -1; + + foreach ($this->sites_created as $site) { + exec("ee site delete $site --yes"); + } + $this->sites_created = []; + } + +} diff --git a/features/bootstrap/UpdateContext.php b/features/bootstrap/UpdateContext.php new file mode 100644 index 0000000..5844df0 --- /dev/null +++ b/features/bootstrap/UpdateContext.php @@ -0,0 +1,66 @@ +shared_context = $scope->getEnvironment()->getContext(SharedContext::class); + } + + /** + * @When I update that cron to site `:site_name` with schedule `:schedule` and command `:command` + */ + function update_created_cron(string $site_name, string $schedule, string $command) { + $this->updated_site_name = $site_name; + $this->updated_schedule = $schedule; + $this->updated_command = $command; + $this->shared_context->command = "ee cron update {$this->shared_context->cron_created} --site=\"$site_name\" --schedule=\"$schedule\" --command=\"$command\""; + exec($this->shared_context->command, $output, $return_status); + $this->shared_context->output = implode($output); + $this->shared_context->return_status = $return_status; + } + + /** + * @Then The cron job listing should reflect the changes + * @throws Exception when the changes are not reflected properly + */ + function check_updated_cron() { + $cron_id = $this->shared_context->cron_created; + // Filter out the output to only include our specific cron job + exec("ee cron list $this->updated_site_name | grep '^$cron_id\s'", $output, $return_status); + $output = implode($output); + if ( + false === strpos($output, $this->shared_context->cron_created) || + false === strpos($output, $this->updated_site_name) || + false === strpos($output, $this->updated_schedule) || + false === strpos($output, $this->updated_command) + ) { + throw new Exception("Expected output to contain the updated cron job details but got:\n\n" . $output); + } + } +} diff --git a/features/create.feature b/features/create.feature new file mode 100644 index 0000000..f1dd644 --- /dev/null +++ b/features/create.feature @@ -0,0 +1,24 @@ +Feature: Cron-Command -> Create + We have a command that we make use of in order to manage cron-jobs + run by easyengine using ofelia cron manager. This is a subcommand + used to create any cron jobs which are to be run on a particular + site's containers. + + Scenario: + I can use the create command to create a cron job for an existing site + + Given EE is present + And I created site `test.site` with type `wp` + When I create a cron for site `test.site` with schedule `"* 1 * * *"` and command `"echo Hello World"` + Then I should see `"Success: Cron created successfully"` + And I should see the relavant cron job in list of crons for site `test.site` + And Exit Code must be 0 + + Scenario: + I can not create a cron job for a non-existing site, the command should error out + + Given EE is present + # Notice that the site `test.site` has not been created + When I create a cron for site `test.site` with schedule `"* 1 * * *"` and command `"echo Hello World"` + Then Cron creation should show an error message + And Exit Code must not be 0 diff --git a/features/delete.feature b/features/delete.feature new file mode 100644 index 0000000..41d2064 --- /dev/null +++ b/features/delete.feature @@ -0,0 +1,25 @@ +Feature: Cron-Command -> Delete + We have a command that we make use of in order to manage cron-jobs + run by easyengine using ofelia cron manager. This is a subcommand + used to remove a cron-job from the list of cron-jobs managed by + ofelia, based on the said job's ID. + + Scenario: + I can use the delete command to remove a cron-job from the list + of cron-jobs managed by ofelia, based on the said job's ID. + + Given EE is present + And I created site `test.site` with type `wp` + And I created cron for site `test.site` with schedule `"* * * * *"` and command `"echo Hello World"` + When I delete that cron job + Then I should see success message for deleting cron job + And I should not see the cron job in the list of crons + And Exit Code must be 0 + +Scenario: + If I try to delete a cron-job that does not exist, I should see an error message + + Given EE is present + When I delete a cron job that does not exist + Then I should see an error message for deleting cron job + And Exit Code must not be 0 diff --git a/features/list.feature b/features/list.feature new file mode 100644 index 0000000..de1776d --- /dev/null +++ b/features/list.feature @@ -0,0 +1,51 @@ +Feature: Cron-Command -> List + We have a command that we make use of in order to manage cron-jobs + run by easyengine using ofelia cron manager. This is a subcommand + used to list all of the cron jobs that are currently configured to + be managed and run by EasyEngine. + + Scenario: + If I list all of the cron entries and there are no sites + present, then I should get an error message for no cron jobs + + Given EE is present + And No site has been created + When I list all of the cron entries + Then I get an error message for no cron jobs + And Exit Code must not be 0 + + Scenario: + If I list all of the cron entries and there are sites + which require a cron service (type wp), then I should + get a list of cron jobs for those sites + + Given EE is present + And I created site `test.site` with type `wp` + And I created site `test2.site` with type `wp` + And I created site `test3.site` with type `wp` + When I list all of the cron entries + Then I should see a list of cron jobs for those sites + And Exit Code must be 0 + + Scenario: + If I list all of the cron entries for a site which does not + requires a cron service, then I should get an error message + for no cron jobs + + Given EE is present + And I created site `test.site` with type `html` + When I list cron jobs for the site `test.site` + Then I get an error message for no cron jobs + And Exit Code must not be 0 + + + Scenario: + If I list all of the cron entries for a site which requires + a cron service, then I should get a list of cron jobs for + that site + + Given EE is present + And I created site `test.site` with type `wp` + When I list cron jobs for the site `test.site` + Then I should see a list of cron jobs for the site `test.site` + And Exit Code must be 0 diff --git a/features/run-now.feature b/features/run-now.feature new file mode 100644 index 0000000..2525b5b --- /dev/null +++ b/features/run-now.feature @@ -0,0 +1,25 @@ +Feature: Cron-Command -> Run-Now + We have a command that we make use of in order to manage cron-jobs + run by easyengine using ofelia cron manager. This is a subcommand + used to execute a given cron-job immediately based on its id. + + Scenario: + If I have a cron-job that is scheduled to run at a later time + and I want to run it immediately, I can make use of the + `ee cron run-now` command to execute the cron-job immediately, + and get its output in the console. + + Given EE is present + And I created site `test.site` with type `wp` + And I created cron for site `test.site` with schedule `"* * * * *"` and command `"echo Hello World"` + When I run that cron job immediately + Then I should see `"Hello World"` + And Exit Code must be 0 + +Scenario: + If I try to run a cron job that does not exist, I should see an error message + + Given EE is present + When I run a cron job that does not exist + Then I should see an error message for cron job not found + And Exit Code must not be 0 diff --git a/features/update.feature b/features/update.feature new file mode 100644 index 0000000..4e0ea10 --- /dev/null +++ b/features/update.feature @@ -0,0 +1,17 @@ +Feature: Cron-Command -> Update + We have a command that we make use of in order to manage cron-jobs + run by easyengine using ofelia cron manager. This is a subcommand + used to update any cron jobs which are to be run by this manager. + + Scenario: + I can use the update command to update various aspects of a cron + job that is to be run by ofelia cron manager. + + Given EE is present + And I created site `test.site` with type `wp` + And I created site `test2.site` with type `wp` + And I created cron for site `test.site` with schedule `"* * * 1 *"` and command `"echo 'Hello World'"` + When I update that cron to site `test2.site` with schedule `"* 1 * * *"` and command `"echo 'It Works!'"` + Then The cron job listing should reflect the changes + And I should see `"Success: Cron update Successfully"` + And Exit Code must be 0