From 760525a673a40bd400735eca70977b38d500cb9c Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 15 Jun 2017 23:03:22 -0500 Subject: [PATCH] Push more tests for location services, setup travis CI integration --- .env.travis | 12 ++ .travis.yml | 32 +++ app/Services/LocationService.php | 17 +- config/database.php | 13 ++ database/factories/ModelFactory.php | 7 + phpunit.xml | 2 + .../Feature/Services/LocationServiceTest.php | 204 ++++++++++++++++++ tests/Unit/Services/UserServiceTest.php | 61 ------ 8 files changed, 279 insertions(+), 69 deletions(-) create mode 100644 .env.travis create mode 100644 .travis.yml create mode 100644 tests/Feature/Services/LocationServiceTest.php delete mode 100644 tests/Unit/Services/UserServiceTest.php diff --git a/.env.travis b/.env.travis new file mode 100644 index 000000000..c3bd08014 --- /dev/null +++ b/.env.travis @@ -0,0 +1,12 @@ +APP_ENV=testing +APP_DEBUG=true +APP_KEY=SomeRandomString32SomeRandomString32 + +DB_CONNECTION=tests +DB_TEST_USERNAME=root +DB_TEST_PASSWORD= + +CACHE_DRIVER=array +SESSION_DRIVER=array +QUEUE_DRIVER=sync +MAIL_DRIVER=array diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..47148c7ba --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ +langauge: php + +dist: trusty + +php: + - 7.0 + - 7.1 + - 7.2 + +sudo: required + +cache: + directories: + - $HOME/.composer/cache + +services: + - mysql + +before_install: + - mysql -e 'CREATE DATABASE travis;' + +before_script: + - phpenv config-rm xdebug.ini + - cp .env.travis .env + - composer self-update + - composer install --no-interaction + - php artisan key:generate --force + - php artisan migrate --force + - php artisan db:seed --force + +script: + - vendor/bin/phpunit --coverage-clover=coverage.xml diff --git a/app/Services/LocationService.php b/app/Services/LocationService.php index 8db67592e..e49304f0d 100644 --- a/app/Services/LocationService.php +++ b/app/Services/LocationService.php @@ -64,15 +64,16 @@ class LocationService /** * Update location model in the DB. * - * @param \Pterodactyl\Models\Location $location - * @param array $data + * @param int $id + * @param array $data * @return \Pterodactyl\Models\Location * * @throws \Throwable * @throws \Watson\Validating\ValidationException */ - public function update(Location $location, array $data) + public function update($id, array $data) { + $location = $this->model->findOrFail($id); $location->fill($data)->saveOrFail(); return $location; @@ -81,15 +82,15 @@ class LocationService /** * Delete a model from the DB. * - * @param \Pterodactyl\Models\Location $location + * @param int $id * @return bool - * - * @throws \Exception * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(Location $location) + public function delete($id) { - if ($location->nodes()->count() > 0) { + $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.'); } diff --git a/config/database.php b/config/database.php index 58324a0b5..00d447623 100644 --- a/config/database.php +++ b/config/database.php @@ -44,6 +44,19 @@ return [ 'prefix' => '', 'strict' => false, ], + + 'tests' => [ + 'driver' => 'mysql', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'travis'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + 'strict' => false, + ], ], /* diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index fe45d9de9..ee2adc3e5 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -31,3 +31,10 @@ $factory->state(Pterodactyl\Models\User::class, 'admin', function () { 'root_admin' => true, ]; }); + +$factory->define(Pterodactyl\Models\Location::class, function (Faker\Generator $faker) { + return [ + 'short' => $faker->domainWord, + 'long' => $faker->catchPhrase, + ]; +}); diff --git a/phpunit.xml b/phpunit.xml index 9ecda835a..ed3420743 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -24,8 +24,10 @@ + + diff --git a/tests/Feature/Services/LocationServiceTest.php b/tests/Feature/Services/LocationServiceTest.php new file mode 100644 index 000000000..f57ce1474 --- /dev/null +++ b/tests/Feature/Services/LocationServiceTest.php @@ -0,0 +1,204 @@ +. + * + * 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\Feature\Services; + +use Illuminate\Validation\ValidationException; +use Tests\TestCase; +use Pterodactyl\Models\Location; +use Pterodactyl\Services\LocationService; + +class LocationServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Services\LocationService + */ + protected $service; + + /** + * Setup the test instance. + */ + public function setUp() + { + parent::setUp(); + + $this->service = $this->app->make(LocationService::class); + } + + /** + * Test that a new location can be successfully added to the database. + */ + public function testShouldCreateANewLocation() + { + $data = [ + 'long' => 'Long Name', + 'short' => 'short', + ]; + + $response = $this->service->create($data); + + $this->assertInstanceOf(Location::class, $response); + $this->assertEquals($data['long'], $response->long); + $this->assertEquals($data['short'], $response->short); + $this->assertDatabaseHas('locations', [ + 'short' => $data['short'], + 'long' => $data['long'] + ]); + } + + /** + * Test that a validation error is thrown if a required field is missing. + * + * @expectedException \Watson\Validating\ValidationException + */ + public function testShouldFailToCreateLocationIfMissingParameter() + { + $data = ['long' => 'Long Name']; + + try { + $this->service->create($data); + } catch (\Exception $ex) { + $this->assertInstanceOf(ValidationException::class, $ex); + + $bag = $ex->getMessageBag()->messages(); + $this->assertArraySubset(['short' => [0]], $bag); + $this->assertEquals('The short field is required.', $bag['short'][0]); + + throw $ex; + } + } + + /** + * Test that a validation error is thrown if the short code provided is already in use. + * + * @expectedException \Watson\Validating\ValidationException + */ + public function testShouldFailToCreateLocationIfShortCodeIsAlreadyInUse() + { + factory(Location::class)->create(['short' => 'inuse']); + $data = [ + 'long' => 'Long Name', + 'short' => 'inuse', + ]; + + try { + $this->service->create($data); + } catch (\Exception $ex) { + $this->assertInstanceOf(ValidationException::class, $ex); + + $bag = $ex->getMessageBag()->messages(); + $this->assertArraySubset(['short' => [0]], $bag); + $this->assertEquals('The short has already been taken.', $bag['short'][0]); + + throw $ex; + } + } + + /** + * Test that a validation error is thrown if the short code is too long. + * + * @expectedException \Watson\Validating\ValidationException + */ + public function testShouldFailToCreateLocationIfShortCodeIsTooLong() + { + $data = [ + 'long' => 'Long Name', + 'short' => str_random(200), + ]; + + try { + $this->service->create($data); + } catch (\Exception $ex) { + $this->assertInstanceOf(ValidationException::class, $ex); + + $bag = $ex->getMessageBag()->messages(); + $this->assertArraySubset(['short' => [0]], $bag); + $this->assertEquals('The short must be between 1 and 60 characters.', $bag['short'][0]); + + throw $ex; + } + } + + /** + * Test that updating a model returns the updated data in a persisted form. + */ + public function testShouldUpdateLocationModelInDatabase() + { + $location = factory(Location::class)->create(); + $data = ['short' => 'test_short']; + + $model = $this->service->update($location->id, $data); + + $this->assertInstanceOf(Location::class, $model); + $this->assertEquals($data['short'], $model->short); + $this->assertNotEquals($model->short, $location->short); + $this->assertEquals($location->long, $model->long); + $this->assertDatabaseHas('locations', [ + 'short' => $data['short'], + 'long' => $location->long, + ]); + } + + /** + * Test that passing the same short-code into the update function as the model + * is currently using will not throw a validation exception. + */ + public function testShouldUpdateModelWithoutErrorWhenValidatingShortCodeIsUnique() + { + $location = factory(Location::class)->create(); + $data = ['short' => $location->short]; + + $model = $this->service->update($location->id, $data); + + $this->assertInstanceOf(Location::class, $model); + $this->assertEquals($model->short, $location->short); + + // Timestamps don't change if no data is modified. + $this->assertEquals($model->updated_at, $location->updated_at); + } + + /** + * Test that passing invalid data to the update method will throw a validation + * exception. + * + * @expectedException \Watson\Validating\ValidationException + */ + public function testShouldNotUpdateModelIfPassedDataIsInvalid() + { + $location = factory(Location::class)->create(); + $data = ['short' => str_random(200)]; + + $this->service->update($location->id, $data); + } + + /** + * Test that an invalid model exception is thrown if a model doesn't exist. + * + * @expectedException \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function testShouldThrowExceptionIfInvalidModelIdIsProvided() + { + $this->service->update(0, []); + } +} diff --git a/tests/Unit/Services/UserServiceTest.php b/tests/Unit/Services/UserServiceTest.php deleted file mode 100644 index c45474698..000000000 --- a/tests/Unit/Services/UserServiceTest.php +++ /dev/null @@ -1,61 +0,0 @@ -. - * - * 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 Illuminate\Config\Repository; -use Illuminate\Contracts\Auth\Guard; -use Illuminate\Contracts\Hashing\Hasher; -use Illuminate\Database\Connection; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\Queue; -use \Mockery as m; -use Pterodactyl\Models\User; -use Pterodactyl\Services\Components\UuidService; -use Pterodactyl\Services\UserService; -use Tests\TestCase; - -class UserServiceTest extends TestCase -{ - protected $service; - - public function setUp() - { - parent::setUp(); - - $this->config = m::mock(Repository::class); - $this->database = m::mock(Connection::class); - $this->guard = m::mock(Guard::class); - $this->hasher = m::mock(Hasher::class); - $this->uuid = m::mock(UuidService::class); - - $this->service = new UserService( - $this->config, - $this->database, - $this->guard, - $this->hasher, - $this->uuid - );; - } -}