diff --git a/app/Contracts/Repository/DatabaseHostInterface.php b/app/Contracts/Repository/DatabaseHostInterface.php new file mode 100644 index 000000000..e1fcee8bd --- /dev/null +++ b/app/Contracts/Repository/DatabaseHostInterface.php @@ -0,0 +1,30 @@ +. + * + * 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\Contracts\Repository; + +interface DatabaseHostInterface extends RepositoryInterface +{ + public function deleteIfNoDatabases($id); +} diff --git a/app/Contracts/Repository/LocationRepositoryInterface.php b/app/Contracts/Repository/LocationRepositoryInterface.php new file mode 100644 index 000000000..81a75ff80 --- /dev/null +++ b/app/Contracts/Repository/LocationRepositoryInterface.php @@ -0,0 +1,32 @@ +. + * + * 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\Contracts\Repository; + +use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface; + +interface LocationRepositoryInterface extends RepositoryInterface, SearchableInterface +{ + public function deleteIfNoNodes($id); +} diff --git a/app/Extensions/DynamicDatabaseConnection.php b/app/Extensions/DynamicDatabaseConnection.php index 18d0001c9..01308190d 100644 --- a/app/Extensions/DynamicDatabaseConnection.php +++ b/app/Extensions/DynamicDatabaseConnection.php @@ -24,6 +24,7 @@ namespace Pterodactyl\Extensions; +use Pterodactyl\Contracts\Repository\DatabaseHostInterface; use Pterodactyl\Models\DatabaseHost; use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Config\Repository as ConfigRepository; @@ -45,25 +46,25 @@ class DynamicDatabaseConnection protected $encrypter; /** - * @var \Pterodactyl\Models\DatabaseHost + * @var \Pterodactyl\Contracts\Repository\DatabaseHostInterface */ - protected $model; + protected $repository; /** * DynamicDatabaseConnection constructor. * - * @param \Illuminate\Config\Repository $config - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter - * @param \Pterodactyl\Models\DatabaseHost $model + * @param \Illuminate\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\DatabaseHostInterface $repository + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct( ConfigRepository $config, - Encrypter $encrypter, - DatabaseHost $model + DatabaseHostInterface $repository, + Encrypter $encrypter ) { $this->config = $config; $this->encrypter = $encrypter; - $this->model = $model; + $this->repository = $repository; } /** @@ -76,7 +77,7 @@ class DynamicDatabaseConnection public function set($connection, $host, $database = 'mysql') { if (! $host instanceof DatabaseHost) { - $host = $this->model->findOrFail($host); + $host = $this->repository->find($host); } $this->config->set('database.connections.' . $connection, [ diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index d7b2d02d0..739b91328 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -25,6 +25,10 @@ namespace Pterodactyl\Providers; use Illuminate\Support\ServiceProvider; +use Pterodactyl\Contracts\Repository\DatabaseHostInterface; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; +use Pterodactyl\Repositories\Eloquent\LocationRepository; use Pterodactyl\Repositories\Eloquent\UserRepository; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; @@ -35,6 +39,8 @@ class RepositoryServiceProvider extends ServiceProvider */ public function register() { + $this->app->bind(DatabaseHostInterface::class, DatabaseHostRepository::class); + $this->app->bind(LocationRepositoryInterface::class, LocationRepository::class); $this->app->bind(UserRepositoryInterface::class, UserRepository::class); } } diff --git a/app/Repositories/Eloquent/DatabaseHostRepository.php b/app/Repositories/Eloquent/DatabaseHostRepository.php new file mode 100644 index 000000000..921bc7b08 --- /dev/null +++ b/app/Repositories/Eloquent/DatabaseHostRepository.php @@ -0,0 +1,67 @@ +. + * + * 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\Repositories\Eloquent; + +use Pterodactyl\Contracts\Repository\DatabaseHostInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Models\DatabaseHost; + +class DatabaseHostRepository extends EloquentRepository implements DatabaseHostInterface +{ + /** + * Setup the model to be used. + * + * @return string + */ + public function model() + { + return DatabaseHost::class; + } + + /** + * 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) + { + $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/LocationRepository.php b/app/Repositories/Eloquent/LocationRepository.php new file mode 100644 index 000000000..fffbe61b0 --- /dev/null +++ b/app/Repositories/Eloquent/LocationRepository.php @@ -0,0 +1,90 @@ +. + * + * 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\Repositories\Eloquent; + +use Pterodactyl\Models\Location; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; + +class LocationRepository extends EloquentRepository implements LocationRepositoryInterface +{ + /** + * @var string + */ + protected $searchTerm; + + /** + * Setup model. + * + * @return string + */ + public function model() + { + return Location::class; + } + + /** + * Setup the model for search abilities. + * + * @param $term + * @return $this + */ + public function search($term) + { + if (empty($term)) { + return $this; + } + + $clone = clone $this; + $clone->searchTerm = $term; + + return $clone; + } + + /** + * Delete a location only if there are no nodes attached to it. + * + * @param $id + * @return bool|mixed|null + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function deleteIfNoNodes($id) + { + $location = $this->getBuilder()->with('nodes')->find($id); + + if (! $location) { + throw new RecordNotFoundException(); + } + + if ($location->nodes_count > 0) { + throw new DisplayException('Cannot delete a location that has nodes assigned to it.'); + } + + return $location->delete(); + } +} diff --git a/app/Services/DatabaseHostService.php b/app/Services/DatabaseHostService.php index ed2850201..b3f2be411 100644 --- a/app/Services/DatabaseHostService.php +++ b/app/Services/DatabaseHostService.php @@ -24,11 +24,10 @@ namespace Pterodactyl\Services; -use Pterodactyl\Models\DatabaseHost; use Illuminate\Database\DatabaseManager; -use Pterodactyl\Exceptions\DisplayException; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Extensions\DynamicDatabaseConnection; +use Pterodactyl\Contracts\Repository\DatabaseHostInterface; class DatabaseHostService { @@ -48,28 +47,28 @@ class DatabaseHostService protected $encrypter; /** - * @var \Pterodactyl\Models\DatabaseHost + * @var \Pterodactyl\Contracts\Repository\DatabaseHostInterface */ - protected $model; + protected $repository; /** * DatabaseHostService constructor. * - * @param \Illuminate\Database\DatabaseManager $database - * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter - * @param \Pterodactyl\Models\DatabaseHost $model + * @param \Pterodactyl\Contracts\Repository\DatabaseHostInterface $repository + * @param \Illuminate\Database\DatabaseManager $database + * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct( + DatabaseHostInterface $repository, DatabaseManager $database, DynamicDatabaseConnection $dynamic, - Encrypter $encrypter, - DatabaseHost $model + Encrypter $encrypter ) { $this->database = $database; $this->dynamic = $dynamic; $this->encrypter = $encrypter; - $this->model = $model; + $this->repository = $repository; } /** @@ -83,10 +82,10 @@ class DatabaseHostService */ public function create(array $data) { - $instance = $this->model->newInstance(); - $instance->password = $this->encrypter->encrypt(array_get($data, 'password')); + $this->database->beginTransaction(); - $instance->fill([ + $host = $this->repository->create([ + 'password' => $this->encrypter->encrypt(array_get($data, 'password')), 'name' => array_get($data, 'name'), 'host' => array_get($data, 'host'), 'port' => array_get($data, 'port'), @@ -96,12 +95,12 @@ class DatabaseHostService ]); // Check Access - $this->dynamic->set('dynamic', $instance); + $this->dynamic->set('dynamic', $host); $this->database->connection('dynamic')->select('SELECT 1 FROM dual'); - $instance->saveOrFail(); + $this->database->commit(); - return $instance; + return $host; } /** @@ -115,19 +114,22 @@ class DatabaseHostService */ public function update($id, array $data) { - $model = $this->model->findOrFail($id); + $this->database->beginTransaction(); if (! empty(array_get($data, 'password'))) { - $model->password = $this->encrypter->encrypt($data['password']); + $data['password'] = $this->encrypter->encrypt($data['password']); + } else { + unset($data['password']); } - $model->fill($data); - $this->dynamic->set('dynamic', $model); + $host = $this->repository->update($id, $data); + + $this->dynamic->set('dynamic', $host); $this->database->connection('dynamic')->select('SELECT 1 FROM dual'); - $model->saveOrFail(); + $this->database->commit(); - return $model; + return $host; } /** @@ -140,12 +142,6 @@ class DatabaseHostService */ public function delete($id) { - $model = $this->model->withCount('databases')->findOrFail($id); - - if ($model->databases_count > 0) { - throw new DisplayException('Cannot delete a database host that has active databases attached to it.'); - } - - return $model->delete(); + return $this->repository->deleteIfNoDatabases($id); } } diff --git a/app/Services/LocationService.php b/app/Services/LocationService.php index a6139e0d8..2bf7a41e3 100644 --- a/app/Services/LocationService.php +++ b/app/Services/LocationService.php @@ -24,25 +24,23 @@ namespace Pterodactyl\Services; -use Pterodactyl\Models\Location; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\Model\DataValidationException; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; class LocationService { /** - * @var \Pterodactyl\Models\Location + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface */ - protected $model; + protected $repository; /** * LocationService constructor. * - * @param \Pterodactyl\Models\Location $location + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository */ - public function __construct(Location $location) + public function __construct(LocationRepositoryInterface $repository) { - $this->model = $location; + $this->repository = $repository; } /** @@ -55,13 +53,7 @@ class LocationService */ public function create(array $data) { - $location = $this->model->newInstance($data); - - if (! $location->save()) { - throw new DataValidationException($location->getValidator()); - } - - return $location; + return $this->repository->create($data); } /** @@ -75,13 +67,7 @@ class LocationService */ public function update($id, array $data) { - $location = $this->model->findOrFail($id)->fill($data); - - if (! $location->save()) { - throw new DataValidationException($location->getValidator()); - } - - return $location; + return $this->repository->update($id, $data); } /** @@ -94,12 +80,6 @@ class LocationService */ public function delete($id) { - $location = $this->model->withCount('nodes')->findOrFail($id); - - if ($location->nodes_count > 0) { - throw new DisplayException('Cannot delete a location that has nodes assigned to it.'); - } - - return $location->delete(); + return $this->repository->deleteIfNoNodes($id); } } diff --git a/tests/Unit/Services/DatabaseHostServiceTest.php b/tests/Unit/Services/DatabaseHostServiceTest.php new file mode 100644 index 000000000..1239c1ece --- /dev/null +++ b/tests/Unit/Services/DatabaseHostServiceTest.php @@ -0,0 +1,193 @@ +. + * + * 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; + +use Mockery as m; +use Tests\TestCase; +use Illuminate\Database\DatabaseManager; +use Pterodactyl\Services\DatabaseHostService; +use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Extensions\DynamicDatabaseConnection; +use Pterodactyl\Contracts\Repository\DatabaseHostInterface; + +class DatabaseHostServiceTest extends TestCase +{ + /** + * @var \Illuminate\Database\DatabaseManager + */ + protected $database; + + /** + * @var \Pterodactyl\Extensions\DynamicDatabaseConnection + */ + protected $dynamic; + + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + protected $encrypter; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseHostInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\DatabaseHostService + */ + 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(DatabaseHostInterface::class); + + $this->service = new DatabaseHostService( + $this->repository, + $this->database, + $this->dynamic, + $this->encrypter + ); + } + + /** + * Test that creating a host returns the correct data. + */ + public function test_create_host_function() + { + $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 test_update_with_password() + { + $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 test_update_without_password() + { + $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 test_delete_function() + { + $this->repository->shouldReceive('deleteIfNoDatabases')->with(1)->once()->andReturn(true); + + $response = $this->service->delete(1); + + $this->assertTrue($response, 'Assert that response is true.'); + } +} diff --git a/tests/Unit/Services/LocationServiceTest.php b/tests/Unit/Services/LocationServiceTest.php new file mode 100644 index 000000000..442b02b58 --- /dev/null +++ b/tests/Unit/Services/LocationServiceTest.php @@ -0,0 +1,101 @@ +. + * + * 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; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Services\LocationService; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; + +class LocationServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\LocationService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(LocationRepositoryInterface::class); + + $this->service = new LocationService($this->repository); + } + + /** + * Test that creating a location returns the correct information. + */ + public function test_create_location() + { + $data = ['short' => 'shortCode', 'long' => 'longCode']; + + $this->repository->shouldReceive('create')->with($data)->once()->andReturn((object) $data); + + $response = $this->service->create($data); + + $this->assertNotNull($response); + $this->assertObjectHasAttribute('short', $response); + $this->assertObjectHasAttribute('long', $response); + $this->assertEquals('shortCode', $response->short); + $this->assertEquals('longCode', $response->long); + } + + /** + * Test that updating a location updates it correctly. + */ + public function test_update_location() + { + $data = ['short' => 'newShort']; + + $this->repository->shouldReceive('update')->with(1, $data)->once()->andReturn((object) $data); + + $response = $this->service->update(1, $data); + + $this->assertNotNull($response); + $this->assertObjectHasAttribute('short', $response); + $this->assertEquals('newShort', $response->short); + } + + /** + * Test that a location deletion returns valid data. + */ + public function test_delete_location() + { + $this->repository->shouldReceive('deleteIfNoNodes')->with(1)->once()->andReturn(true); + + $response = $this->service->delete(1); + + $this->assertTrue($response); + } +}