From 364adb1f840d24430ca21e9a32546911cfff4d6d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 12 Aug 2017 16:30:27 -0500 Subject: [PATCH] Add tests for service option services --- .../Options/InstallScriptUpdateService.php | 15 +- database/factories/ModelFactory.php | 10 ++ .../InstallScriptUpdateServiceTest.php | 128 ++++++++++++++++++ .../Options/OptionCreationServiceTest.php | 122 +++++++++++++++++ .../Options/OptionDeletionServiceTest.php | 86 ++++++++++++ .../Options/OptionUpdateServiceTest.php | 121 +++++++++++++++++ 6 files changed, 475 insertions(+), 7 deletions(-) create mode 100644 tests/Unit/Services/Services/Options/InstallScriptUpdateServiceTest.php create mode 100644 tests/Unit/Services/Services/Options/OptionCreationServiceTest.php create mode 100644 tests/Unit/Services/Services/Options/OptionDeletionServiceTest.php create mode 100644 tests/Unit/Services/Services/Options/OptionUpdateServiceTest.php diff --git a/app/Services/Services/Options/InstallScriptUpdateService.php b/app/Services/Services/Options/InstallScriptUpdateService.php index f2f135591..1a63c8003 100644 --- a/app/Services/Services/Options/InstallScriptUpdateService.php +++ b/app/Services/Services/Options/InstallScriptUpdateService.php @@ -33,16 +33,16 @@ class InstallScriptUpdateService /** * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface */ - protected $serviceOptionRepository; + protected $repository; /** * InstallScriptUpdateService constructor. * - * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $serviceOptionRepository + * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository */ - public function __construct(ServiceOptionRepositoryInterface $serviceOptionRepository) + public function __construct(ServiceOptionRepositoryInterface $repository) { - $this->serviceOptionRepository = $serviceOptionRepository; + $this->repository = $repository; } /** @@ -52,21 +52,22 @@ class InstallScriptUpdateService * @param array $data * * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Services\ServiceOption\InvalidCopyFromException */ public function handle($option, array $data) { if (! $option instanceof ServiceOption) { - $option = $this->serviceOptionRepository->find($option); + $option = $this->repository->find($option); } if (! is_null(array_get($data, 'copy_script_from'))) { - if (! $this->serviceOptionRepository->isCopiableScript(array_get($data, 'copy_script_from'), $option->service_id)) { + if (! $this->repository->isCopiableScript(array_get($data, 'copy_script_from'), $option->service_id)) { throw new InvalidCopyFromException(trans('admin/exceptions.service.options.invalid_copy_id')); } } - $this->serviceOptionRepository->withoutFresh()->update($option->id, [ + $this->repository->withoutFresh()->update($option->id, [ 'script_install' => array_get($data, 'script_install'), 'script_is_privileged' => array_get($data, 'script_is_privileged'), 'script_entry' => array_get($data, 'script_entry'), diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index c16652f02..b7e84eafe 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -88,6 +88,16 @@ $factory->define(Pterodactyl\Models\Node::class, function (Faker\Generator $fake ]; }); +$factory->define(Pterodactyl\Models\ServiceOption::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'service_id' => $faker->unique()->randomNumber(), + 'name' => $faker->name, + 'description' => $faker->sentences(3), + 'tag' => $faker->unique()->randomNumber(5), + ]; +}); + $factory->define(Pterodactyl\Models\ServiceVariable::class, function (Faker\Generator $faker) { return [ 'id' => $faker->unique()->randomNumber(), diff --git a/tests/Unit/Services/Services/Options/InstallScriptUpdateServiceTest.php b/tests/Unit/Services/Services/Options/InstallScriptUpdateServiceTest.php new file mode 100644 index 000000000..ea0b881c3 --- /dev/null +++ b/tests/Unit/Services/Services/Options/InstallScriptUpdateServiceTest.php @@ -0,0 +1,128 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Tests\Unit\Services\Services\Options; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Services\Services\Options\InstallScriptUpdateService; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; +use Pterodactyl\Exceptions\Services\ServiceOption\InvalidCopyFromException; + +class InstallScriptUpdateServiceTest extends TestCase +{ + /** + * @var array + */ + protected $data = [ + 'script_install' => 'test-script', + 'script_is_privileged' => true, + 'script_entry' => '/bin/bash', + 'script_container' => 'ubuntu', + 'copy_script_from' => null, + ]; + + /** + * @var \Pterodactyl\Models\ServiceOption + */ + protected $model; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Services\Options\InstallScriptUpdateService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->model = factory(ServiceOption::class)->make(); + $this->repository = m::mock(ServiceOptionRepositoryInterface::class); + + $this->service = new InstallScriptUpdateService($this->repository); + } + + /** + * Test that passing a new copy_script_from attribute works properly. + */ + public function testUpdateWithValidCopyScriptFromAttribute() + { + $this->data['copy_script_from'] = 1; + + $this->repository->shouldReceive('isCopiableScript')->with(1, $this->model->service_id)->once()->andReturn(true); + $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, $this->data)->andReturnNull(); + + $this->service->handle($this->model, $this->data); + } + + /** + * Test that an exception gets raised when the script is not copiable. + */ + public function testUpdateWithInvalidCopyScriptFromAttribute() + { + $this->data['copy_script_from'] = 1; + + $this->repository->shouldReceive('isCopiableScript')->with(1, $this->model->service_id)->once()->andReturn(false); + try { + $this->service->handle($this->model, $this->data); + } catch (Exception $exception) { + $this->assertInstanceOf(InvalidCopyFromException::class, $exception); + $this->assertEquals(trans('admin/exceptions.service.options.invalid_copy_id'), $exception->getMessage()); + } + } + + /** + * Test standard functionality. + */ + public function testUpdateWithoutNewCopyScriptFromAttribute() + { + $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, $this->data)->andReturnNull(); + + $this->service->handle($this->model, $this->data); + } + + /** + * Test that an integer can be passed in place of a model. + */ + public function testFunctionAcceptsIntegerInPlaceOfModel() + { + $this->repository->shouldReceive('find')->with($this->model->id)->once()->andReturn($this->model); + $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, $this->data)->andReturnNull(); + + $this->service->handle($this->model->id, $this->data); + } +} diff --git a/tests/Unit/Services/Services/Options/OptionCreationServiceTest.php b/tests/Unit/Services/Services/Options/OptionCreationServiceTest.php new file mode 100644 index 000000000..950168644 --- /dev/null +++ b/tests/Unit/Services/Services/Options/OptionCreationServiceTest.php @@ -0,0 +1,122 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Tests\Unit\Services\Services\Options; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Services\Services\Options\OptionCreationService; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; +use Pterodactyl\Exceptions\Services\ServiceOption\NoParentConfigurationFoundException; + +class OptionCreationServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Models\ServiceOption + */ + protected $model; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Services\Options\OptionCreationService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->model = factory(ServiceOption::class)->make(); + $this->repository = m::mock(ServiceOptionRepositoryInterface::class); + + $this->service = new OptionCreationService($this->repository); + } + + /** + * Test that a new model is created when not using the config from attribute. + */ + public function testCreateNewModelWithoutUsingConfigFrom() + { + $this->repository->shouldReceive('create')->with(['name' => $this->model->name, 'config_from' => null]) + ->once()->andReturn($this->model); + + $response = $this->service->handle(['name' => $this->model->name]); + + $this->assertNotEmpty($response); + $this->assertNull(object_get($response, 'config_from')); + $this->assertEquals($this->model->name, $response->name); + } + + /** + * Test that a new model is created when using the config from attribute. + */ + public function testCreateNewModelUsingConfigFrom() + { + $data = [ + 'name' => $this->model->name, + 'service_id' => $this->model->service_id, + 'config_from' => 1, + ]; + + $this->repository->shouldReceive('findCountWhere')->with([ + ['service_id', '=', $data['service_id']], + ['id', '=', $data['config_from']] + ])->once()->andReturn(1); + + $this->repository->shouldReceive('create')->with($data) + ->once()->andReturn($this->model); + + $response = $this->service->handle($data); + + $this->assertNotEmpty($response); + $this->assertEquals($response, $this->model); + } + + /** + * Test that an exception is thrown if no parent configuration can be located. + */ + public function testExceptionIsThrownIfNoParentConfigurationIsFound() + { + $this->repository->shouldReceive('findCountWhere')->with([ + ['service_id', '=', null], + ['id', '=', 1] + ])->once()->andReturn(0); + + try { + $this->service->handle(['config_from' => 1]); + } catch (Exception $exception) { + $this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception); + $this->assertEquals(trans('admin/exceptions.service.options.must_be_child'), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/Services/Options/OptionDeletionServiceTest.php b/tests/Unit/Services/Services/Options/OptionDeletionServiceTest.php new file mode 100644 index 000000000..725f6d0cb --- /dev/null +++ b/tests/Unit/Services/Services/Options/OptionDeletionServiceTest.php @@ -0,0 +1,86 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Tests\Unit\Services\Services\Options; + +use Mockery as m; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; +use Pterodactyl\Exceptions\Services\ServiceOption\HasActiveServersException; +use Pterodactyl\Services\Services\Options\OptionDeletionService; +use Tests\TestCase; + +class OptionDeletionServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Services\Services\Options\OptionDeletionService + */ + protected $service; + + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(ServiceOptionRepositoryInterface::class); + $this->serverRepository = m::mock(ServerRepositoryInterface::class); + + $this->service = new OptionDeletionService($this->serverRepository, $this->repository); + } + + /** + * Test that option is deleted if no servers are found. + */ + public function testOptionIsDeletedIfNoServersAreFound() + { + $this->serverRepository->shouldReceive('findCountWhere')->with([['option_id', '=', 1]])->once()->andReturn(0); + $this->repository->shouldReceive('delete')->with(1)->once()->andReturn(1); + + $this->assertEquals(1, $this->service->handle(1)); + } + + /** + * Test that option is not deleted if servers are found. + */ + public function testExceptionIsThrownIfServersAreFound() + { + $this->serverRepository->shouldReceive('findCountWhere')->with([['option_id', '=', 1]])->once()->andReturn(1); + + try { + $this->service->handle(1); + } catch (\Exception $exception) { + $this->assertInstanceOf(HasActiveServersException::class, $exception); + $this->assertEquals(trans('admin/exceptions.service.options.delete_has_servers'), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/Services/Options/OptionUpdateServiceTest.php b/tests/Unit/Services/Services/Options/OptionUpdateServiceTest.php new file mode 100644 index 000000000..20d23761c --- /dev/null +++ b/tests/Unit/Services/Services/Options/OptionUpdateServiceTest.php @@ -0,0 +1,121 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Tests\Unit\Services\Services\Options; + +use Exception; +use Mockery as m; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; +use Pterodactyl\Exceptions\Services\ServiceOption\NoParentConfigurationFoundException; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Services\Services\Options\OptionUpdateService; +use Tests\TestCase; + +class OptionUpdateServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Models\ServiceOption + */ + protected $model; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Services\Options\OptionUpdateService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->model = factory(ServiceOption::class)->make(); + $this->repository = m::mock(ServiceOptionRepositoryInterface::class); + + $this->service = new OptionUpdateService($this->repository); + } + + /** + * Test that an option is updated when no config_from attribute is passed. + */ + public function testOptionIsUpdatedWhenNoConfigFromIsProvided() + { + $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, ['test_field' => 'field_value'])->once()->andReturnNull(); + + $this->service->handle($this->model, ['test_field' => 'field_value']); + } + + /** + * Test that option is updated when a valid config_from attribute is passed. + */ + public function testOptionIsUpdatedWhenValidConfigFromIsPassed() + { + $this->repository->shouldReceive('findCountWhere')->with([ + ['service_id', '=', $this->model->service_id], + ['id', '=', 1], + ])->once()->andReturn(1); + + $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, ['config_from' => 1])->once()->andReturnNull(); + + $this->service->handle($this->model, ['config_from' => 1]); + } + + /** + * Test that an exception is thrown if an invalid config_from attribute is passed. + */ + public function testExceptionIsThrownIfInvalidParentConfigIsPassed() + { + $this->repository->shouldReceive('findCountWhere')->with([ + ['service_id', '=', $this->model->service_id], + ['id', '=', 1], + ])->once()->andReturn(0); + + try { + $this->service->handle($this->model, ['config_from' => 1]); + } catch (Exception $exception) { + $this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception); + $this->assertEquals(trans('admin/exceptions.service.options.must_be_child'), $exception->getMessage()); + } + } + + /** + * Test that an integer linking to a model can be passed in place of the ServiceOption model. + */ + public function testIntegerCanBePassedInPlaceOfModel() + { + $this->repository->shouldReceive('find')->with($this->model->id)->once()->andReturn($this->model); + $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, ['test_field' => 'field_value'])->once()->andReturnNull(); + + $this->service->handle($this->model->id, ['test_field' => 'field_value']); + } +}