From 7ec1c2b7f04c1177922ed51aca0120f1be5b9d5c Mon Sep 17 00:00:00 2001 From: punkfairie Date: Mon, 24 Feb 2025 20:31:25 -0800 Subject: [PATCH] testing --- app/Http/Requests/CategoryRequest.php | 15 +- composer.json | 1 + composer.lock | 99 ++++++++- tests/Feature/CategoryTest.php | 305 ++++++++++++++++++++++---- tests/Pest.php | 55 ++--- tests/TestCase.php | 2 +- 6 files changed, 392 insertions(+), 85 deletions(-) diff --git a/app/Http/Requests/CategoryRequest.php b/app/Http/Requests/CategoryRequest.php index 1ecfcb9..b51c1c4 100644 --- a/app/Http/Requests/CategoryRequest.php +++ b/app/Http/Requests/CategoryRequest.php @@ -2,7 +2,6 @@ namespace App\Http\Requests; -use App\Models\Category; use Illuminate\Foundation\Http\FormRequest; class CategoryRequest extends FormRequest @@ -17,18 +16,6 @@ public function rules(): array public function authorize(): bool { - if ($this->request->has('restore')) { - return $this->user()->can('restore', $this->route('category')); - } - - if ($this->routeIs('categories.store')) { - return $this->user()->can('create', Category::class); - } - - if ($this->routeIs('categories.update')) { - return $this->user()->can('update', $this->route('category')); - } - - return false; + return true; } } diff --git a/composer.json b/composer.json index f67d241..fe8ab93 100755 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "laravel/tinker": "^2.9" }, "require-dev": { + "defstudio/pest-plugin-laravel-expectations": "^2.4", "fakerphp/faker": "^1.23", "laravel/breeze": "^2.3", "laravel/pail": "^1.1", diff --git a/composer.lock b/composer.lock index 6385e49..ba09f20 100755 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a739267a391adcba09c97097cbad9f52", + "content-hash": "077ed4d24289030adae8f37f202a9bbc", "packages": [ { "name": "brick/math", @@ -5893,6 +5893,103 @@ ], "time": "2024-12-11T14:50:44+00:00" }, + { + "name": "defstudio/pest-plugin-laravel-expectations", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/defstudio/pest-plugin-laravel-expectations.git", + "reference": "4bfa314db13cba3271e25cb571aa8e8f73f8a2b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/defstudio/pest-plugin-laravel-expectations/zipball/4bfa314db13cba3271e25cb571aa8e8f73f8a2b4", + "reference": "4bfa314db13cba3271e25cb571aa8e8f73f8a2b4", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0|^11.0.3|^12.0", + "illuminate/database": "^10.0|^11.0.3|^12.0", + "illuminate/http": "^10.0|^11.0.3|^12.0", + "illuminate/support": "^10.0|^11.0.3|^12.0", + "illuminate/testing": "^10.0|^11.0.3|^12.0", + "pestphp/pest": "^2.0|^3.0", + "pestphp/pest-plugin": "^2.0|^3.0", + "pestphp/pest-plugin-laravel": "^2.0|^3.0", + "php": "^8.1.0" + }, + "require-dev": { + "ergebnis/phpstan-rules": "^2.1.0", + "laravel/pint": "^1.11.0", + "nesbot/carbon": "^2.62.1", + "orchestra/testbench": "^8.0|^9.0", + "phpstan/phpstan": "^1.10.29", + "phpstan/phpstan-strict-rules": "^1.5.1", + "rector/rector": "^1.0.3", + "symfony/var-dumper": "^6.3.3|^v7.0.4", + "symplify/phpstan-rules": "^12.1.4.72", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "DefStudio\\PestLaravelExpectations\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabio Ivona", + "email": "fabio.ivona@defstudio.it", + "homepage": "https://defstudio.it", + "role": "Developer" + }, + { + "name": "Daniele Romeo", + "email": "danieleromeo@defstudio.it", + "homepage": "https://defstudio.it", + "role": "Developer" + } + ], + "description": "A plugin to add laravel tailored expectations to Pest", + "keywords": [ + "expectations", + "framework", + "laravel", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "issues": "https://github.com/defstudio/pest-plugin-laravel-expectations/issues", + "source": "https://github.com/defstudio/pest-plugin-laravel-expectations/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://github.com/defstudio", + "type": "github" + }, + { + "url": "https://github.com/fabio-ivona", + "type": "github" + } + ], + "time": "2025-02-22T11:50:10+00:00" + }, { "name": "doctrine/deprecations", "version": "1.1.4", diff --git a/tests/Feature/CategoryTest.php b/tests/Feature/CategoryTest.php index fed7cf7..af7bcf3 100644 --- a/tests/Feature/CategoryTest.php +++ b/tests/Feature/CategoryTest.php @@ -6,65 +6,286 @@ covers(CategoryController::class); -test('guests can view category index', function () { - $response = $this->get(route('categories.index')); +dataset('invalid-values', [null, true, 50]); +dataset('valid-values', ['hello', 'hello there', 'h#llo', 'h3llo']); - $response->assertOk(); +describe('categories.index', function () { + test('can be rendered to guests', function () { + $response = $this->get(route('categories.index')); + + $response->assertOk(); + }); + + test('can be rendered to logged in users', function () { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->get(route('categories.index')); + + $response->assertOk(); + }); + + test('contains categories', function () { + $response = $this->get(route('categories.index')); + + $response->assertViewHas('categories', Category::all()); + expect($response['categories']) + ->toBeEloquentCollection() + ->toContainOnlyInstancesOf(Category::class); + }); + + test('contains trashedCategories', function () { + $response = $this->get(route('categories.index')); + + $response->assertViewHas( + 'trashedCategories', + Category::onlyTrashed()->get() + ); + expect($response['categories']) + ->toBeEloquentCollection() + ->toContainOnlyInstancesOf(Category::class); + }); }); -test('logged in users can view category index', function () { - $user = User::factory()->create(); - $response = $this->actingAs($user)->get(route('categories.index')); +describe('categories.create', function () { + test("can't be rendered to guests", function () { + $response = $this->get(route('categories.create')); - $response->assertOk(); + $response->assertForbidden(); + }); + + test('can be rendered to logged in users', function () { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->get(route('categories.create')); + + $response->assertOk(); + }); }); -test('categories.index request contains categories', function () { - $response = $this->get(route('categories.index')); +describe('categories.store', function () { + test("can't be accessed by guests", function () { + $category = Category::factory()->make(); - $response->assertViewHas('categories', Category::all()); - expect($response['categories']) - ->toBeEloquentCollection() - ->toContainOnlyInstancesOf(Category::class); + $response = $this->post( + route('categories.store'), + ['name' => $category->name] + ); + + $response->assertForbidden(); + expect($category)->not->toMatchDatabaseRecord(); + }); + + test('can be accessed by logged in users', function () { + $user = User::factory()->create(); + $category = Category::factory()->make(); + + $response = $this->actingAs($user)->post( + route('categories.store'), + ['name' => $category->name] + ); + + $response->assertRedirect(route('categories.index', absolute: false)); + expect($category)->toMatchDatabaseRecord(); + }); + + test('stores valid categories', function (string $name) { + $user = User::factory()->create(); + $category = Category::factory()->make(['name' => $name]); + + $response = $this->actingAs($user)->post( + route('categories.store'), + ['name' => $name] + ); + + $response->assertRedirect(route('categories.index', absolute: false)); + expect($category)->toMatchDatabaseRecord(); + })->with('valid-values'); + + test('does not store invalid categories', function (mixed $name) { + $user = User::factory()->create(); + $category = Category::factory()->make(['name' => $name]); + + $response = $this->actingAs($user)->post( + route('categories.store'), + ['name' => $name], + ); + + $response->assertRedirect(); + expect($category)->not->toMatchDatabaseRecord(); + })->with('invalid-values'); }); -test('categories.index request contains trashedCategories', function () { - $response = $this->get(route('categories.index')); +describe('categories.show', function () { + test('can be rendered to guests', function () { + $category = Category::factory()->create(); - $response - ->assertViewHas('trashedCategories', Category::onlyTrashed()->get()); - expect($response['categories']) - ->toBeEloquentCollection() - ->toContainOnlyInstancesOf(Category::class); + $response = $this->get(route('categories.show', $category)); + + $response->assertOk(); + }); + + test('can be rendered to logged in users', function () { + $user = User::factory()->create(); + $category = Category::factory()->create(); + + $response = $this + ->actingAs($user) + ->get(route('categories.show', $category)); + + $response->assertOk(); + }); + + test('contains the category', function () { + $category = Category::factory()->create(); + + $response = $this->get(route('categories.show', $category)); + + $response->assertViewHas('category', $category); + }); + + test('can show trashed categories', function () { + $category = Category::factory()->trashed()->create(); + + $response = $this->get(route('categories.show', $category)); + + $response->assertViewHas('category', $category); + expect($category)->toBeSoftDeleted(); + }); }); -test('guests can view categories', function () { - $category = Category::factory()->create(); - $response = $this->get(route('categories.show', $category)); +describe('categories.edit', function () { + test("can't be rendered to guests", function () { + $category = Category::factory()->create(); - $response->assertOk(); + $response = $this->get(route('categories.edit', $category)); + + $response->assertForbidden(); + }); + + test('can be rendered to logged in users', function () { + $user = User::factory()->create(); + $category = Category::factory()->create(); + + $response = $this + ->actingAs($user) + ->get(route('categories.edit', $category)); + + $response->assertOk(); + }); + + test('contains the category', function () { + $user = User::factory()->create(); + $category = Category::factory()->create(); + + $response = $this + ->actingAs($user) + ->get(route('categories.edit', $category)); + + $response->assertViewHas('category', $category); + }); }); -test('logged in users can view categories', function () { - $user = User::factory()->create(); - $category = Category::factory()->create(); - $response = $this - ->actingAs($user) - ->get(route('categories.show', $category)); +describe('categories.update', function () { + test("can't be accessed by guests", function () { + $category = Category::factory()->create(); + $updated = Category::factory()->make(); - $response->assertOk(); + $response = $this->patch(route('categories.update', $category), [ + 'name' => $updated->name + ]); + + $response->assertForbidden(); + expect($category)->toBeInDatabaseExactly(); + }); + + test('can be accessed by logged in users', function () { + $user = User::factory()->create(); + $category = Category::factory()->create(); + $updated = Category::factory()->make(); + + $response = $this + ->actingAs($user) + ->patch(route('categories.update', $category), [ + 'name' => $updated->name + ]); + + $category->refresh(); + + $response->assertRedirect(route('categories.index', absolute: false)); + expect($category)->toMatchObject($updated); + }); + + test('updates categories with valid values', function (string $name) { + $user = User::factory()->create(); + $category = Category::factory()->create(); + + $response = $this + ->actingAs($user) + ->patch(route('categories.update', $category), [ + 'name' => $name + ]); + + $category->refresh(); + + $response->assertRedirect(route('categories.index', absolute: false)); + expect($category->name)->toEqual($name); + })->with('valid-values'); + + test( + 'does not update categories with invalid values', + function (mixed $name) { + $user = User::factory()->create(); + $category = Category::factory()->create(); + + $response = $this + ->actingAs($user) + ->patch(route('categories.update', $category), [ + 'name' => $name + ]); + + $updated = Category::find($category->id); + + $response->assertRedirect(); + expect($updated->name)->toEqual($category->name); + } + )->with('invalid-values'); + + test('restores trashed categories', function () { + $user = User::factory()->create(); + $category = Category::factory()->trashed()->create(); + + $response = $this + ->actingAs($user) + ->patch(route('categories.update', $category), [ + 'restore' => 1 + ]); + + $category->refresh(); + + $response->assertRedirect(); + expect($category)->not->toBeSoftDeleted(); + }); }); -test('categories.show contains the category', function () { - $category = Category::factory()->create(); - $response = $this->get(route('categories.show', $category)); +describe('categories.destroy', function () { + test("can't be accessed by guests", function () { + $category = Category::factory()->create(); - $response->assertViewHas('category', $category); -}); - -test('categories.show can show trashed categories', function () { - $category = Category::factory()->trashed()->create(); - $response = $this->get(route('categories.show', $category)); - - $response->assertViewHas('category', $category); + $response = $this->delete(route('categories.destroy', $category)); + + $response->assertForbidden(); + expect($category)->not->toBeSoftDeleted(); + }); + + test('can be accessed by logged in users', function () { + $user = User::factory()->create(); + $category = Category::factory()->create(); + + $response = $this + ->actingAs($user) + ->delete(route('categories.destroy', $category)); + + $response->assertRedirect(route('categories.index', absolute: false)); + expect($category)->toBeSoftDeleted(); + }); }); diff --git a/tests/Pest.php b/tests/Pest.php index 4da3399..7a0f69e 100755 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -11,10 +11,10 @@ | */ -use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Pest\Expectation; -use function PHPUnit\Framework\assertTrue; +use function Pest\Laravel\assertDatabaseHas; +use function Pest\Laravel\assertModelExists; pest() ->extend(Tests\TestCase::class) @@ -37,38 +37,39 @@ // https://github.com/defstudio/pest-plugin-laravel-expectations expect()->extend( - 'toBeEloquentCollection', - /** - * Assert that the value is an instance of \Illuminate\Database\Eloquent\Collection. - */ - function (): Expectation { - return $this->toBeInstanceOf(Collection::class); - } -); + 'toMatchDatabaseRecord', + function (?string $table = null, ?string $connection = null): Expectation { + $this->toBeInstanceOf(Model::class); + $table = $table ?? $this->value->getTable(); + $value = $this->value->attributesToArray(); -expect()->extend( - 'toBeSameModelAs', - /** - * Assert that the given model has the same ID and belong to the same table of another model. - */ - function (Model $model): Expectation { - /** @var Model $value */ - $value = $this->value; - - assertTrue($value->is($model), - 'Failed asserting that two models have the same ID and belong to the same table'); + assertDatabaseHas($table, $value, $connection); return $this; } ); -expect()->intercept( - 'toBe', - Model::class, - function (Model $anotherModel) { - return expect($this->value)->toBeSameModelAs($anotherModel); - }); +expect()->extend( + 'toBeInDatabaseExactly', + function (?string $table = null, ?string $connection = null): Expectation { + assertModelExists($this->value); + + return $this->toMatchDatabaseRecord(); + } +); + +expect()->pipe( + 'toMatchObject', + function (Closure $next, mixed $expected) { + if ($expected instanceof Model) { + return expect($this->value) + ->toMatchObject($expected->attributesToArray()); + } + + return $next; + } +); /* |-------------------------------------------------------------------------- diff --git a/tests/TestCase.php b/tests/TestCase.php index a0f65fe..5bd15b7 100755 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -12,5 +12,5 @@ abstract class TestCase extends BaseTestCase * @var bool * @noinspection PhpMissingFieldTypeInspection */ - protected $seed = true; +// protected $seed = true; }