diff --git a/app/Services/Databases/DeployServerDatabaseService.php b/app/Services/Databases/DeployServerDatabaseService.php index 2932b87f2..c8b5ed179 100644 --- a/app/Services/Databases/DeployServerDatabaseService.php +++ b/app/Services/Databases/DeployServerDatabaseService.php @@ -21,6 +21,7 @@ class DeployServerDatabaseService * @var \Pterodactyl\Services\Databases\DatabaseManagementService */ private $managementService; + /** * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface */ @@ -63,21 +64,23 @@ class DeployServerDatabaseService } $allowRandom = config('pterodactyl.client_features.databases.allow_random'); - $host = $this->databaseHostRepository->setColumns(['id'])->findWhere([ + $hosts = $this->databaseHostRepository->setColumns(['id'])->findWhere([ ['node_id', '=', $server->node_id], - ])->random(); + ]); - if (empty($host) && ! $allowRandom) { + if ($hosts->isEmpty() && ! $allowRandom) { throw new NoSuitableDatabaseHostException; } - if (empty($host)) { - $host = $this->databaseHostRepository->setColumns(['id'])->all()->random(); - if (empty($host)) { + if ($hosts->isEmpty()) { + $hosts = $this->databaseHostRepository->setColumns(['id'])->all(); + if ($hosts->isEmpty()) { throw new NoSuitableDatabaseHostException; } } + $host = $hosts->random(); + return $this->managementService->create($server->id, [ 'database_host_id' => $host->id, 'database' => array_get($data, 'database'), diff --git a/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php b/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php index d44481c1b..b76cbfb86 100644 --- a/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php +++ b/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php @@ -1,17 +1,10 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Tests\Unit\Http\Controllers\Server\Files; use Mockery as m; -use phpmock\phpunit\PHPMock; use Pterodactyl\Models\Node; +use Tests\Traits\MocksUuids; use Pterodactyl\Models\Server; use Illuminate\Cache\Repository; use Tests\Unit\Http\Controllers\ControllerTestCase; @@ -19,7 +12,7 @@ use Pterodactyl\Http\Controllers\Server\Files\DownloadController; class DownloadControllerTest extends ControllerTestCase { - use PHPMock; + use MocksUuids; /** * @var \Illuminate\Cache\Repository|\Mockery\Mock @@ -48,16 +41,20 @@ class DownloadControllerTest extends ControllerTestCase $this->setRequestAttribute('server', $server); $controller->shouldReceive('authorize')->with('download-files', $server)->once()->andReturnNull(); - $this->getFunctionMock('\\Pterodactyl\\Http\\Controllers\\Server\\Files', 'str_random') - ->expects($this->once())->willReturn('randomString'); - $this->cache->shouldReceive('tags')->with(['Server:Downloads'])->once()->andReturnSelf(); - $this->cache->shouldReceive('put')->with('randomString', ['server' => $server->uuid, 'path' => '/my/file.txt'], 5)->once()->andReturnNull(); + $this->cache->shouldReceive('put') + ->once() + ->with('Server:Downloads:' . $this->getKnownUuid(), ['server' => $server->uuid, 'path' => '/my/file.txt'], 5) + ->andReturnNull(); $response = $controller->index($this->request, $server->uuidShort, '/my/file.txt'); $this->assertIsRedirectResponse($response); $this->assertRedirectUrlEquals(sprintf( - '%s://%s:%s/v1/server/file/download/%s', $server->node->scheme, $server->node->fqdn, $server->node->daemonListen, 'randomString' + '%s://%s:%s/v1/server/file/download/%s', + $server->node->scheme, + $server->node->fqdn, + $server->node->daemonListen, + $this->getKnownUuid() ), $response); } diff --git a/tests/Unit/Services/Databases/DeployServerDatabaseServiceTest.php b/tests/Unit/Services/Databases/DeployServerDatabaseServiceTest.php new file mode 100644 index 000000000..f8f2ff70c --- /dev/null +++ b/tests/Unit/Services/Databases/DeployServerDatabaseServiceTest.php @@ -0,0 +1,236 @@ +databaseHostRepository = m::mock(DatabaseHostRepositoryInterface::class); + $this->managementService = m::mock(DatabaseManagementService::class); + $this->repository = m::mock(DatabaseRepositoryInterface::class); + + // Set configs for testing instances. + config()->set('pterodactyl.client_features.databases.enabled', true); + config()->set('pterodactyl.client_features.databases.allow_random', true); + } + + /** + * Test handling of non-random hosts when a host is found. + * + * @dataProvider databaseLimitDataProvider + */ + public function testNonRandomFoundHost($limit, $count) + { + config()->set('pterodactyl.client_features.databases.allow_random', false); + + $server = factory(Server::class)->make(['database_limit' => $limit]); + $model = factory(Database::class)->make(); + + $this->repository->shouldReceive('findCountWhere') + ->once() + ->with([['server_id', '=', $server->id]]) + ->andReturn($count); + + $this->databaseHostRepository->shouldReceive('setColumns->findWhere') + ->once() + ->with([['node_id', '=', $server->node_id]]) + ->andReturn(collect([$model])); + + $this->managementService->shouldReceive('create') + ->once() + ->with($server->id, [ + 'database_host_id' => $model->id, + 'database' => 'testdb', + 'remote' => null, + ]) + ->andReturn($model); + + $response = $this->getService()->handle($server, ['database' => 'testdb']); + + $this->assertInstanceOf(Database::class, $response); + $this->assertSame($model, $response); + } + + /** + * Test that an exception is thrown if in non-random mode and no host is found. + * + * @expectedException \Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException + */ + public function testNonRandomNoHost() + { + config()->set('pterodactyl.client_features.databases.allow_random', false); + + $server = factory(Server::class)->make(['database_limit' => 1]); + + $this->repository->shouldReceive('findCountWhere') + ->once() + ->with([['server_id', '=', $server->id]]) + ->andReturn(0); + + $this->databaseHostRepository->shouldReceive('setColumns->findWhere') + ->once() + ->with([['node_id', '=', $server->node_id]]) + ->andReturn(collect()); + + $this->getService()->handle($server, []); + } + + /** + * Test handling of random host selection. + */ + public function testRandomFoundHost() + { + $server = factory(Server::class)->make(['database_limit' => 1]); + $model = factory(Database::class)->make(); + + $this->repository->shouldReceive('findCountWhere') + ->once() + ->with([['server_id', '=', $server->id]]) + ->andReturn(0); + + $this->databaseHostRepository->shouldReceive('setColumns->findWhere') + ->once() + ->with([['node_id', '=', $server->node_id]]) + ->andReturn(collect()); + + $this->databaseHostRepository->shouldReceive('setColumns->all') + ->once() + ->andReturn(collect([$model])); + + $this->managementService->shouldReceive('create') + ->once() + ->with($server->id, [ + 'database_host_id' => $model->id, + 'database' => 'testdb', + 'remote' => null, + ]) + ->andReturn($model); + + $response = $this->getService()->handle($server, ['database' => 'testdb']); + + $this->assertInstanceOf(Database::class, $response); + $this->assertSame($model, $response); + } + + /** + * Test that an exception is thrown when no host is found and random is allowed. + * + * @expectedException \Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException + */ + public function testRandomNoHost() + { + $server = factory(Server::class)->make(['database_limit' => 1]); + + $this->repository->shouldReceive('findCountWhere') + ->once() + ->with([['server_id', '=', $server->id]]) + ->andReturn(0); + + $this->databaseHostRepository->shouldReceive('setColumns->findWhere') + ->once() + ->with([['node_id', '=', $server->node_id]]) + ->andReturn(collect()); + + $this->databaseHostRepository->shouldReceive('setColumns->all') + ->once() + ->andReturn(collect()); + + $this->getService()->handle($server, []); + } + + /** + * Test that a server over the database limit throws an exception. + * + * @dataProvider databaseExceedingLimitDataProvider + * @expectedException \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException + */ + public function testServerOverDatabaseLimit($limit, $count) + { + $server = factory(Server::class)->make(['database_limit' => $limit]); + + $this->repository->shouldReceive('findCountWhere') + ->once() + ->with([['server_id', '=', $server->id]]) + ->andReturn($count); + + $this->getService()->handle($server, []); + } + + /** + * Test that an exception is thrown if the feature is not enabled. + * + * @expectedException \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException + */ + public function testFeatureNotEnabled() + { + config()->set('pterodactyl.client_features.databases.enabled', false); + + $this->getService()->handle(factory(Server::class)->make(), []); + } + + /** + * Provide limits and current database counts for testing. + * + * @return array + */ + public function databaseLimitDataProvider(): array + { + return [ + [null, 10], + [1, 0], + ]; + } + + /** + * Provide data for servers over their database limit. + * + * @return array + */ + public function databaseExceedingLimitDataProvider(): array + { + return [ + [2, 2], + [2, 3], + ]; + } + + /** + * Return an instance of the service with mocked dependencies for testing. + * + * @return \Pterodactyl\Services\Databases\DeployServerDatabaseService + */ + private function getService(): DeployServerDatabaseService + { + return new DeployServerDatabaseService($this->repository, $this->databaseHostRepository, $this->managementService); + } +}