From f451e4dc47215a0356490b5aac2e7e3b3b270f65 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 26 Aug 2017 19:58:24 -0500 Subject: [PATCH] Begin unit tests for repositories --- .../DatabaseHostRepositoryInterface.php | 11 -- .../DuplicateDatabaseNameException.php | 31 ++++ .../Controllers/Admin/LocationController.php | 2 + .../Controllers/Admin/ServersController.php | 4 + app/Providers/RepositoryServiceProvider.php | 11 +- .../Eloquent/DatabaseHostRepository.php | 23 +-- .../Eloquent/DatabaseRepository.php | 10 +- .../Eloquent/LocationRepository.php | 1 + app/Services/Database/DatabaseHostService.php | 18 +- database/factories/ModelFactory.php | 22 +++ resources/lang/en/admin/exceptions.php | 3 + .../Eloquent/AllocationRepositoryTest.php | 87 +++++++++ .../Eloquent/ApiKeyRepositoryTest.php | 65 +++++++ .../Eloquent/ApiPermissionRepositoryTest.php | 65 +++++++ .../Eloquent/DatabaseHostRepositoryTest.php | 103 +++++++++++ .../Eloquent/DatabaseRepositoryTest.php | 172 ++++++++++++++++++ .../Eloquent/LocationRepositoryTest.php | 115 ++++++++++++ .../Database/DatabaseHostServiceTest.php | 34 +++- 18 files changed, 734 insertions(+), 43 deletions(-) create mode 100644 app/Exceptions/Repository/DuplicateDatabaseNameException.php create mode 100644 tests/Unit/Repositories/Eloquent/AllocationRepositoryTest.php create mode 100644 tests/Unit/Repositories/Eloquent/ApiKeyRepositoryTest.php create mode 100644 tests/Unit/Repositories/Eloquent/ApiPermissionRepositoryTest.php create mode 100644 tests/Unit/Repositories/Eloquent/DatabaseHostRepositoryTest.php create mode 100644 tests/Unit/Repositories/Eloquent/DatabaseRepositoryTest.php create mode 100644 tests/Unit/Repositories/Eloquent/LocationRepositoryTest.php diff --git a/app/Contracts/Repository/DatabaseHostRepositoryInterface.php b/app/Contracts/Repository/DatabaseHostRepositoryInterface.php index 59ff2405d..3a677fcbb 100644 --- a/app/Contracts/Repository/DatabaseHostRepositoryInterface.php +++ b/app/Contracts/Repository/DatabaseHostRepositoryInterface.php @@ -42,15 +42,4 @@ interface DatabaseHostRepositoryInterface extends RepositoryInterface * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function getWithServers($id); - - /** - * Delete a database host from the DB if there are no databases using it. - * - * @param int $id - * @return bool|null - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function deleteIfNoDatabases($id); } diff --git a/app/Exceptions/Repository/DuplicateDatabaseNameException.php b/app/Exceptions/Repository/DuplicateDatabaseNameException.php new file mode 100644 index 000000000..2308f407e --- /dev/null +++ b/app/Exceptions/Repository/DuplicateDatabaseNameException.php @@ -0,0 +1,31 @@ +. + * + * 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 Pterodactyl\Exceptions\Repository; + +use Pterodactyl\Exceptions\DisplayException; + +class DuplicateDatabaseNameException extends DisplayException +{ +} diff --git a/app/Http/Controllers/Admin/LocationController.php b/app/Http/Controllers/Admin/LocationController.php index f3e121961..c7a78baf0 100644 --- a/app/Http/Controllers/Admin/LocationController.php +++ b/app/Http/Controllers/Admin/LocationController.php @@ -83,6 +83,8 @@ class LocationController extends Controller * * @param int $id * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function view($id) { diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index efd74b2e5..c693bcb12 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -434,6 +434,7 @@ class ServersController extends Controller * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function toggleInstall(Server $server) { @@ -493,6 +494,7 @@ class ServersController extends Controller * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function manageSuspension(Request $request, Server $server) { @@ -533,6 +535,7 @@ class ServersController extends Controller * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function delete(Request $request, Server $server) { @@ -551,6 +554,7 @@ class ServersController extends Controller * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function saveStartup(Request $request, Server $server) { diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index b1be571ea..178c91025 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -25,7 +25,13 @@ namespace Pterodactyl\Providers; use Illuminate\Support\ServiceProvider; +use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface; use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Repositories\Daemon\CommandRepository; +use Pterodactyl\Repositories\Daemon\FileRepository; +use Pterodactyl\Repositories\Daemon\PowerRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\PackRepository; use Pterodactyl\Repositories\Eloquent\UserRepository; @@ -71,8 +77,8 @@ class RepositoryServiceProvider extends ServiceProvider $this->app->bind(AllocationRepositoryInterface::class, AllocationRepository::class); $this->app->bind(ApiKeyRepositoryInterface::class, ApiKeyRepository::class); $this->app->bind(ApiPermissionRepositoryInterface::class, ApiPermissionRepository::class); - $this->app->bind(DatabaseHostRepositoryInterface::class, DatabaseHostRepository::class); $this->app->bind(DatabaseRepositoryInterface::class, DatabaseRepository::class); + $this->app->bind(DatabaseHostRepositoryInterface::class, DatabaseHostRepository::class); $this->app->bind(LocationRepositoryInterface::class, LocationRepository::class); $this->app->bind(NodeRepositoryInterface::class, NodeRepository::class); $this->app->bind(OptionVariableRepositoryInterface::class, OptionVariableRepository::class); @@ -86,6 +92,9 @@ class RepositoryServiceProvider extends ServiceProvider // Daemon Repositories $this->app->bind(ConfigurationRepositoryInterface::class, ConfigurationRepository::class); + $this->app->bind(CommandRepositoryInterface::class, CommandRepository::class); $this->app->bind(DaemonServerRepositoryInterface::class, DaemonServerRepository::class); + $this->app->bind(FileRepositoryInterface::class, FileRepository::class); + $this->app->bind(PowerRepositoryInterface::class, PowerRepository::class); } } diff --git a/app/Repositories/Eloquent/DatabaseHostRepository.php b/app/Repositories/Eloquent/DatabaseHostRepository.php index 5aff26740..3304c3089 100644 --- a/app/Repositories/Eloquent/DatabaseHostRepository.php +++ b/app/Repositories/Eloquent/DatabaseHostRepository.php @@ -26,7 +26,6 @@ namespace Pterodactyl\Repositories\Eloquent; use Webmozart\Assert\Assert; use Pterodactyl\Models\DatabaseHost; -use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; @@ -48,6 +47,9 @@ class DatabaseHostRepository extends EloquentRepository implements DatabaseHostR return $this->getBuilder()->withCount('databases')->with('node')->get(); } + /** + * {@inheritdoc} + */ public function getWithServers($id) { Assert::numeric($id, 'First argument passed to getWithServers must be numeric, recieved %s.'); @@ -59,23 +61,4 @@ class DatabaseHostRepository extends EloquentRepository implements DatabaseHostR return $instance; } - - /** - * {@inheritdoc} - */ - public function deleteIfNoDatabases($id) - { - Assert::numeric($id, 'First argument passed to deleteIfNoDatabases must be numeric, recieved %s.'); - - $instance = $this->getBuilder()->withCount('databases')->find($id); - if (! $instance) { - throw new RecordNotFoundException(); - } - - if ($instance->databases_count > 0) { - throw new DisplayException('Cannot delete a database host that has active databases attached to it.'); - } - - return $instance->delete(); - } } diff --git a/app/Repositories/Eloquent/DatabaseRepository.php b/app/Repositories/Eloquent/DatabaseRepository.php index e274f1935..120006806 100644 --- a/app/Repositories/Eloquent/DatabaseRepository.php +++ b/app/Repositories/Eloquent/DatabaseRepository.php @@ -24,10 +24,10 @@ namespace Pterodactyl\Repositories\Eloquent; +use Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException; use Pterodactyl\Models\Database; use Illuminate\Foundation\Application; use Illuminate\Database\DatabaseManager; -use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; class DatabaseRepository extends EloquentRepository implements DatabaseRepositoryInterface @@ -67,13 +67,13 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor public function createIfNotExists(array $data) { $instance = $this->getBuilder()->where([ - ['server_id', $data['server_id']], - ['database_host_id', $data['database_host_id']], - ['database', $data['database']], + ['server_id', '=', array_get($data, 'server_id')], + ['database_host_id', '=', array_get($data, 'database_host_id')], + ['database', '=', array_get($data, 'database')], ])->count(); if ($instance > 0) { - throw new DisplayException('A database with those details already exists for the specified server.'); + throw new DuplicateDatabaseNameException('A database with those details already exists for the specified server.'); } return $this->create($data); diff --git a/app/Repositories/Eloquent/LocationRepository.php b/app/Repositories/Eloquent/LocationRepository.php index 70bece645..229e1e112 100644 --- a/app/Repositories/Eloquent/LocationRepository.php +++ b/app/Repositories/Eloquent/LocationRepository.php @@ -49,6 +49,7 @@ class LocationRepository extends EloquentRepository implements LocationRepositor /** * {@inheritdoc} + * @todo remove this, do logic in service */ public function deleteIfNoNodes($id) { diff --git a/app/Services/Database/DatabaseHostService.php b/app/Services/Database/DatabaseHostService.php index 654ce6127..2ad11eccf 100644 --- a/app/Services/Database/DatabaseHostService.php +++ b/app/Services/Database/DatabaseHostService.php @@ -26,6 +26,8 @@ namespace Pterodactyl\Services\Database; use Illuminate\Database\DatabaseManager; use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Extensions\DynamicDatabaseConnection; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; @@ -36,6 +38,11 @@ class DatabaseHostService */ protected $database; + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface + */ + protected $databaseRepository; + /** * @var \Pterodactyl\Extensions\DynamicDatabaseConnection */ @@ -55,17 +62,20 @@ class DatabaseHostService * DatabaseHostService constructor. * * @param \Illuminate\Database\DatabaseManager $database + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct( DatabaseManager $database, + DatabaseRepositoryInterface $databaseRepository, DatabaseHostRepositoryInterface $repository, DynamicDatabaseConnection $dynamic, Encrypter $encrypter ) { $this->database = $database; + $this->databaseRepository = $databaseRepository; $this->dynamic = $dynamic; $this->encrypter = $encrypter; $this->repository = $repository; @@ -111,6 +121,7 @@ class DatabaseHostService * @return mixed * * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function update($id, array $data) { @@ -142,6 +153,11 @@ class DatabaseHostService */ public function delete($id) { - return $this->repository->deleteIfNoDatabases($id); + $count = $this->databaseRepository->findCountWhere([['database_host_id', '=', $id]]); + if ($count > 0) { + throw new DisplayException(trans('admin/exceptions.databases.delete_has_databases')); + } + + return $this->repository->delete($id); } } diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index b8b2f123e..8726b2f4f 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -91,6 +91,7 @@ $factory->define(Pterodactyl\Models\Node::class, function (Faker\Generator $fake $factory->define(Pterodactyl\Models\Service::class, function (Faker\Generator $faker) { return [ + 'id' => $faker->unique()->randomNumber(), 'author' => $faker->unique()->uuid, 'name' => $faker->word, 'description' => null, @@ -155,3 +156,24 @@ $factory->define(Pterodactyl\Models\Subuser::class, function (Faker\Generator $f 'daemonSecret' => $faker->unique()->uuid, ]; }); + +$factory->define(Pterodactyl\Models\Allocation::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'node_id' => $faker->randomNumber(), + 'ip' => $faker->ipv4, + 'port' => $faker->randomNumber(5), + ]; +}); + +$factory->define(Pterodactyl\Models\DatabaseHost::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'name' => $faker->colorName, + 'host' => $faker->unique()->ipv4, + 'port' => 3306, + 'username' => $faker->colorName, + 'password' => Crypt::encrypt($faker->word), + 'node_id' => $faker->randomNumber(), + ]; +}); diff --git a/resources/lang/en/admin/exceptions.php b/resources/lang/en/admin/exceptions.php index 21bd812b2..97c537755 100644 --- a/resources/lang/en/admin/exceptions.php +++ b/resources/lang/en/admin/exceptions.php @@ -58,4 +58,7 @@ return [ 'user_is_owner' => 'You cannot add the server owner as a subuser for this server.', 'subuser_exists' => 'A user with that email address is already assigned as a subuser for this server.', ], + 'databases' => [ + 'delete_has_databases' => 'Cannot delete a database host server that has active databases linked to it.', + ], ]; diff --git a/tests/Unit/Repositories/Eloquent/AllocationRepositoryTest.php b/tests/Unit/Repositories/Eloquent/AllocationRepositoryTest.php new file mode 100644 index 000000000..0c7b6f4cf --- /dev/null +++ b/tests/Unit/Repositories/Eloquent/AllocationRepositoryTest.php @@ -0,0 +1,87 @@ +. + * + * 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\Repositories\Eloquent; + +use Illuminate\Database\Eloquent\Builder; +use Mockery as m; +use Pterodactyl\Models\Allocation; +use Pterodactyl\Repositories\Eloquent\AllocationRepository; +use Tests\TestCase; + +class AllocationRepositoryTest extends TestCase +{ + /** + * @var \Illuminate\Database\Eloquent\Builder + */ + protected $builder; + + /** + * @var \Pterodactyl\Repositories\Eloquent\AllocationRepository + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->builder = m::mock(Builder::class); + $this->repository = m::mock(AllocationRepository::class)->makePartial(); + + $this->repository->shouldReceive('getBuilder')->withNoArgs()->andReturn($this->builder); + } + + /** + * Test that we are returning the correct model. + */ + public function testCorrectModelIsAssigned() + { + $this->assertEquals(Allocation::class, $this->repository->model()); + } + + /** + * Test that allocations can be assigned to a server correctly. + */ + public function testAllocationsAreAssignedToAServer() + { + $this->builder->shouldReceive('whereIn')->with('id', [1, 2])->once()->andReturnSelf() + ->shouldReceive('update')->with(['server_id' => 10])->once()->andReturn(true); + + $this->assertTrue($this->repository->assignAllocationsToServer(10, [1, 2])); + } + + /** + * Test that allocations with a node relationship are returned. + */ + public function testAllocationsForANodeAreReturned() + { + $this->builder->shouldReceive('where')->with('node_id', 1)->once()->andReturnSelf() + ->shouldReceive('get')->once()->andReturn(factory(Allocation::class)->make()); + + $this->assertInstanceOf(Allocation::class, $this->repository->getAllocationsForNode(1)); + } +} diff --git a/tests/Unit/Repositories/Eloquent/ApiKeyRepositoryTest.php b/tests/Unit/Repositories/Eloquent/ApiKeyRepositoryTest.php new file mode 100644 index 000000000..eadfbde48 --- /dev/null +++ b/tests/Unit/Repositories/Eloquent/ApiKeyRepositoryTest.php @@ -0,0 +1,65 @@ +. + * + * 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\Repositories\Eloquent; + +use Illuminate\Database\Eloquent\Builder; +use Mockery as m; +use Pterodactyl\Models\APIKey; +use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; +use Tests\TestCase; + +class ApiKeyRepositoryTest extends TestCase +{ + /** + * @var \Illuminate\Database\Eloquent\Builder + */ + protected $builder; + + /** + * @var \Pterodactyl\Repositories\Eloquent\ApiKeyRepository + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->builder = m::mock(Builder::class); + $this->repository = m::mock(ApiKeyRepository::class)->makePartial(); + + $this->repository->shouldReceive('getBuilder')->withNoArgs()->andReturn($this->builder); + } + + /** + * Test that we are returning the correct model. + */ + public function testCorrectModelIsAssigned() + { + $this->assertEquals(APIKey::class, $this->repository->model()); + } +} diff --git a/tests/Unit/Repositories/Eloquent/ApiPermissionRepositoryTest.php b/tests/Unit/Repositories/Eloquent/ApiPermissionRepositoryTest.php new file mode 100644 index 000000000..945693c5c --- /dev/null +++ b/tests/Unit/Repositories/Eloquent/ApiPermissionRepositoryTest.php @@ -0,0 +1,65 @@ +. + * + * 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\Repositories\Eloquent; + +use Illuminate\Database\Eloquent\Builder; +use Mockery as m; +use Pterodactyl\Models\APIPermission; +use Pterodactyl\Repositories\Eloquent\ApiPermissionRepository; +use Tests\TestCase; + +class ApiPermissionRepositoryTest extends TestCase +{ + /** + * @var \Illuminate\Database\Eloquent\Builder + */ + protected $builder; + + /** + * @var \Pterodactyl\Repositories\Eloquent\ApiPermissionRepository + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->builder = m::mock(Builder::class); + $this->repository = m::mock(ApiPermissionRepository::class)->makePartial(); + + $this->repository->shouldReceive('getBuilder')->withNoArgs()->andReturn($this->builder); + } + + /** + * Test that we are returning the correct model. + */ + public function testCorrectModelIsAssigned() + { + $this->assertEquals(APIPermission::class, $this->repository->model()); + } +} diff --git a/tests/Unit/Repositories/Eloquent/DatabaseHostRepositoryTest.php b/tests/Unit/Repositories/Eloquent/DatabaseHostRepositoryTest.php new file mode 100644 index 000000000..389961b68 --- /dev/null +++ b/tests/Unit/Repositories/Eloquent/DatabaseHostRepositoryTest.php @@ -0,0 +1,103 @@ +. + * + * 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\Repositories\Eloquent; + +use Illuminate\Database\Eloquent\Builder; +use Mockery as m; +use Pterodactyl\Models\DatabaseHost; +use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; +use Tests\TestCase; + +class DatabaseHostRepositoryTest extends TestCase +{ + /** + * @var \Illuminate\Database\Eloquent\Builder + */ + protected $builder; + + /** + * @var \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->builder = m::mock(Builder::class); + $this->repository = m::mock(DatabaseHostRepository::class)->makePartial(); + + $this->repository->shouldReceive('getBuilder')->withNoArgs()->andReturn($this->builder); + } + + /** + * Test that we are returning the correct model. + */ + public function testCorrectModelIsAssigned() + { + $this->assertEquals(DatabaseHost::class, $this->repository->model()); + } + + /** + * Test query to reutrn all of the default view data. + */ + public function testHostWithDefaultViewDataIsReturned() + { + $this->builder->shouldReceive('withCount')->with('databases')->once()->andReturnSelf() + ->shouldReceive('with')->with('node')->once()->andReturnSelf() + ->shouldReceive('get')->withNoArgs()->once()->andReturnNull(); + + $this->assertNull($this->repository->getWithViewDetails()); + } + + /** + * Test query to return host and servers. + */ + public function testHostIsReturnedWithServers() + { + $model = factory(DatabaseHost::class)->make(); + + $this->builder->shouldReceive('with')->with('databases.server')->once()->andReturnSelf() + ->shouldReceive('find')->with(1, ['*'])->once()->andReturn($model); + + $this->assertEquals($model, $this->repository->getWithServers(1)); + } + + /** + * Test exception is found if no host is found when querying for servers. + * + * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function testExceptionIsThrownIfNoRecordIsFoundWithServers() + { + $this->builder->shouldReceive('with')->with('databases.server')->once()->andReturnSelf() + ->shouldReceive('find')->with(1, ['*'])->once()->andReturnNull(); + + $this->repository->getWithServers(1); + } +} diff --git a/tests/Unit/Repositories/Eloquent/DatabaseRepositoryTest.php b/tests/Unit/Repositories/Eloquent/DatabaseRepositoryTest.php new file mode 100644 index 000000000..e18802c33 --- /dev/null +++ b/tests/Unit/Repositories/Eloquent/DatabaseRepositoryTest.php @@ -0,0 +1,172 @@ +. + * + * 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\Repositories\Eloquent; + +use Illuminate\Database\Eloquent\Builder; +use Mockery as m; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException; +use Pterodactyl\Models\Database; +use Pterodactyl\Repositories\Eloquent\DatabaseRepository; +use Tests\TestCase; + +class DatabaseRepositoryTest extends TestCase +{ + /** + * @var \Illuminate\Database\Eloquent\Builder + */ + protected $builder; + + /** + * @var \Pterodactyl\Repositories\Eloquent\DatabaseRepository + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->builder = m::mock(Builder::class); + $this->repository = m::mock(DatabaseRepository::class)->makePartial()->shouldAllowMockingProtectedMethods(); + + $this->repository->shouldReceive('getBuilder')->withNoArgs()->andReturn($this->builder); + $this->repository->shouldNotReceive('runStatement'); + } + + /** + * Test that we are returning the correct model. + */ + public function testCorrectModelIsAssigned() + { + $this->assertEquals(Database::class, $this->repository->model()); + } + + /** + * Test that a database can be created if it does not already exist. + */ + public function testDatabaseIsCreatedIfNotExists() + { + $data = [ + 'server_id' => 1, + 'database_host_id' => 100, + 'database' => 'somename', + ]; + + $this->builder->shouldReceive('where')->with([ + ['server_id', '=', array_get($data, 'server_id')], + ['database_host_id', '=', array_get($data, 'database_host_id')], + ['database', '=', array_get($data, 'database')], + ])->once()->andReturnSelf() + ->shouldReceive('count')->withNoArgs()->once()->andReturn(0); + + $this->repository->shouldReceive('create')->with($data)->once()->andReturn(true); + + $this->assertTrue($this->repository->createIfNotExists($data)); + } + + /** + * Test that an exception is thrown if a database already exists with the given name. + */ + public function testExceptionIsThrownIfDatabaseAlreadyExists() + { + $this->builder->shouldReceive('where->count')->once()->andReturn(1); + $this->repository->shouldNotReceive('create'); + + try { + $this->repository->createIfNotExists([]); + } catch (DisplayException $exception) { + $this->assertInstanceOf(DuplicateDatabaseNameException::class, $exception); + $this->assertEquals('A database with those details already exists for the specified server.', $exception->getMessage()); + } + } + + /** + * Test SQL used to create a database. + */ + public function testCreateDatabaseStatement() + { + $query = sprintf('CREATE DATABASE IF NOT EXISTS `%s`', 'test_database'); + $this->repository->shouldReceive('runStatement')->with($query, 'test')->once()->andReturn(true); + + $this->assertTrue($this->repository->createDatabase('test_database', 'test')); + } + + /** + * Test SQL used to create a user. + */ + public function testCreateUserStatement() + { + $query = sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', 'test', '%', 'password'); + $this->repository->shouldReceive('runStatement')->with($query, 'test')->once()->andReturn(true); + + $this->assertTrue($this->repository->createUser('test', '%', 'password', 'test')); + } + + /** + * Test that a user is assigned the correct permissions on a database. + */ + public function testUserAssignmentToDatabaseStatement() + { + $query = sprintf('GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON `%s`.* TO `%s`@`%s`', 'test_database', 'test', '%'); + $this->repository->shouldReceive('runStatement')->with($query, 'test')->once()->andReturn(true); + + $this->assertTrue($this->repository->assignUserToDatabase('test_database', 'test', '%', 'test')); + } + + /** + * Test SQL for flushing privileges. + */ + public function testFlushStatement() + { + $this->repository->shouldReceive('runStatement')->with('FLUSH PRIVILEGES', 'test')->once()->andReturn(true); + + $this->assertTrue($this->repository->flush('test')); + } + + /** + * Test SQL to drop a database. + */ + public function testDropDatabaseStatement() + { + $query = sprintf('DROP DATABASE IF EXISTS `%s`', 'test_database'); + $this->repository->shouldReceive('runStatement')->with($query, 'test')->once()->andReturn(true); + + $this->assertTrue($this->repository->dropDatabase('test_database', 'test')); + } + + /** + * Test SQL to drop a user. + */ + public function testDropUserStatement() + { + $query = sprintf('DROP USER IF EXISTS `%s`@`%s`', 'test', '%'); + $this->repository->shouldReceive('runStatement')->with($query, 'test')->once()->andReturn(true); + + $this->assertTrue($this->repository->dropUser('test', '%', 'test')); + } +} diff --git a/tests/Unit/Repositories/Eloquent/LocationRepositoryTest.php b/tests/Unit/Repositories/Eloquent/LocationRepositoryTest.php new file mode 100644 index 000000000..fbc224fee --- /dev/null +++ b/tests/Unit/Repositories/Eloquent/LocationRepositoryTest.php @@ -0,0 +1,115 @@ +. + * + * 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\Repositories\Eloquent; + +use Illuminate\Database\Eloquent\Builder; +use Mockery as m; +use Pterodactyl\Models\Location; +use Pterodactyl\Repositories\Eloquent\LocationRepository; +use Tests\TestCase; + +class LocationRepositoryTest extends TestCase +{ + /** + * @var \Illuminate\Database\Eloquent\Builder + */ + protected $builder; + + /** + * @var \Pterodactyl\Repositories\Eloquent\LocationRepository + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->builder = m::mock(Builder::class); + $this->repository = m::mock(LocationRepository::class)->makePartial(); + + $this->repository->shouldReceive('getBuilder')->withNoArgs()->andReturn($this->builder); + } + + /** + * Test that we are returning the correct model. + */ + public function testCorrectModelIsAssigned() + { + $this->assertEquals(Location::class, $this->repository->model()); + } + + /** + * Test that all locations with associated node and server counts are returned. + */ + public function testAllLocationsWithDetailsAreReturned() + { + $this->builder->shouldReceive('withCount')->with('nodes', 'servers')->once()->andReturnSelf() + ->shouldReceive('get')->with(['*'])->once()->andReturnNull(); + + $this->assertNull($this->repository->getAllWithDetails()); + } + + /** + * Test that all locations with associated node are returned. + */ + public function testAllLocationsWithNodes() + { + $this->builder->shouldReceive('with')->with('nodes')->once()->andReturnSelf() + ->shouldReceive('get')->with(['*'])->once()->andReturnNull(); + + $this->assertNull($this->repository->getAllWithNodes()); + } + + /** + * Test that a single location with associated node is returned. + */ + public function testLocationWithNodeIsReturned() + { + $model = factory(Location::class)->make(); + + $this->builder->shouldReceive('with')->with('nodes.servers')->once()->andReturnSelf() + ->shouldReceive('find')->with(1, ['*'])->once()->andReturn($model); + + $response = $this->repository->getWithNodes(1); + $this->assertInstanceOf(Location::class, $response); + $this->assertEquals($model, $response); + } + + /** + * Test that an exception is thrown when getting location with nodes if no location is found. + * + * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function testExceptionIsThrownIfNoLocationIsFoundWithNodes() + { + $this->builder->shouldReceive('with')->with('nodes.servers')->once()->andReturnSelf() + ->shouldReceive('find')->with(1, ['*'])->once()->andReturnNull(); + + $this->repository->getWithNodes(1); + } +} diff --git a/tests/Unit/Services/Database/DatabaseHostServiceTest.php b/tests/Unit/Services/Database/DatabaseHostServiceTest.php index 84df7f028..464d80ea9 100644 --- a/tests/Unit/Services/Database/DatabaseHostServiceTest.php +++ b/tests/Unit/Services/Database/DatabaseHostServiceTest.php @@ -25,6 +25,8 @@ namespace Tests\Unit\Services\Administrative; use Mockery as m; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Exceptions\DisplayException; use Tests\TestCase; use Illuminate\Database\DatabaseManager; use Illuminate\Contracts\Encryption\Encrypter; @@ -39,6 +41,11 @@ class DatabaseHostServiceTest extends TestCase */ protected $database; + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface + */ + protected $databaseRepository; + /** * @var \Pterodactyl\Extensions\DynamicDatabaseConnection */ @@ -67,12 +74,14 @@ class DatabaseHostServiceTest extends TestCase parent::setUp(); $this->database = m::mock(DatabaseManager::class); + $this->databaseRepository = m::mock(DatabaseRepositoryInterface::class); $this->dynamic = m::mock(DynamicDatabaseConnection::class); $this->encrypter = m::mock(Encrypter::class); $this->repository = m::mock(DatabaseHostRepositoryInterface::class); $this->service = new DatabaseHostService( $this->database, + $this->databaseRepository, $this->repository, $this->dynamic, $this->encrypter @@ -82,7 +91,7 @@ class DatabaseHostServiceTest extends TestCase /** * Test that creating a host returns the correct data. */ - public function test_create_host_function() + public function testHostIsCreated() { $data = [ 'password' => 'raw-password', @@ -130,7 +139,7 @@ class DatabaseHostServiceTest extends TestCase /** * Test that passing a password will store an encrypted version in the DB. */ - public function test_update_with_password() + public function testHostIsUpdatedWithPasswordProvided() { $finalData = (object) ['password' => 'enc-pass', 'host' => '123.456.78.9']; @@ -158,7 +167,7 @@ class DatabaseHostServiceTest extends TestCase /** * Test that passing no or empty password will skip storing it. */ - public function test_update_without_password() + public function testHostIsUpdatedWithoutPassword() { $finalData = (object) ['host' => '123.456.78.9']; @@ -182,12 +191,27 @@ class DatabaseHostServiceTest extends TestCase /** * Test that a database host can be deleted. */ - public function test_delete_function() + public function testHostIsDeleted() { - $this->repository->shouldReceive('deleteIfNoDatabases')->with(1)->once()->andReturn(true); + $this->databaseRepository->shouldReceive('findCountWhere')->with([['database_host_id', '=', 1]])->once()->andReturn(0); + $this->repository->shouldReceive('delete')->with(1)->once()->andReturn(true); $response = $this->service->delete(1); $this->assertTrue($response, 'Assert that response is true.'); } + + /** + * Test exception is thrown when there are databases attached to a host. + */ + public function testExceptionIsThrownIfHostHasDatabases() + { + $this->databaseRepository->shouldReceive('findCountWhere')->with([['database_host_id', '=', 1]])->once()->andReturn(2); + + try { + $this->service->delete(1); + } catch (DisplayException $exception) { + $this->assertEquals(trans('admin/exceptions.databases.delete_has_databases'), $exception->getMessage()); + } + } }