diff --git a/app/Services/Databases/DatabaseManagementService.php b/app/Services/Databases/DatabaseManagementService.php index 845ee6282..95182a288 100644 --- a/app/Services/Databases/DatabaseManagementService.php +++ b/app/Services/Databases/DatabaseManagementService.php @@ -9,6 +9,7 @@ namespace Pterodactyl\Services\Databases; +use Pterodactyl\Models\Database; use Illuminate\Database\DatabaseManager; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Extensions\DynamicDatabaseConnection; @@ -95,7 +96,7 @@ class DatabaseManagementService $this->database->commit(); } catch (\Exception $ex) { try { - if (isset($database)) { + if (isset($database) && $database instanceof Database) { $this->repository->dropDatabase($database->database); $this->repository->dropUser($database->username, $database->remote); $this->repository->flush(); diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index f3e4f4093..82d7ad8b0 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -39,6 +39,8 @@ $factory->define(Pterodactyl\Models\Server::class, function (Faker\Generator $fa }); $factory->define(Pterodactyl\Models\User::class, function (Faker\Generator $faker) { + static $password; + return [ 'id' => $faker->unique()->randomNumber(), 'external_id' => null, @@ -47,7 +49,7 @@ $factory->define(Pterodactyl\Models\User::class, function (Faker\Generator $fake 'email' => $faker->safeEmail, 'name_first' => $faker->firstName, 'name_last' => $faker->lastName, - 'password' => bcrypt('password'), + 'password' => $password ?: $password = bcrypt('password'), 'language' => 'en', 'root_admin' => false, 'use_totp' => false, @@ -173,6 +175,21 @@ $factory->define(Pterodactyl\Models\DatabaseHost::class, function (Faker\Generat ]; }); +$factory->define(Pterodactyl\Models\Database::class, function (Faker\Generator $faker) { + static $password; + + return [ + 'id' => $faker->unique()->randomNumber(), + 'server_id' => $faker->randomNumber(), + 'database_host_id' => $faker->randomNumber(), + 'database' => str_random(10), + 'username' => str_random(10), + 'password' => $password ?: bcrypt('test123'), + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString(), + ]; +}); + $factory->define(Pterodactyl\Models\Schedule::class, function (Faker\Generator $faker) { return [ 'id' => $faker->unique()->randomNumber(), diff --git a/tests/Unit/Http/Controllers/Admin/DatabaseControllerTest.php b/tests/Unit/Http/Controllers/Admin/DatabaseControllerTest.php index ac723f6e9..62b66d0bb 100644 --- a/tests/Unit/Http/Controllers/Admin/DatabaseControllerTest.php +++ b/tests/Unit/Http/Controllers/Admin/DatabaseControllerTest.php @@ -14,6 +14,9 @@ use Tests\TestCase; use Prologue\Alerts\AlertsMessageBag; use Tests\Assertions\ControllerAssertionsTrait; use Pterodactyl\Http\Controllers\Admin\DatabaseController; +use Pterodactyl\Services\Databases\Hosts\HostUpdateService; +use Pterodactyl\Services\Databases\Hosts\HostCreationService; +use Pterodactyl\Services\Databases\Hosts\HostDeletionService; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; @@ -22,29 +25,34 @@ class DatabaseControllerTest extends TestCase use ControllerAssertionsTrait; /** - * @var \Prologue\Alerts\AlertsMessageBag + * @var \Prologue\Alerts\AlertsMessageBag|\Mockery\Mock */ - protected $alert; + private $alert; /** - * @var \Pterodactyl\Http\Controllers\Admin\DatabaseController + * @var \Pterodactyl\Services\Databases\Hosts\HostCreationService|\Mockery\Mock */ - protected $controller; + private $creationService; /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + * @var \Pterodactyl\Services\Databases\Hosts\HostDeletionService|\Mockery\Mock */ - protected $locationRepository; + private $deletionService; /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface|\Mockery\Mock */ - protected $repository; + private $locationRepository; /** - * @var \Pterodactyl\Services\Databases\HostsUpdateService + * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface|\Mockery\Mock */ - protected $service; + private $repository; + + /** + * @var \Pterodactyl\Services\Databases\Hosts\HostUpdateService|\Mockery\Mock + */ + private $updateService; /** * Setup tests. @@ -54,16 +62,11 @@ class DatabaseControllerTest extends TestCase parent::setUp(); $this->alert = m::mock(AlertsMessageBag::class); + $this->creationService = m::mock(HostCreationService::class); + $this->deletionService = m::mock(HostDeletionService::class); $this->locationRepository = m::mock(LocationRepositoryInterface::class); $this->repository = m::mock(DatabaseHostRepositoryInterface::class); - $this->service = m::mock(HostUpdateService::class); - - $this->controller = new DatabaseController( - $this->alert, - $this->repository, - $this->service, - $this->locationRepository - ); + $this->updateService = m::mock(HostUpdateService::class); } /** @@ -74,7 +77,7 @@ class DatabaseControllerTest extends TestCase $this->locationRepository->shouldReceive('getAllWithNodes')->withNoArgs()->once()->andReturn('getAllWithNodes'); $this->repository->shouldReceive('getWithViewDetails')->withNoArgs()->once()->andReturn('getWithViewDetails'); - $response = $this->controller->index(); + $response = $this->getController()->index(); $this->assertIsViewResponse($response); $this->assertViewNameEquals('admin.databases.index', $response); @@ -92,7 +95,7 @@ class DatabaseControllerTest extends TestCase $this->locationRepository->shouldReceive('getAllWithNodes')->withNoArgs()->once()->andReturn('getAllWithNodes'); $this->repository->shouldReceive('getWithServers')->with(1)->once()->andReturn('getWithServers'); - $response = $this->controller->view(1); + $response = $this->getController()->view(1); $this->assertIsViewResponse($response); $this->assertViewNameEquals('admin.databases.view', $response); @@ -101,4 +104,21 @@ class DatabaseControllerTest extends TestCase $this->assertViewKeyEquals('locations', 'getAllWithNodes', $response); $this->assertViewKeyEquals('host', 'getWithServers', $response); } + + /** + * Return an instance of the DatabaseController with mock dependencies. + * + * @return \Pterodactyl\Http\Controllers\Admin\DatabaseController + */ + private function getController(): DatabaseController + { + return new DatabaseController( + $this->alert, + $this->repository, + $this->creationService, + $this->deletionService, + $this->updateService, + $this->locationRepository + ); + } } diff --git a/tests/Unit/Repositories/Eloquent/DatabaseRepositoryTest.php b/tests/Unit/Repositories/Eloquent/DatabaseRepositoryTest.php index f33ec15e3..4a7f0ccc3 100644 --- a/tests/Unit/Repositories/Eloquent/DatabaseRepositoryTest.php +++ b/tests/Unit/Repositories/Eloquent/DatabaseRepositoryTest.php @@ -96,7 +96,7 @@ class DatabaseRepositoryTest extends TestCase public function testCreateDatabaseStatement() { $query = sprintf('CREATE DATABASE IF NOT EXISTS `%s`', 'test_database'); - $this->repository->shouldReceive('runStatement')->with($query, 'test')->once()->andReturn(true); + $this->repository->shouldReceive('runStatement')->with($query)->once()->andReturn(true); $this->assertTrue($this->repository->createDatabase('test_database', 'test')); } @@ -107,7 +107,7 @@ class DatabaseRepositoryTest extends TestCase 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->repository->shouldReceive('runStatement')->with($query)->once()->andReturn(true); $this->assertTrue($this->repository->createUser('test', '%', 'password', 'test')); } @@ -118,7 +118,7 @@ class DatabaseRepositoryTest extends TestCase public function testUserAssignmentToDatabaseStatement() { $query = sprintf('GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX, EXECUTE ON `%s`.* TO `%s`@`%s`', 'test_database', 'test', '%'); - $this->repository->shouldReceive('runStatement')->with($query, 'test')->once()->andReturn(true); + $this->repository->shouldReceive('runStatement')->with($query)->once()->andReturn(true); $this->assertTrue($this->repository->assignUserToDatabase('test_database', 'test', '%', 'test')); } @@ -128,7 +128,7 @@ class DatabaseRepositoryTest extends TestCase */ public function testFlushStatement() { - $this->repository->shouldReceive('runStatement')->with('FLUSH PRIVILEGES', 'test')->once()->andReturn(true); + $this->repository->shouldReceive('runStatement')->with('FLUSH PRIVILEGES')->once()->andReturn(true); $this->assertTrue($this->repository->flush('test')); } @@ -139,7 +139,7 @@ class DatabaseRepositoryTest extends TestCase public function testDropDatabaseStatement() { $query = sprintf('DROP DATABASE IF EXISTS `%s`', 'test_database'); - $this->repository->shouldReceive('runStatement')->with($query, 'test')->once()->andReturn(true); + $this->repository->shouldReceive('runStatement')->with($query)->once()->andReturn(true); $this->assertTrue($this->repository->dropDatabase('test_database', 'test')); } @@ -150,7 +150,7 @@ class DatabaseRepositoryTest extends TestCase public function testDropUserStatement() { $query = sprintf('DROP USER IF EXISTS `%s`@`%s`', 'test', '%'); - $this->repository->shouldReceive('runStatement')->with($query, 'test')->once()->andReturn(true); + $this->repository->shouldReceive('runStatement')->with($query)->once()->andReturn(true); $this->assertTrue($this->repository->dropUser('test', '%', 'test')); } diff --git a/tests/Unit/Services/Database/DatabaseHostServiceTest.php b/tests/Unit/Services/Database/DatabaseHostServiceTest.php deleted file mode 100644 index f5e8d09f6..000000000 --- a/tests/Unit/Services/Database/DatabaseHostServiceTest.php +++ /dev/null @@ -1,201 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Tests\Unit\Services\Administrative; - -use Mockery as m; -use Tests\TestCase; -use Illuminate\Database\DatabaseManager; -use Pterodactyl\Exceptions\DisplayException; -use Illuminate\Contracts\Encryption\Encrypter; -use Pterodactyl\Extensions\DynamicDatabaseConnection; -use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; -use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; - -class DatabaseHostServiceTest extends TestCase -{ - /** - * @var \Illuminate\Database\DatabaseManager - */ - protected $database; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - protected $databaseRepository; - - /** - * @var \Pterodactyl\Extensions\DynamicDatabaseConnection - */ - protected $dynamic; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - protected $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Databases\HostsUpdateService - */ - protected $service; - - /** - * Setup tests. - */ - public function setUp() - { - 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 HostUpdateService( - $this->database, - $this->databaseRepository, - $this->repository, - $this->dynamic, - $this->encrypter - ); - } - - /** - * Test that creating a host returns the correct data. - */ - public function testHostIsCreated() - { - $data = [ - 'password' => 'raw-password', - 'name' => 'HostName', - 'host' => '127.0.0.1', - 'port' => 3306, - 'username' => 'someusername', - 'node_id' => null, - ]; - - $finalData = (object) array_replace($data, ['password' => 'enc-password']); - - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->encrypter->shouldReceive('encrypt')->with('raw-password')->once()->andReturn('enc-password'); - - $this->repository->shouldReceive('create')->with([ - 'password' => 'enc-password', - 'name' => 'HostName', - 'host' => '127.0.0.1', - 'port' => 3306, - 'username' => 'someusername', - 'max_databases' => null, - 'node_id' => null, - ])->once()->andReturn($finalData); - - $this->dynamic->shouldReceive('set')->with('dynamic', $finalData)->once()->andReturnNull(); - $this->database->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf() - ->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull(); - - $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $response = $this->service->create($data); - - $this->assertNotNull($response); - $this->assertTrue(is_object($response), 'Assert that response is an object.'); - - $this->assertEquals('enc-password', $response->password); - $this->assertEquals('HostName', $response->name); - $this->assertEquals('127.0.0.1', $response->host); - $this->assertEquals(3306, $response->port); - $this->assertEquals('someusername', $response->username); - $this->assertNull($response->node_id); - } - - /** - * Test that passing a password will store an encrypted version in the DB. - */ - public function testHostIsUpdatedWithPasswordProvided() - { - $finalData = (object) ['password' => 'enc-pass', 'host' => '123.456.78.9']; - - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->encrypter->shouldReceive('encrypt')->with('raw-pass')->once()->andReturn('enc-pass'); - - $this->repository->shouldReceive('update')->with(1, [ - 'password' => 'enc-pass', - 'host' => '123.456.78.9', - ])->once()->andReturn($finalData); - - $this->dynamic->shouldReceive('set')->with('dynamic', $finalData)->once()->andReturnNull(); - $this->database->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf() - ->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull(); - - $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $response = $this->service->update(1, ['password' => 'raw-pass', 'host' => '123.456.78.9']); - - $this->assertNotNull($response); - $this->assertEquals('enc-pass', $response->password); - $this->assertEquals('123.456.78.9', $response->host); - } - - /** - * Test that passing no or empty password will skip storing it. - */ - public function testHostIsUpdatedWithoutPassword() - { - $finalData = (object) ['host' => '123.456.78.9']; - - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->encrypter->shouldNotReceive('encrypt'); - - $this->repository->shouldReceive('update')->with(1, ['host' => '123.456.78.9'])->once()->andReturn($finalData); - - $this->dynamic->shouldReceive('set')->with('dynamic', $finalData)->once()->andReturnNull(); - $this->database->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf() - ->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull(); - - $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $response = $this->service->update(1, ['password' => '', 'host' => '123.456.78.9']); - - $this->assertNotNull($response); - $this->assertEquals('123.456.78.9', $response->host); - } - - /** - * Test that a database host can be deleted. - */ - public function testHostIsDeleted() - { - $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('exceptions.databases.delete_has_databases'), $exception->getMessage()); - } - } -} diff --git a/tests/Unit/Services/Database/DatabaseManagementServiceTest.php b/tests/Unit/Services/Database/DatabaseManagementServiceTest.php deleted file mode 100644 index a721b4760..000000000 --- a/tests/Unit/Services/Database/DatabaseManagementServiceTest.php +++ /dev/null @@ -1,344 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Tests\Unit\Services\Database; - -use Exception; -use Mockery as m; -use Tests\TestCase; -use phpmock\phpunit\PHPMock; -use Illuminate\Database\DatabaseManager; -use Illuminate\Contracts\Encryption\Encrypter; -use Pterodactyl\Extensions\DynamicDatabaseConnection; -use Pterodactyl\Services\Databases\DatabaseManagementService; -use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; - -class DatabaseManagementServiceTest extends TestCase -{ - use PHPMock; - - const TEST_DATA = [ - 'server_id' => 1, - 'database' => 'd1_dbname', - 'remote' => '%', - 'username' => 'u1_str_random', - 'password' => 'enc_password', - 'database_host_id' => 3, - ]; - - /** - * @var \Illuminate\Database\DatabaseManager - */ - protected $database; - - /** - * @var \Pterodactyl\Extensions\DynamicDatabaseConnection - */ - protected $dynamic; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - protected $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService - */ - protected $service; - - /** - * Setup tests. - */ - public function setUp() - { - parent::setUp(); - - $this->database = m::mock(DatabaseManager::class); - $this->dynamic = m::mock(DynamicDatabaseConnection::class); - $this->encrypter = m::mock(Encrypter::class); - $this->repository = m::mock(DatabaseRepositoryInterface::class); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Database', 'str_random') - ->expects($this->any())->willReturn('str_random'); - - $this->service = new DatabaseManagementService( - $this->database, - $this->dynamic, - $this->repository, - $this->encrypter - ); - } - - /** - * Test that a new database can be created that is linked to a specific host. - */ - public function testCreateANewDatabaseThatIsLinkedToAHost() - { - $this->encrypter->shouldReceive('encrypt')->with('str_random')->once()->andReturn('enc_password'); - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - - $this->repository->shouldReceive('createIfNotExists') - ->with(self::TEST_DATA) - ->once() - ->andReturn((object) self::TEST_DATA); - $this->dynamic->shouldReceive('set') - ->with('dynamic', self::TEST_DATA['database_host_id']) - ->once() - ->andReturnNull(); - $this->repository->shouldReceive('createDatabase')->with( - self::TEST_DATA['database'], - 'dynamic' - )->once()->andReturnNull(); - - $this->encrypter->shouldReceive('decrypt')->with('enc_password')->once()->andReturn('str_random'); - $this->repository->shouldReceive('createUser')->with( - self::TEST_DATA['username'], - self::TEST_DATA['remote'], - 'str_random', - 'dynamic' - )->once()->andReturnNull(); - - $this->repository->shouldReceive('assignUserToDatabase')->with( - self::TEST_DATA['database'], - self::TEST_DATA['username'], - self::TEST_DATA['remote'], - 'dynamic' - )->once()->andReturnNull(); - - $this->repository->shouldReceive('flush')->with('dynamic')->once()->andReturnNull(); - $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $response = $this->service->create(1, [ - 'database' => 'dbname', - 'remote' => '%', - 'database_host_id' => 3, - ]); - - $this->assertNotEmpty($response); - $this->assertTrue(is_object($response), 'Assert that response is an object.'); - - $this->assertEquals(self::TEST_DATA['database'], $response->database); - $this->assertEquals(self::TEST_DATA['remote'], $response->remote); - $this->assertEquals(self::TEST_DATA['username'], $response->username); - $this->assertEquals(self::TEST_DATA['password'], $response->password); - $this->assertEquals(self::TEST_DATA['database_host_id'], $response->database_host_id); - } - - /** - * Test that an exception before the database is created and returned does not attempt any actions. - * - * @expectedException \Exception - */ - public function testExceptionBeforeDatabaseIsCreatedShouldNotAttemptAnyRollBackOperations() - { - $this->encrypter->shouldReceive('encrypt')->with('str_random')->once()->andReturn('enc_password'); - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('createIfNotExists') - ->with(self::TEST_DATA) - ->once() - ->andThrow(new Exception('Test Message')); - $this->repository->shouldNotReceive('dropDatabase'); - $this->database->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); - - $this->service->create(1, [ - 'database' => 'dbname', - 'remote' => '%', - 'database_host_id' => 3, - ]); - } - - /** - * Test that an exception after database creation attempts to clean up previous operations. - * - * @expectedException \Exception - */ - public function testExceptionAfterDatabaseCreationShouldAttemptRollBackOperations() - { - $this->encrypter->shouldReceive('encrypt')->with('str_random')->once()->andReturn('enc_password'); - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('createIfNotExists') - ->with(self::TEST_DATA) - ->once() - ->andReturn((object) self::TEST_DATA); - $this->dynamic->shouldReceive('set') - ->with('dynamic', self::TEST_DATA['database_host_id']) - ->once() - ->andReturnNull(); - $this->repository->shouldReceive('createDatabase')->with( - self::TEST_DATA['database'], - 'dynamic' - )->once()->andThrow(new Exception('Test Message')); - - $this->repository->shouldReceive('dropDatabase') - ->with(self::TEST_DATA['database'], 'dynamic') - ->once() - ->andReturnNull(); - $this->repository->shouldReceive('dropUser')->with( - self::TEST_DATA['username'], - self::TEST_DATA['remote'], - 'dynamic' - )->once()->andReturnNull(); - $this->repository->shouldReceive('flush')->with('dynamic')->once()->andReturnNull(); - - $this->database->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); - - $this->service->create(1, [ - 'database' => 'dbname', - 'remote' => '%', - 'database_host_id' => 3, - ]); - } - - /** - * Test that an exception thrown during a rollback operation is silently handled and not returned. - */ - public function testExceptionThrownDuringRollBackProcessShouldNotBeThrownToCallingFunction() - { - $this->encrypter->shouldReceive('encrypt')->with('str_random')->once()->andReturn('enc_password'); - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('createIfNotExists') - ->with(self::TEST_DATA) - ->once() - ->andReturn((object) self::TEST_DATA); - $this->dynamic->shouldReceive('set') - ->with('dynamic', self::TEST_DATA['database_host_id']) - ->once() - ->andReturnNull(); - $this->repository->shouldReceive('createDatabase')->with( - self::TEST_DATA['database'], - 'dynamic' - )->once()->andThrow(new Exception('Test One')); - - $this->repository->shouldReceive('dropDatabase')->with(self::TEST_DATA['database'], 'dynamic') - ->once()->andThrow(new Exception('Test Two')); - - $this->database->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); - - try { - $this->service->create(1, [ - 'database' => 'dbname', - 'remote' => '%', - 'database_host_id' => 3, - ]); - } catch (Exception $ex) { - $this->assertInstanceOf(Exception::class, $ex); - $this->assertEquals('Test One', $ex->getMessage()); - } - } - - /** - * Test that a password can be changed for a given database. - */ - public function testDatabasePasswordShouldBeChanged() - { - $this->repository->shouldReceive('find')->with(1)->once()->andReturn((object) self::TEST_DATA); - $this->dynamic->shouldReceive('set') - ->with('dynamic', self::TEST_DATA['database_host_id']) - ->once() - ->andReturnNull(); - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - - $this->encrypter->shouldReceive('encrypt')->with('new_password')->once()->andReturn('new_enc_password'); - $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('update')->with(1, [ - 'password' => 'new_enc_password', - ])->andReturn(true); - - $this->repository->shouldReceive('dropUser')->with( - self::TEST_DATA['username'], - self::TEST_DATA['remote'], - 'dynamic' - )->once()->andReturnNull(); - - $this->repository->shouldReceive('createUser')->with( - self::TEST_DATA['username'], - self::TEST_DATA['remote'], - 'new_password', - 'dynamic' - )->once()->andReturnNull(); - - $this->repository->shouldReceive('assignUserToDatabase')->with( - self::TEST_DATA['database'], - self::TEST_DATA['username'], - self::TEST_DATA['remote'], - 'dynamic' - )->once()->andReturnNull(); - - $this->repository->shouldReceive('flush')->with('dynamic')->once()->andReturnNull(); - $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $response = $this->service->changePassword(1, 'new_password'); - - $this->assertTrue($response); - } - - /** - * Test that an exception thrown while changing a password will attempt a rollback. - * - * @expectedException \Exception - */ - public function testExceptionThrownWhileChangingDatabasePasswordShouldRollBack() - { - $this->repository->shouldReceive('find')->with(1)->once()->andReturn((object) self::TEST_DATA); - $this->dynamic->shouldReceive('set') - ->with('dynamic', self::TEST_DATA['database_host_id']) - ->once() - ->andReturnNull(); - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - - $this->encrypter->shouldReceive('encrypt')->with('new_password')->once()->andReturn('new_enc_password'); - $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('update')->with(1, [ - 'password' => 'new_enc_password', - ])->andReturn(true); - - $this->repository->shouldReceive('dropUser')->with( - self::TEST_DATA['username'], - self::TEST_DATA['remote'], - 'dynamic' - )->once()->andThrow(new Exception()); - - $this->database->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); - - $this->service->changePassword(1, 'new_password'); - } - - /** - * Test that a database can be deleted. - */ - public function testDatabaseShouldBeDeleted() - { - $this->repository->shouldReceive('find')->with(1)->once()->andReturn((object) self::TEST_DATA); - $this->dynamic->shouldReceive('set') - ->with('dynamic', self::TEST_DATA['database_host_id']) - ->once() - ->andReturnNull(); - - $this->repository->shouldReceive('dropDatabase') - ->with(self::TEST_DATA['database'], 'dynamic') - ->once() - ->andReturnNull(); - $this->repository->shouldReceive('dropUser')->with( - self::TEST_DATA['username'], - self::TEST_DATA['remote'], - 'dynamic' - )->once()->andReturnNull(); - $this->repository->shouldReceive('flush')->with('dynamic')->once()->andReturnNull(); - $this->repository->shouldReceive('delete')->with(1)->once()->andReturn(1); - - $response = $this->service->delete(1); - - $this->assertEquals(1, $response); - } -} diff --git a/tests/Unit/Services/Databases/DatabasePasswordServiceTest.php b/tests/Unit/Services/Databases/DatabasePasswordServiceTest.php new file mode 100644 index 000000000..099d44616 --- /dev/null +++ b/tests/Unit/Services/Databases/DatabasePasswordServiceTest.php @@ -0,0 +1,99 @@ +connection = m::mock(ConnectionInterface::class); + $this->dynamic = m::mock(DynamicDatabaseConnection::class); + $this->encrypter = m::mock(Encrypter::class); + $this->repository = m::mock(DatabaseRepositoryInterface::class); + } + + /** + * Test that a password can be updated. + * + * @dataProvider useModelDataProvider + */ + public function testPasswordIsChanged($useModel) + { + $model = factory(Database::class)->make(); + + if (! $useModel) { + $this->repository->shouldReceive('find')->with(1234)->once()->andReturn($model); + } + + $this->dynamic->shouldReceive('set')->with('dynamic', $model->database_host_id)->once()->andReturnNull(); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->encrypter->shouldReceive('encrypt')->with('test123')->once()->andReturn('enc123'); + + $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf(); + $this->repository->shouldReceive('update')->with($model->id, ['password' => 'enc123'])->once()->andReturn(true); + + $this->repository->shouldReceive('dropUser')->with($model->username, $model->remote)->once()->andReturnNull(); + $this->repository->shouldReceive('createUser')->with($model->username, $model->remote, 'test123')->once()->andReturnNull(); + $this->repository->shouldReceive('assignUserToDatabase')->with($model->database, $model->username, $model->remote)->once()->andReturnNull(); + $this->repository->shouldReceive('flush')->withNoArgs()->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle($useModel ? $model : 1234, 'test123'); + $this->assertNotEmpty($response); + $this->assertTrue($response); + } + + /** + * Data provider to determine if a model should be passed or an int. + * + * @return array + */ + public function useModelDataProvider(): array + { + return [[false], [true]]; + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Databases\DatabasePasswordService + */ + private function getService(): DatabasePasswordService + { + return new DatabasePasswordService($this->connection, $this->repository, $this->dynamic, $this->encrypter); + } +} diff --git a/tests/Unit/Services/Databases/Hosts/HostCreationServiceTest.php b/tests/Unit/Services/Databases/Hosts/HostCreationServiceTest.php new file mode 100644 index 000000000..603b871a0 --- /dev/null +++ b/tests/Unit/Services/Databases/Hosts/HostCreationServiceTest.php @@ -0,0 +1,101 @@ +connection = m::mock(ConnectionInterface::class); + $this->databaseManager = m::mock(DatabaseManager::class); + $this->dynamic = m::mock(DynamicDatabaseConnection::class); + $this->encrypter = m::mock(Encrypter::class); + $this->repository = m::mock(DatabaseHostRepositoryInterface::class); + } + + /** + * Test that a database host can be created. + */ + public function testDatabaseHostIsCreated() + { + $model = factory(DatabaseHost::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->encrypter->shouldReceive('encrypt')->with('test123')->once()->andReturn('enc123'); + $this->repository->shouldReceive('create')->with(m::subset([ + 'password' => 'enc123', + 'username' => $model->username, + 'node_id' => $model->node_id, + ]))->once()->andReturn($model); + + $this->dynamic->shouldReceive('set')->with('dynamic', $model)->once()->andReturnNull(); + $this->databaseManager->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf(); + $this->databaseManager->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle([ + 'password' => 'test123', + 'username' => $model->username, + 'node_id' => $model->node_id, + ]); + + $this->assertNotEmpty($response); + $this->assertSame($model, $response); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Databases\Hosts\HostCreationService + */ + private function getService(): HostCreationService + { + return new HostCreationService( + $this->connection, + $this->databaseManager, + $this->repository, + $this->dynamic, + $this->encrypter + ); + } +} diff --git a/tests/Unit/Services/Databases/Hosts/HostDeletionServiceTest.php b/tests/Unit/Services/Databases/Hosts/HostDeletionServiceTest.php new file mode 100644 index 000000000..402bf507c --- /dev/null +++ b/tests/Unit/Services/Databases/Hosts/HostDeletionServiceTest.php @@ -0,0 +1,85 @@ +databaseRepository = m::mock(DatabaseRepositoryInterface::class); + $this->repository = m::mock(DatabaseHostRepositoryInterface::class); + } + + /** + * Test that a host can be deleted. + */ + public function testHostIsDeleted() + { + $this->databaseRepository->shouldReceive('findCountWhere')->with([['database_host_id', '=', 1234]])->once()->andReturn(0); + $this->repository->shouldReceive('delete')->with(1234)->once()->andReturn(1); + + $response = $this->getService()->handle(1234); + $this->assertNotEmpty($response); + $this->assertSame(1, $response); + } + + /** + * Test that an exception is thrown if a host with databases is deleted. + * + * @dataProvider databaseCountDataProvider + */ + public function testExceptionIsThrownIfDeletingHostWithDatabases($count) + { + $this->databaseRepository->shouldReceive('findCountWhere')->with([['database_host_id', '=', 1234]])->once()->andReturn($count); + + try { + $this->getService()->handle(1234); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(HasActiveServersException::class, $exception); + $this->assertEquals(trans('exceptions.databases.delete_has_databases'), $exception->getMessage()); + } + } + + /** + * Data provider to ensure exceptions are thrown for any value > 0. + * + * @return array + */ + public function databaseCountDataProvider(): array + { + return [[1], [2], [10]]; + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Databases\Hosts\HostDeletionService + */ + private function getService(): HostDeletionService + { + return new HostDeletionService($this->databaseRepository, $this->repository); + } +} diff --git a/tests/Unit/Services/Databases/Hosts/HostUpdateServiceTest.php b/tests/Unit/Services/Databases/Hosts/HostUpdateServiceTest.php new file mode 100644 index 000000000..7e115c000 --- /dev/null +++ b/tests/Unit/Services/Databases/Hosts/HostUpdateServiceTest.php @@ -0,0 +1,112 @@ +connection = m::mock(ConnectionInterface::class); + $this->databaseManager = m::mock(DatabaseManager::class); + $this->dynamic = m::mock(DynamicDatabaseConnection::class); + $this->encrypter = m::mock(Encrypter::class); + $this->repository = m::mock(DatabaseHostRepositoryInterface::class); + } + + /** + * Test that a password is encrypted before storage if provided. + */ + public function testPasswordIsEncryptedWhenProvided() + { + $model = factory(DatabaseHost::class)->make(); + + $this->encrypter->shouldReceive('encrypt')->with('test123')->once()->andReturn('enc123'); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('update')->with(1234, ['password' => 'enc123'])->once()->andReturn($model); + + $this->dynamic->shouldReceive('set')->with('dynamic', $model)->once()->andReturnNull(); + $this->databaseManager->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf(); + $this->databaseManager->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle(1234, ['password' => 'test123']); + $this->assertNotEmpty($response); + $this->assertSame($model, $response); + } + + /** + * Test that updates still occur when no password is provided. + */ + public function testUpdateOccursWhenNoPasswordIsProvided() + { + $model = factory(DatabaseHost::class)->make(); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('update')->with(1234, ['username' => 'test'])->once()->andReturn($model); + + $this->dynamic->shouldReceive('set')->with('dynamic', $model)->once()->andReturnNull(); + $this->databaseManager->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf(); + $this->databaseManager->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->getService()->handle(1234, ['password' => '', 'username' => 'test']); + $this->assertNotEmpty($response); + $this->assertSame($model, $response); + } + + /** + * Return an instance of the service with mocked dependencies. + * + * @return \Pterodactyl\Services\Databases\Hosts\HostUpdateService + */ + private function getService(): HostUpdateService + { + return new HostUpdateService( + $this->connection, + $this->databaseManager, + $this->repository, + $this->dynamic, + $this->encrypter + ); + } +}