From c44e70e8c103c139e078590bcda81b1008c08c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Mon, 22 Nov 2021 19:58:37 -0300 Subject: [PATCH 01/18] Criados Category e Genre --- .devcontainer/devcontainer.json | 2 +- .../Controllers/Api/CategoryController.php | 72 +++++++++++++++++++ app/Http/Controllers/Api/GenreController.php | 72 +++++++++++++++++++ app/Models/Category.php | 16 +++++ app/Models/Genre.php | 16 +++++ app/Models/Traits/Uuid.php | 15 ++++ database/factories/CategoryFactory.php | 13 ++++ database/factories/GenreFactory.php | 12 ++++ ...1_11_21_164340_create_categories_table.php | 35 +++++++++ .../2021_11_22_190938_create_genres_table.php | 34 +++++++++ database/seeds/CategorySeeder.php | 16 +++++ database/seeds/DatabaseSeeder.php | 2 + database/seeds/GenreSeeder.php | 16 +++++ routes/api.php | 5 ++ 14 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 app/Http/Controllers/Api/CategoryController.php create mode 100644 app/Http/Controllers/Api/GenreController.php create mode 100644 app/Models/Category.php create mode 100644 app/Models/Genre.php create mode 100644 app/Models/Traits/Uuid.php create mode 100644 database/factories/CategoryFactory.php create mode 100644 database/factories/GenreFactory.php create mode 100644 database/migrations/2021_11_21_164340_create_categories_table.php create mode 100644 database/migrations/2021_11_22_190938_create_genres_table.php create mode 100644 database/seeds/CategorySeeder.php create mode 100644 database/seeds/GenreSeeder.php diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5136d281d..5a0a8acb4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.128.0/containers/docker-existing-docker-compose // If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml. { - "name": "Laravel QuickStart", + "name": "code-micro-videos dev container", // Update the 'dockerComposeFile' list if you have more compose files or use different names. // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. diff --git a/app/Http/Controllers/Api/CategoryController.php b/app/Http/Controllers/Api/CategoryController.php new file mode 100644 index 000000000..dfed44140 --- /dev/null +++ b/app/Http/Controllers/Api/CategoryController.php @@ -0,0 +1,72 @@ +validate($request, [ + 'name' => 'required|max:255', + 'is_active' => 'boolean', + ]); + return Category::create($request->all()); + } + + /** + * Display the specified resource. + * + * @param \App\Models\Category $category + * @return \Illuminate\Http\Response + */ + public function show(Category $category) + { + return $category; + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param \App\Models\Category $category + * @return \Illuminate\Http\Response + */ + public function update(Request $request, Category $category) + { + $this->validate($request, $this->rules); + $this->update($request->all()); + return $request; + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Category $category + * @return \Illuminate\Http\Response + */ + public function destroy(Category $category) + { + $category->delete(); + return response()->noContent(); + } +} diff --git a/app/Http/Controllers/Api/GenreController.php b/app/Http/Controllers/Api/GenreController.php new file mode 100644 index 000000000..56018d398 --- /dev/null +++ b/app/Http/Controllers/Api/GenreController.php @@ -0,0 +1,72 @@ +validate($request, [ + 'name' => 'required|max:255', + 'is_active' => 'boolean', + ]); + return Genre::create($request->all()); + } + + /** + * Display the specified resource. + * + * @param \App\Models\Genre $genre + * @return \Illuminate\Http\Response + */ + public function show(Genre $genre) + { + return $genre; + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param \App\Models\Genre $genre + * @return \Illuminate\Http\Response + */ + public function update(Request $request, Genre $genre) + { + $this->validate($request, $this->rules); + $this->update($request->all()); + return $request; + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Genre $genre + * @return \Illuminate\Http\Response + */ + public function destroy(Genre $genre) + { + $category->delete(); + return response()->noContent(); + } +} diff --git a/app/Models/Category.php b/app/Models/Category.php new file mode 100644 index 000000000..ffcd8e3be --- /dev/null +++ b/app/Models/Category.php @@ -0,0 +1,16 @@ + 'string', + ]; +} diff --git a/app/Models/Genre.php b/app/Models/Genre.php new file mode 100644 index 000000000..f365ce0a3 --- /dev/null +++ b/app/Models/Genre.php @@ -0,0 +1,16 @@ + 'string', + ]; +} diff --git a/app/Models/Traits/Uuid.php b/app/Models/Traits/Uuid.php new file mode 100644 index 000000000..d98c270bd --- /dev/null +++ b/app/Models/Traits/Uuid.php @@ -0,0 +1,15 @@ +id = RamseyUuid::uuid4()->toString(); + }); + } +} diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php new file mode 100644 index 000000000..4119a9bac --- /dev/null +++ b/database/factories/CategoryFactory.php @@ -0,0 +1,13 @@ +define(Category::class, function (Faker $faker) { + return [ + 'name' => $faker->colorName, + 'description' => rand(1, 10) % 2 == 0 ? $faker->sentence() : null, + ]; +}); diff --git a/database/factories/GenreFactory.php b/database/factories/GenreFactory.php new file mode 100644 index 000000000..39379fd11 --- /dev/null +++ b/database/factories/GenreFactory.php @@ -0,0 +1,12 @@ +define(Genre::class, function (Faker $faker) { + return [ + 'name' => $faker->colorName, + ]; +}); diff --git a/database/migrations/2021_11_21_164340_create_categories_table.php b/database/migrations/2021_11_21_164340_create_categories_table.php new file mode 100644 index 000000000..74e2ba069 --- /dev/null +++ b/database/migrations/2021_11_21_164340_create_categories_table.php @@ -0,0 +1,35 @@ +uuid('id')->primary(); + $table->string('name'); + $table->text('description')->nullable(); + $table->boolean('is_active')->default(true); + $table->softDeletes(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('categories'); + } +} diff --git a/database/migrations/2021_11_22_190938_create_genres_table.php b/database/migrations/2021_11_22_190938_create_genres_table.php new file mode 100644 index 000000000..d97f45e89 --- /dev/null +++ b/database/migrations/2021_11_22_190938_create_genres_table.php @@ -0,0 +1,34 @@ +uuid('id')->primary(); + $table->string('name'); + $table->boolean('is_active')->default(true); + $table->softDeletes(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('genres'); + } +} diff --git a/database/seeds/CategorySeeder.php b/database/seeds/CategorySeeder.php new file mode 100644 index 000000000..b7846baed --- /dev/null +++ b/database/seeds/CategorySeeder.php @@ -0,0 +1,16 @@ +create(); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 91cb6d1c2..67440e54e 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -12,5 +12,7 @@ class DatabaseSeeder extends Seeder public function run() { // $this->call(UsersTableSeeder::class); + $this->call(CategorySeeder::class); + $this->call(GenreSeeder::class); } } diff --git a/database/seeds/GenreSeeder.php b/database/seeds/GenreSeeder.php new file mode 100644 index 000000000..57ba1d98e --- /dev/null +++ b/database/seeds/GenreSeeder.php @@ -0,0 +1,16 @@ +create(); + } +} diff --git a/routes/api.php b/routes/api.php index c641ca5e5..c225bae0f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -16,3 +16,8 @@ Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); + +Route::group(['namespace' => 'Api'], function() { + Route::resource('categories', 'CategoryController', ['except' => ['create', 'edit']]); + Route::resource('genres', 'GenreController', ['except' => ['create', 'edit']]); +}); \ No newline at end of file From 04b02660a897b1af935f5eea7b5fab5bd43ebd6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sun, 28 Nov 2021 09:05:08 -0300 Subject: [PATCH 02/18] Criados testes para Category e Genre --- .docker/mysql/initdb.sql | 2 + .gitignore | 1 + app/Models/Category.php | 6 +- app/Models/Genre.php | 6 +- database/factories/GenreFactory.php | 2 +- docker-compose.yaml | 5 +- routes/api.php | 5 +- tests/Feature/Models/CategoryTest.php | 89 +++++++++++++++++++++++++++ tests/Feature/Models/GenreTest.php | 81 ++++++++++++++++++++++++ tests/Unit/Models/CategoryTest.php | 55 +++++++++++++++++ tests/Unit/Models/GenreTes.php | 55 +++++++++++++++++ 11 files changed, 296 insertions(+), 11 deletions(-) create mode 100644 .docker/mysql/initdb.sql create mode 100644 tests/Feature/Models/CategoryTest.php create mode 100644 tests/Feature/Models/GenreTest.php create mode 100644 tests/Unit/Models/CategoryTest.php create mode 100644 tests/Unit/Models/GenreTes.php diff --git a/.docker/mysql/initdb.sql b/.docker/mysql/initdb.sql new file mode 100644 index 000000000..f7aa6a981 --- /dev/null +++ b/.docker/mysql/initdb.sql @@ -0,0 +1,2 @@ +CREATE DATABASE IF NOT EXISTS code_micro_videos; +CREATE DATABASE IF NOT EXISTS code_micro_videos_test; diff --git a/.gitignore b/.gitignore index fcdc9aff9..0faa0260e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /storage/*.key /vendor .env +.env.testing .env.backup .phpunit.result.cache Homestead.json diff --git a/app/Models/Category.php b/app/Models/Category.php index ffcd8e3be..41a8eea25 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -10,7 +10,7 @@ class Category extends Model use SoftDeletes, Traits\Uuid; protected $fillable = ['name', 'description', 'is_active']; protected $dates = ['deleted_at']; - protected $casts = [ - 'id' => 'string', - ]; + public $incrementing = false; + protected $keyType = 'string'; + protected $casts = ['is_active' => 'boolean']; } diff --git a/app/Models/Genre.php b/app/Models/Genre.php index f365ce0a3..0edd5ea5f 100644 --- a/app/Models/Genre.php +++ b/app/Models/Genre.php @@ -10,7 +10,7 @@ class Genre extends Model use SoftDeletes, Traits\Uuid; protected $fillable = ['name', 'is_active']; protected $dates = ['deleted_at']; - protected $casts = [ - 'id' => 'string', - ]; + public $incrementing = false; + protected $keyType = 'string'; + protected $casts = ['is_active' => 'boolean']; } diff --git a/database/factories/GenreFactory.php b/database/factories/GenreFactory.php index 39379fd11..297c07046 100644 --- a/database/factories/GenreFactory.php +++ b/database/factories/GenreFactory.php @@ -7,6 +7,6 @@ $factory->define(Genre::class, function (Faker $faker) { return [ - 'name' => $faker->colorName, + 'name' => $faker->country, ]; }); diff --git a/docker-compose.yaml b/docker-compose.yaml index a6cd5dfc6..2839a97f8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -5,12 +5,13 @@ services: app: build: . container_name: micro-videos-app - entrypoint: dockerize -template ./.docker/app/.env:.env -wait tcp://db:3306 -timeout 40s ./.docker/entrypoint.sh + entrypoint: dockerize -template ./.docker/app/.env:.env -template ./.docker/app/.env.testing:.env.testing -wait tcp://db:3306 -timeout 40s ./.docker/entrypoint.sh environment: - _DB_HOST=db - _DB_DATABASE=code_micro_videos - _DB_USERNAME=root - _DB_PASSWORD=root + - _TEST_DB_DATABASE=code_micro_videos volumes: - .:/var/www networks: @@ -42,8 +43,8 @@ services: - "33006:3306" volumes: - ./.docker/dbdata:/var/lib/mysql + - ./.docker/mysql:/docker-entrypoint-initdb.d environment: - - MYSQL_DATABASE=code_micro_videos - MYSQL_ROOT_PASSWORD=root networks: - app-network diff --git a/routes/api.php b/routes/api.php index c225bae0f..93ac978ee 100644 --- a/routes/api.php +++ b/routes/api.php @@ -18,6 +18,7 @@ }); Route::group(['namespace' => 'Api'], function() { - Route::resource('categories', 'CategoryController', ['except' => ['create', 'edit']]); - Route::resource('genres', 'GenreController', ['except' => ['create', 'edit']]); + $exceptCreateAndEdit = ['except' => ['create', 'edit']]; + Route::resource('categories', 'CategoryController', $exceptCreateAndEdit); + Route::resource('genres', 'GenreController', $exceptCreateAndEdit); }); \ No newline at end of file diff --git a/tests/Feature/Models/CategoryTest.php b/tests/Feature/Models/CategoryTest.php new file mode 100644 index 000000000..a82fef585 --- /dev/null +++ b/tests/Feature/Models/CategoryTest.php @@ -0,0 +1,89 @@ +create(); + $categories = Category::all(); + $fields = array_keys($categories->first()->getAttributes()); + $this->assertCount(1, $categories); + $this->assertEqualsCanonicalizing([ + 'name', + 'description', + 'is_active', + 'created_at', + 'deleted_at', + 'updated_at', + "id", + ], $fields); + } + + public function testCreate() + { + $category = Category::create([ + 'name' => 'test1', + ]); + $category->refresh(); + $this->assertEquals('test1', $category->name); + $this->assertNull($category->description); + $this->assertTrue($category->is_active); + $this->assertRegExp('/[a-f0-9]{8}\-[a-f0-9]{4}\-4[a-f0-9]{3}\-(8|9|a|b)[a-f0-9]{3}\-[a-f0-9]{12}/', $category->id); + $category = Category::create([ + 'name' => 'test2', + 'description' => null, + ]); + $category->refresh(); + $this->assertNull($category->description); + $category = Category::create([ + 'name' => 'test3', + 'is_active' => false, + ]); + $category->refresh(); + $this->assertFalse($category->is_active); + $category = Category::create([ + 'name' => 'test4', + 'is_active' => true, + ]); + $category->refresh(); + $this->assertTrue($category->is_active); + } + + public function testUpdate() + { + $category = factory(Category::class, 1)->create([ + 'description' => 'test description', + 'is_active' => false, + ])->first(); + + $data = [ + 'name' => 'test update', + 'description' => 'testing update', + 'is_active' => true, + ]; + $category->update($data); + + foreach($data as $key => $value) { + $this->assertEquals($value, $category->{$key}); + } + } + + public function testDelete() + { + $category = factory(Category::class, 1)->create()->first(); + $category->delete(); + $this->assertNull(Category::find($category->id)); + } +} diff --git a/tests/Feature/Models/GenreTest.php b/tests/Feature/Models/GenreTest.php new file mode 100644 index 000000000..124861d32 --- /dev/null +++ b/tests/Feature/Models/GenreTest.php @@ -0,0 +1,81 @@ +create(); + $genres = Genre::all(); + $fields = array_keys($genres->first()->getAttributes()); + $this->assertCount(1, $genres); + $this->assertEqualsCanonicalizing([ + 'name', + 'is_active', + 'created_at', + 'deleted_at', + 'updated_at', + "id", + ], $fields); + } + + public function testCreate() + { + $genre = Genre::create([ + 'name' => 'test1', + ]); + $genre->refresh(); + $this->assertEquals('test1', $genre->name); + $this->assertNull($genre->description); + $this->assertTrue($genre->is_active); + $this->assertRegExp('/[a-f0-9]{8}\-[a-f0-9]{4}\-4[a-f0-9]{3}\-(8|9|a|b)[a-f0-9]{3}\-[a-f0-9]{12}/', $genre->id); + $this->assertNull($genre->description); + $genre = Genre::create([ + 'name' => 'test3', + 'is_active' => false, + ]); + $genre->refresh(); + $this->assertFalse($genre->is_active); + $genre = Genre::create([ + 'name' => 'test4', + 'is_active' => true, + ]); + $genre->refresh(); + $this->assertTrue($genre->is_active); + } + + public function testUpdate() + { + $genre = factory(Genre::class, 1)->create([ + 'is_active' => false, + ])->first(); + + $data = [ + 'name' => 'test update', + 'is_active' => true, + ]; + $genre->update($data); + + foreach($data as $key => $value) { + $this->assertEquals($value, $genre->{$key}); + } + } + + public function testDelete() + { + $genre = factory(Genre::class, 1)->create()->first(); + $genre->delete(); + $this->assertNull(Genre::find($genre->id)); + } +} diff --git a/tests/Unit/Models/CategoryTest.php b/tests/Unit/Models/CategoryTest.php new file mode 100644 index 000000000..61e78317c --- /dev/null +++ b/tests/Unit/Models/CategoryTest.php @@ -0,0 +1,55 @@ +category = new Category(); + } + + protected function tearDown(): void + { + parent::tearDown(); + $this->category = new Category(); + } + + public function testFillable() + { + $fillable = ['name', 'description', 'is_active']; + $this->assertEquals($this->category->getFillable(), $fillable); + } + + public function testUseOfTrais() + { + $traits = [softDeletes::class, uuid::class]; + $categoryTraits = array_keys(class_uses(Category::class)); + $this->assertEqualsCanonicalizing($traits, $categoryTraits); + } + + public function testCasts() + { + $casts = ['is_active' => 'boolean']; + $this->assertEqualsCanonicalizing($casts, $this->category->getCasts()); + } + + public function testDates() + { + $dates = ['created_at', 'deleted_at', 'updated_at']; + $this->assertEqualsCanonicalizing($dates, $this->category->getDates()); + } + + public function testNotIncreminting() + { + $this->assertFalse($this->category->incrementing); + } +} diff --git a/tests/Unit/Models/GenreTes.php b/tests/Unit/Models/GenreTes.php new file mode 100644 index 000000000..eda330be5 --- /dev/null +++ b/tests/Unit/Models/GenreTes.php @@ -0,0 +1,55 @@ +genre = new Genre(); + } + + protected function tearDown(): void + { + parent::tearDown(); + $this->genre = new Genre(); + } + + public function testFillable() + { + $fillable = ['name', 'is_active']; + $this->assertEquals($this->genre->getFillable(), $fillable); + } + + public function testUseOfTrais() + { + $traits = [softDeletes::class, uuid::class]; + $genreTraits = array_keys(class_uses(Genre::class)); + $this->assertEqualsCanonicalizing($traits, $genreTraits); + } + + public function testCasts() + { + $casts = ['is_active' => 'boolean']; + $this->assertEqualsCanonicalizing($casts, $this->genre->getCasts()); + } + + public function testDates() + { + $dates = ['created_at', 'deleted_at', 'updated_at']; + $this->assertEqualsCanonicalizing($dates, $this->genre->getDates()); + } + + public function testNotIncreminting() + { + $this->assertFalse($this->genre->incrementing); + } +} From a77ec58ec4f415177279e252de3c5bfe4f1909cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sun, 5 Dec 2021 11:14:07 -0300 Subject: [PATCH 03/18] Criados testes HTTP para Genre e Category --- .../Controllers/Api/CategoryController.php | 18 ++- app/Http/Controllers/Api/GenreController.php | 20 ++- .../Api/CategoryControllerTest.php | 149 ++++++++++++++++++ .../Controllers/Api/GenreControllerTest.php | 129 +++++++++++++++ 4 files changed, 301 insertions(+), 15 deletions(-) create mode 100644 tests/Feature/Http/Controllers/Api/CategoryControllerTest.php create mode 100644 tests/Feature/Http/Controllers/Api/GenreControllerTest.php diff --git a/app/Http/Controllers/Api/CategoryController.php b/app/Http/Controllers/Api/CategoryController.php index dfed44140..ef6b20076 100644 --- a/app/Http/Controllers/Api/CategoryController.php +++ b/app/Http/Controllers/Api/CategoryController.php @@ -8,6 +8,10 @@ class CategoryController extends Controller { + private $rules = [ + 'name' => 'required|max:255', + 'is_active' => 'boolean', + ]; /** * Display a listing of the resource. * @@ -26,11 +30,10 @@ public function index() */ public function store(Request $request) { - $this->validate($request, [ - 'name' => 'required|max:255', - 'is_active' => 'boolean', - ]); - return Category::create($request->all()); + $this->validate($request, $this->rules); + $category = Category::create($request->all()); + $category->refresh(); + return $category; } /** @@ -54,8 +57,9 @@ public function show(Category $category) public function update(Request $request, Category $category) { $this->validate($request, $this->rules); - $this->update($request->all()); - return $request; + $category->update($request->all()); + $category->refresh(); + return $category; } /** diff --git a/app/Http/Controllers/Api/GenreController.php b/app/Http/Controllers/Api/GenreController.php index 56018d398..43d682604 100644 --- a/app/Http/Controllers/Api/GenreController.php +++ b/app/Http/Controllers/Api/GenreController.php @@ -8,6 +8,10 @@ class GenreController extends Controller { + private $rules = [ + 'name' => 'required|max:255', + 'is_active' => 'boolean', + ]; /** * Display a listing of the resource. * @@ -26,11 +30,10 @@ public function index() */ public function store(Request $request) { - $this->validate($request, [ - 'name' => 'required|max:255', - 'is_active' => 'boolean', - ]); - return Genre::create($request->all()); + $this->validate($request, $this->rules); + $genre = Genre::create($request->all()); + $genre->refresh(); + return $genre; } /** @@ -54,8 +57,9 @@ public function show(Genre $genre) public function update(Request $request, Genre $genre) { $this->validate($request, $this->rules); - $this->update($request->all()); - return $request; + $genre->update($request->all()); + $genre->refresh(); + return $genre; } /** @@ -66,7 +70,7 @@ public function update(Request $request, Genre $genre) */ public function destroy(Genre $genre) { - $category->delete(); + $genre->delete(); return response()->noContent(); } } diff --git a/tests/Feature/Http/Controllers/Api/CategoryControllerTest.php b/tests/Feature/Http/Controllers/Api/CategoryControllerTest.php new file mode 100644 index 000000000..f8a1d30c8 --- /dev/null +++ b/tests/Feature/Http/Controllers/Api/CategoryControllerTest.php @@ -0,0 +1,149 @@ +create(); + $response = $this->get(route('categories.index')); + + $response->assertStatus(200)->assertJson([$category->toArray()]); + } + + public function testShow() + { + $category = factory(Category::class)->create(); + $response = $this->get(route('categories.show', ['category' => $category->id])); + + $response->assertStatus(200)->assertJson($category->toArray()); + } + + public function testInvalidationDataPost() + { + $response = $this->json('POST', route('categories.store'), []); + $this->assertInvalidationRequired($response); + $response = $this->json('POST', route('categories.store'), [ + 'name' => str_repeat('a', 256), + 'is_active' => 'true', + ]); + $this->assertInvalidationName($response); + $this->assertInvalidationIsActive($response); + } + + public function testInvalidationDataPut() + { + $category = factory(Category::class)->create(); + $response = $this->json('PUT', route('categories.update', ['category' => $category->id]), []); + $this->assertInvalidationRequired($response); + $response = $this->json('PUT', route('categories.update', ['category' => $category->id]), [ + 'name' => str_repeat('a', 256), + 'is_active' => 'true', + ]); + $this->assertInvalidationName($response); + $this->assertInvalidationIsActive($response); + } + + public function testStore() { + $values = [ + 'name' => 'test1', + ]; + $response = $this->json('POST', route('categories.store'), $values); + $id = $response->json('id'); + $category = Category::find($id); + $response->assertStatus(201) + ->assertJson($category->toArray()); + $this->assertTrue($response->json('is_active')); + $this->assertNull($response->json('description')); + $this->assertEquals($values['name'], $response->json('name')); + $newValues = [ + 'name' => 'test2', + 'description' => 'description 1', + 'is_active' => false, + ]; + $response = $this->json('POST', route('categories.store'), $newValues); + $id = $response->json('id'); + $category = Category::find($id); + $response->assertStatus(201) + ->assertJson($category->toArray()); + $this->assertFalse($response->json('is_active')); + $this->assertEquals($newValues['description'], $response->json('description')); + $this->assertEquals($newValues['name'], $response->json('name')); + } + + public function testUpdate() + { + $category = factory(Category::class)->create([ + 'is_active' => false, + 'description' => 'description 1', + ]); + $newValues = [ + 'name' => 'test 1', + 'description' => 'description 2', + 'is_active' => true, + ]; + $response = $this->json('PUT', route('categories.update', ['category' => $category->id]), $newValues); + $category = Category::find($category->id); + $response->assertStatus(200) + ->assertJson($category->toArray()); + $this->assertTrue($response->json('is_active')); + $this->assertEquals($newValues['description'], $response->json('description')); + $this->assertEquals($newValues['name'], $response->json('name')); + + $response = $this->json('PUT', route('categories.update', ['category' => $category->id]), [ + 'description' => '', + 'name' => $newValues['name'], + ]); + $response->assertStatus(200)->assertJsonFragment(['description' => null]); + + $category->description = 'test description'; + $category->save(); + $response = $this->json('PUT', route('categories.update', ['category' => $category->id]), [ + 'description' => null, + 'name' => $newValues['name'], + ]); + $response->assertStatus(200)->assertJsonFragment(['description' => null]); + } + + public function testDelete() + { + $category = factory(Category::class)->create(); + $this->json('DELETE', route('categories.destroy', ['category' => $category->id])); + $category = Category::find($category->id); + $this->assertNull($category); + } + + protected function assertInvalidationRequired(TestResponse $response) + { + $response->assertStatus(422) + ->assertJsonValidationErrors(['name']) + ->assertJsonMissingValidationErrors(['is_active']) + ->assertJsonFragment( + [\Lang::get('validation.required', ['attribute' => 'name'])] + ); + } + + protected function assertInvalidationName(TestResponse $response) { + $response->assertStatus(422) + ->assertJsonValidationErrors(['name']) + ->assertJsonFragment( + [\Lang::get('validation.max.string', ['attribute' => 'name', 'max' => 255])] + ); + } + + protected function assertInvalidationIsActive(TestResponse $response) { + $response->assertStatus(422) + ->assertJsonValidationErrors(['is_active']) + ->assertJsonFragment( + [\Lang::get('validation.boolean', ['attribute' => 'is active'])] + ); + } +} diff --git a/tests/Feature/Http/Controllers/Api/GenreControllerTest.php b/tests/Feature/Http/Controllers/Api/GenreControllerTest.php new file mode 100644 index 000000000..a9f81f82c --- /dev/null +++ b/tests/Feature/Http/Controllers/Api/GenreControllerTest.php @@ -0,0 +1,129 @@ +create(); + $response = $this->get(route('genres.index')); + + $response->assertStatus(200)->assertJson([$genre->toArray()]); + } + + public function testShow() + { + $genre = factory(Genre::class)->create(); + $response = $this->get(route('genres.show', ['genre' => $genre->id])); + + $response->assertStatus(200)->assertJson($genre->toArray()); + } + + public function testInvalidationDataPost() + { + $response = $this->json('POST', route('genres.store'), []); + $this->assertInvalidationRequired($response); + $response = $this->json('POST', route('genres.store'), [ + 'name' => str_repeat('a', 256), + 'is_active' => 'true', + ]); + $this->assertInvalidationName($response); + $this->assertInvalidationIsActive($response); + } + + public function testInvalidationDataPut() + { + $genre = factory(Genre::class)->create(); + $response = $this->json('PUT', route('genres.update', ['genre' => $genre->id]), []); + $this->assertInvalidationRequired($response); + $response = $this->json('PUT', route('genres.update', ['genre' => $genre->id]), [ + 'name' => str_repeat('a', 256), + 'is_active' => 'true', + ]); + $this->assertInvalidationName($response); + $this->assertInvalidationIsActive($response); + } + + public function testStore() { + $values = [ + 'name' => 'test1', + ]; + $response = $this->json('POST', route('genres.store'), $values); + $id = $response->json('id'); + $genre = Genre::find($id); + $response->assertStatus(201) + ->assertJson($genre->toArray()); + $this->assertTrue($response->json('is_active')); + $this->assertEquals($values['name'], $response->json('name')); + $newValues = [ + 'name' => 'test2', + 'is_active' => false, + ]; + $response = $this->json('POST', route('genres.store'), $newValues); + $id = $response->json('id'); + $genre = Genre::find($id); + $response->assertStatus(201) + ->assertJson($genre->toArray()); + $this->assertFalse($response->json('is_active')); + $this->assertEquals($newValues['name'], $response->json('name')); + } + + public function testUpdate() + { + $genre = factory(Genre::class)->create([ + 'is_active' => false, + ]); + $newValues = [ + 'name' => 'test 1', + 'is_active' => true, + ]; + $response = $this->json('PUT', route('genres.update', ['genre' => $genre->id]), $newValues); + $genre = Genre::find($genre->id); + $response->assertStatus(200) + ->assertJson($genre->toArray()); + $this->assertTrue($response->json('is_active')); + $this->assertEquals($newValues['name'], $response->json('name')); + } + + public function testDelete() + { + $genre = factory(Genre::class)->create(); + $this->json('DELETE', route('genres.destroy', ['genre' => $genre->id])); + $genre = Genre::find($genre->id); + $this->assertNull($genre); + } + + protected function assertInvalidationRequired(TestResponse $response) + { + $response->assertStatus(422) + ->assertJsonValidationErrors(['name']) + ->assertJsonMissingValidationErrors(['is_active']) + ->assertJsonFragment( + [\Lang::get('validation.required', ['attribute' => 'name'])] + ); + } + + protected function assertInvalidationName(TestResponse $response) { + $response->assertStatus(422) + ->assertJsonValidationErrors(['name']) + ->assertJsonFragment( + [\Lang::get('validation.max.string', ['attribute' => 'name', 'max' => 255])] + ); + } + + protected function assertInvalidationIsActive(TestResponse $response) { + $response->assertStatus(422) + ->assertJsonValidationErrors(['is_active']) + ->assertJsonFragment( + [\Lang::get('validation.boolean', ['attribute' => 'is active'])] + ); + } +} From 78ac8cd6600b9bcc35699abc0ac10dd1bdb298b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sat, 11 Dec 2021 16:51:03 -0300 Subject: [PATCH 04/18] Abstract CRUD e Resource CastMember --- .docker/entrypoint.sh | 2 + .env.example | 6 +- .env.testing.example | 44 ++++++ Dockerfile.prod | 23 +++ .../Controllers/Api/BasicCrudController.php | 63 ++++++++ .../Controllers/Api/CastMemberController.php | 23 +++ .../Controllers/Api/CategoryController.php | 73 ++------- app/Http/Controllers/Api/GenreController.php | 74 ++------- app/Models/CastMember.php | 16 ++ cloudbuild.yaml | 33 ++++ database/factories/CastMemberFactory.php | 13 ++ ...12_09_194740_create_cast_members_table.php | 26 ++++ database/seeds/CastMemberSeeder.php | 16 ++ database/seeds/DatabaseSeeder.php | 1 + docker-compose.prod.yaml | 57 +++++++ routes/api.php | 1 + .../Api/BasicCrudControllerTest.php | 77 ++++++++++ .../Http/Controllers/Api/CastMemberTest.php | 85 +++++++++++ .../Api/CategoryControllerTest.php | 144 +++++++----------- .../Controllers/Api/GenreControllerTest.php | 7 +- .../Controllers/CategoryControllerStubs.php | 22 +++ tests/Stubs/Models/CategoryStub.php | 28 ++++ tests/Traits/TestSaves.php | 41 +++++ tests/Traits/TestValidations.php | 47 ++++++ .../Models/{GenreTes.php => GenreTest.php} | 0 25 files changed, 701 insertions(+), 221 deletions(-) create mode 100644 .env.testing.example create mode 100644 Dockerfile.prod create mode 100644 app/Http/Controllers/Api/BasicCrudController.php create mode 100644 app/Http/Controllers/Api/CastMemberController.php create mode 100644 app/Models/CastMember.php create mode 100644 cloudbuild.yaml create mode 100644 database/factories/CastMemberFactory.php create mode 100644 database/migrations/2021_12_09_194740_create_cast_members_table.php create mode 100644 database/seeds/CastMemberSeeder.php create mode 100644 docker-compose.prod.yaml create mode 100644 tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php create mode 100644 tests/Feature/Http/Controllers/Api/CastMemberTest.php create mode 100644 tests/Stubs/Controllers/CategoryControllerStubs.php create mode 100644 tests/Stubs/Models/CategoryStub.php create mode 100644 tests/Traits/TestSaves.php create mode 100644 tests/Traits/TestValidations.php rename tests/Unit/Models/{GenreTes.php => GenreTest.php} (100%) diff --git a/.docker/entrypoint.sh b/.docker/entrypoint.sh index 02020f80b..09634e5c4 100755 --- a/.docker/entrypoint.sh +++ b/.docker/entrypoint.sh @@ -1,6 +1,8 @@ #!/bin/bash #On error no such file entrypoint.sh, execute in terminal - dos2unix .docker\entrypoint.sh +cp .env.example .env +cp .env.example.testing .env.testing chown -R www-data:www-data . composer install php artisan key:generate diff --git a/.env.example b/.env.example index 604b401fe..20558db27 100644 --- a/.env.example +++ b/.env.example @@ -7,11 +7,11 @@ APP_URL=http://localhost LOG_CHANNEL=stack DB_CONNECTION=mysql -DB_HOST=127.0.0.1 +DB_HOST=db DB_PORT=3306 -DB_DATABASE=laravel +DB_DATABASE=code-micro-videos DB_USERNAME=root -DB_PASSWORD= +DB_PASSWORD=root BROADCAST_DRIVER=log CACHE_DRIVER=file diff --git a/.env.testing.example b/.env.testing.example new file mode 100644 index 000000000..e2a785e5b --- /dev/null +++ b/.env.testing.example @@ -0,0 +1,44 @@ +APP_NAME=Laravel +APP_ENV=testing +APP_KEY=base64:KXriJ44+bmjwlMw6u8kD2cvxoNssPsfu+E0oMb/DcQE= +APP_DEBUG=true +APP_URL=http://localhost + +LOG_CHANNEL=stack + +DB_CONNECTION=mysql +DB_HOST=db +DB_PORT=3306 +DB_DATABASE=code_micro_videos +DB_USERNAME=root +DB_PASSWORD=root + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +QUEUE_CONNECTION=sync +SESSION_DRIVER=file +SESSION_LIFETIME=120 + +REDIS_HOST=redis +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_DRIVER=smtp +MAIL_HOST=smtp.mailtrap.io +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= + +PUSHER_APP_ID= +PUSHER_APP_KEY= +PUSHER_APP_SECRET= +PUSHER_APP_CLUSTER=mt1 + +MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 000000000..811c5b6f8 --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,23 @@ +FROM php:7.3.6-fpm-alpine3.9 + +RUN apk add --no-cache shadow openssl bash mysql-client nodejs npm git +RUN docker-php-ext-install pdo pdo_mysql + +RUN touch /home/www-data/.bashrc | echo "PS1='\w\$ '" >> /home/www-data/.bashrc + +ENV DOCKERIZE_VERSION v0.6.1 +RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ + && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ + && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +RUN usermod -u 1000 www-data + +WORKDIR /var/www + +RUN rm -rf /var/www/html && ln -s public html + +USER www-data + +EXPOSE 9000 diff --git a/app/Http/Controllers/Api/BasicCrudController.php b/app/Http/Controllers/Api/BasicCrudController.php new file mode 100644 index 000000000..97427f1c0 --- /dev/null +++ b/app/Http/Controllers/Api/BasicCrudController.php @@ -0,0 +1,63 @@ +model()::all(); + } + + public function store(Request $request) + { + $validatedData = $this->validate($request, $this->rulesStore()); + $obj = $this->model()::create($validatedData); + $obj->refresh(); + return $obj; + } + + protected function findOrFail(string $id) + { + $model = $this->model(); + $keyName = (new $model())->getRouteKeyName(); + return $this->model()::where($keyName, $id)->firstOrFail(); + } + + public function show($id) + { + return $this->findOrFail($id); + } + + public function update(Request $request, $id) + { + $this->validate($request, $this->rulesStore()); + $record = $this->findOrFail($id); + $record->update($request->all()); + $record->refresh(); + return $record; + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Category $category + * @return \Illuminate\Http\Response + */ + public function destroy($id) + { + $record = $this->findOrFail($id); + $record->delete(); + return response()->noContent(); + } +} diff --git a/app/Http/Controllers/Api/CastMemberController.php b/app/Http/Controllers/Api/CastMemberController.php new file mode 100644 index 000000000..07467836a --- /dev/null +++ b/app/Http/Controllers/Api/CastMemberController.php @@ -0,0 +1,23 @@ + 'required|max:255', + 'type' => 'numeric|min:1|max:2', + 'is_active' => 'boolean', + ]; + } +} diff --git a/app/Http/Controllers/Api/CategoryController.php b/app/Http/Controllers/Api/CategoryController.php index ef6b20076..0ab191579 100644 --- a/app/Http/Controllers/Api/CategoryController.php +++ b/app/Http/Controllers/Api/CategoryController.php @@ -2,75 +2,22 @@ namespace App\Http\Controllers\Api; +use App\Http\Controllers\Api\BasicCrudController; use App\Models\Category; -use Illuminate\Http\Request; -use App\Http\Controllers\Controller; -class CategoryController extends Controller +class CategoryController extends BasicCrudController { - private $rules = [ - 'name' => 'required|max:255', - 'is_active' => 'boolean', - ]; - /** - * Display a listing of the resource. - * - * @return \Illuminate\Http\Response - */ - public function index() + protected function model(): string { - return Category::all(); + return Category::class; } - /** - * Store a newly created resource in storage. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function store(Request $request) + protected function rulesStore(): array { - $this->validate($request, $this->rules); - $category = Category::create($request->all()); - $category->refresh(); - return $category; - } - - /** - * Display the specified resource. - * - * @param \App\Models\Category $category - * @return \Illuminate\Http\Response - */ - public function show(Category $category) - { - return $category; - } - - /** - * Update the specified resource in storage. - * - * @param \Illuminate\Http\Request $request - * @param \App\Models\Category $category - * @return \Illuminate\Http\Response - */ - public function update(Request $request, Category $category) - { - $this->validate($request, $this->rules); - $category->update($request->all()); - $category->refresh(); - return $category; - } - - /** - * Remove the specified resource from storage. - * - * @param \App\Models\Category $category - * @return \Illuminate\Http\Response - */ - public function destroy(Category $category) - { - $category->delete(); - return response()->noContent(); + return [ + 'name' => 'required|max:255', + 'description' => 'nullable', + 'is_active' => 'boolean', + ]; } } diff --git a/app/Http/Controllers/Api/GenreController.php b/app/Http/Controllers/Api/GenreController.php index 43d682604..3891d108b 100644 --- a/app/Http/Controllers/Api/GenreController.php +++ b/app/Http/Controllers/Api/GenreController.php @@ -3,74 +3,20 @@ namespace App\Http\Controllers\Api; use App\Models\Genre; -use Illuminate\Http\Request; -use App\Http\Controllers\Controller; +use App\Http\Controllers\Api\BasicCrudController; -class GenreController extends Controller +class GenreController extends BasicCrudController { - private $rules = [ - 'name' => 'required|max:255', - 'is_active' => 'boolean', - ]; - /** - * Display a listing of the resource. - * - * @return \Illuminate\Http\Response - */ - public function index() + protected function model(): string { - return Genre::all(); + return Genre::class; } - /** - * Store a newly created resource in storage. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function store(Request $request) + protected function rulesStore(): array { - $this->validate($request, $this->rules); - $genre = Genre::create($request->all()); - $genre->refresh(); - return $genre; + return [ + 'name' => 'required|max:255', + 'is_active' => 'boolean', + ]; } - - /** - * Display the specified resource. - * - * @param \App\Models\Genre $genre - * @return \Illuminate\Http\Response - */ - public function show(Genre $genre) - { - return $genre; - } - - /** - * Update the specified resource in storage. - * - * @param \Illuminate\Http\Request $request - * @param \App\Models\Genre $genre - * @return \Illuminate\Http\Response - */ - public function update(Request $request, Genre $genre) - { - $this->validate($request, $this->rules); - $genre->update($request->all()); - $genre->refresh(); - return $genre; - } - - /** - * Remove the specified resource from storage. - * - * @param \App\Models\Genre $genre - * @return \Illuminate\Http\Response - */ - public function destroy(Genre $genre) - { - $genre->delete(); - return response()->noContent(); - } -} +} \ No newline at end of file diff --git a/app/Models/CastMember.php b/app/Models/CastMember.php new file mode 100644 index 000000000..b8b7b18c6 --- /dev/null +++ b/app/Models/CastMember.php @@ -0,0 +1,16 @@ + 'boolean']; +} diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 000000000..ad48d1a09 --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,33 @@ +steps: + +- id: "rodando docker-compose" + name: 'docker/compose' + args: ['-f', 'docker-compose.prod.yaml', 'up', '-d'] + +- id: "chown in /var/www" + name: 'docker' + args: ['exec','-u','root','-t','app','chown','-R','www-data:www-data','/var/www'] + +- id: "instalando com composer" + name: 'docker' + args: ['exec','-t','app','composer', 'install'] + +- id: "copiando .env" + name: 'docker' + args: ['exec','-t','app','cp', '.env.example', '.env'] + +- id: "copiando .env.testing" + name: 'docker' + args: ['exec','-t','app','cp', '.env.example.testing', '.env.testing'] + +- id: "gerando chaves Laravel" + name: 'docker' + args: ['exec','-t','app','php', '/var/www/artisan', 'key:generate'] + +- id: "rodando migrations" + name: 'docker' + args: ['exec','-t','app','php', '/var/www/artisan', 'migrate'] + +- id: "rodando PHP unit" + name: 'docker' + args: ['exec','-t','app','php', '/var/www/vendor/bin/phpunit', '-c', '/var/www/phpunit.xml'] diff --git a/database/factories/CastMemberFactory.php b/database/factories/CastMemberFactory.php new file mode 100644 index 000000000..44973456f --- /dev/null +++ b/database/factories/CastMemberFactory.php @@ -0,0 +1,13 @@ +define(CastMember::class, function (Faker $faker) { + return [ + 'name' => $faker->firstName() . ' ' . $faker->lastName, + 'type' => rand(1, 2), + ]; +}); diff --git a/database/migrations/2021_12_09_194740_create_cast_members_table.php b/database/migrations/2021_12_09_194740_create_cast_members_table.php new file mode 100644 index 000000000..2e5654dc2 --- /dev/null +++ b/database/migrations/2021_12_09_194740_create_cast_members_table.php @@ -0,0 +1,26 @@ +uuid('id')->primary(); + $table->string('name'); + $table->smallInteger('type')->min(1)->max(2); + $table->boolean('is_active')->default(true); + $table->softDeletes(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('cast_members'); + } +} diff --git a/database/seeds/CastMemberSeeder.php b/database/seeds/CastMemberSeeder.php new file mode 100644 index 000000000..f7cbcb183 --- /dev/null +++ b/database/seeds/CastMemberSeeder.php @@ -0,0 +1,16 @@ +create(); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 67440e54e..cacb53c6d 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -14,5 +14,6 @@ public function run() // $this->call(UsersTableSeeder::class); $this->call(CategorySeeder::class); $this->call(GenreSeeder::class); + $this->call(CastMemberSeeder::class); } } diff --git a/docker-compose.prod.yaml b/docker-compose.prod.yaml new file mode 100644 index 000000000..9a5e54da8 --- /dev/null +++ b/docker-compose.prod.yaml @@ -0,0 +1,57 @@ +version: '3' + +services: + + app: + build: . + container_name: micro-videos-app + entrypoint: dockerize -wait tcp://db:3306 -timeout 40s ./.docker/entrypoint.sh + volumes: + - .:/var/www + networks: + - app-network + depends_on: + - db + - redis + + nginx: + build: .docker/nginx + container_name: micro-videos-nginx + restart: always + tty: true + ports: + - "8000:80" + volumes: + - .:/var/www + networks: + - app-network + depends_on: + - app + + db: + build: ./.docker/mysql + container_name: micro-videos-db + restart: always + tty: true + ports: + - "33006:3306" + volumes: + - ./.docker/dbdata:/var/lib/mysql + - ./.docker/mysql:/docker-entrypoint-initdb.d + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_ROOT=root + networks: + - app-network + + redis: + image: redis:alpine + container_name: micro-videos-redis + expose: + - 6379 + networks: + - app-network + +networks: + app-network: + driver: bridge diff --git a/routes/api.php b/routes/api.php index 93ac978ee..a5d340d17 100644 --- a/routes/api.php +++ b/routes/api.php @@ -21,4 +21,5 @@ $exceptCreateAndEdit = ['except' => ['create', 'edit']]; Route::resource('categories', 'CategoryController', $exceptCreateAndEdit); Route::resource('genres', 'GenreController', $exceptCreateAndEdit); + Route::resource('cast_members', 'CastMemberController', $exceptCreateAndEdit); }); \ No newline at end of file diff --git a/tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php b/tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php new file mode 100644 index 000000000..f76d3f776 --- /dev/null +++ b/tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php @@ -0,0 +1,77 @@ +controller = new CategoryControllerStubs(); + } + + protected function tearDown(): void + { + CategoryStub::dropTable(); + parent::tearDown(); + } + + public function testIndex() + { + $category = CategoryStub::create(['name' => 'test name', 'description' => 'description test']); + $result = $this->controller->index()->toArray(); + $this->assertEquals([$category->toArray()], $result); + } + + + public function testInvalidationDataSore() + { + $this->expectException(ValidationException::class); + $request = \Mockery::mock(Request::class); + /** @var Request $request */ + $request->shouldReceive('all')->once()->andReturn(['name' => '']); + $this->controller->store($request); + } + + public function testStore() { + $this->expectException(ValidationException::class); + $request = \Mockery::mock(Request::class); + /** @var Request $request */ + $request->shouldReceive('all')->once()->andReturn(['name' => '']); + $obj = $this->controller->store($request); + $this->assertEquals(CategoryStub::find(1)->toArray(), $obj->toArray()); + } + + public function testIfFindOrFailFetchsRecord() + { + $category = CategoryStub::create(['name' => 'test name', 'description' => 'description test']); + $reflectionClass = new ReflectionClass(BasicCrudController::class); + $reflectionMethod = $reflectionClass->getMethod('findOrFail'); + $reflectionMethod->setAccessible(true); + $result = $reflectionMethod->invokeArgs($this->controller, [$category->id]); + $this->assertInstanceOf(CategoryStub::class, $result); + } + + public function testIfFindOrFailThrowsExceptionWhenInvalid() + { + $this->expectException(ModelNotFoundException::class); + $reflectionClass = new ReflectionClass(BasicCrudController::class); + $reflectionMethod = $reflectionClass->getMethod('findOrFail'); + $reflectionMethod->setAccessible(true); + $result = $reflectionMethod->invokeArgs($this->controller, [0]); + } +} + diff --git a/tests/Feature/Http/Controllers/Api/CastMemberTest.php b/tests/Feature/Http/Controllers/Api/CastMemberTest.php new file mode 100644 index 000000000..fd8c1e9b7 --- /dev/null +++ b/tests/Feature/Http/Controllers/Api/CastMemberTest.php @@ -0,0 +1,85 @@ +castMember = factory(CastMember::class)->create(); + } + + public function testIndex() + { + $response = $this->get(route('cast_members.index')); + $response->assertStatus(200)->assertJson([$this->castMember->toArray()]); + } + + public function testShow() + { + $response = $this->get(route('cast_members.show', ['cast_member' => $this->castMember->id])); + $response->assertStatus(200)->assertJson($this->castMember->toArray()); + } + + + public function testInvalidationDataPost() + { + $this->assertInvalidationInStore(['name' => ''], 'required'); + $this->assertInvalidationInStore(['type' => 0], 'min.numeric', ['min' => 1]); + $this->assertInvalidationInStore(['type' => 3], 'max.numeric', ['max' => 2]); + $this->assertInvalidationInStore(['name' => str_repeat('a', 256)], 'max.string', ['max' => 255]); + $this->assertInvalidationInStore(['is_active' => 'true'], 'boolean'); + } + + public function testInvalidationDataPut() + { + $this->assertInvalidationInUpdate(['name' => ''], 'required'); + $this->assertInvalidationInUpdate(['type' => 0], 'min.numeric', ['min' => 1]); + $this->assertInvalidationInUpdate(['type' => 3], 'max.numeric', ['max' => 2]); + $this->assertInvalidationInUpdate(['name' => str_repeat('a', 256)], 'max.string', ['max' => 255]); + $this->assertInvalidationInUpdate(['is_active' => 'true'], 'boolean'); + } + + public function testDelete() + { + $response = $this->json('DELETE', route('cast_members.destroy', ['cast_member' => $this->castMember->id])); + $response->assertStatus(204); + $this->assertNull(CastMember::find($this->castMember->id)); + $this->assertNotNull(CastMember::withTrashed()->find($this->castMember->id)); + } + + protected function assertInvalidationRequired(TestResponse $response) + { + $this->AssertInvalidationFields($response, ['name', 'type'], 'required', []); + $response->assertJsonMissingValidationErrors(['is_active']); + } + + protected function routeStore(): string + { + return route('cast_members.store'); + } + + protected function routeUpdate(): string + { + return route('cast_members.update', ['cast_member' => $this->castMember->id]); + } + + protected function model(): string + { + return CastMember::class; + } + +} diff --git a/tests/Feature/Http/Controllers/Api/CategoryControllerTest.php b/tests/Feature/Http/Controllers/Api/CategoryControllerTest.php index f8a1d30c8..edcafaa82 100644 --- a/tests/Feature/Http/Controllers/Api/CategoryControllerTest.php +++ b/tests/Feature/Http/Controllers/Api/CategoryControllerTest.php @@ -6,144 +6,112 @@ use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\TestResponse; use Tests\TestCase; +use Tests\Traits\TestSaves; +use Tests\Traits\TestValidations; class CategoryControllerTest extends TestCase { - use DatabaseMigrations; + use DatabaseMigrations, TestValidations, TestSaves; + + private $category; + + protected function setUp(): void + { + parent::setUp(); + $this->category = factory(Category::class)->create(); + } public function testIndex() { - $category = factory(Category::class)->create(); $response = $this->get(route('categories.index')); - - $response->assertStatus(200)->assertJson([$category->toArray()]); + $response->assertStatus(200)->assertJson([$this->category->toArray()]); } public function testShow() { - $category = factory(Category::class)->create(); - $response = $this->get(route('categories.show', ['category' => $category->id])); - - $response->assertStatus(200)->assertJson($category->toArray()); + $response = $this->get(route('categories.show', ['category' => $this->category->id])); + $response->assertStatus(200)->assertJson($this->category->toArray()); } public function testInvalidationDataPost() { - $response = $this->json('POST', route('categories.store'), []); - $this->assertInvalidationRequired($response); - $response = $this->json('POST', route('categories.store'), [ - 'name' => str_repeat('a', 256), - 'is_active' => 'true', - ]); - $this->assertInvalidationName($response); - $this->assertInvalidationIsActive($response); + $this->assertInvalidationInStore(['name' => ''], 'required'); + $this->assertInvalidationInStore(['name' => str_repeat('a', 256)], 'max.string', ['max' => 255]); + $this->assertInvalidationInStore(['is_active' => 'true'], 'boolean'); } public function testInvalidationDataPut() { - $category = factory(Category::class)->create(); - $response = $this->json('PUT', route('categories.update', ['category' => $category->id]), []); - $this->assertInvalidationRequired($response); - $response = $this->json('PUT', route('categories.update', ['category' => $category->id]), [ - 'name' => str_repeat('a', 256), - 'is_active' => 'true', - ]); - $this->assertInvalidationName($response); - $this->assertInvalidationIsActive($response); + $this->assertInvalidationInUpdate(['name' => ''], 'required'); + $this->assertInvalidationInUpdate(['name' => str_repeat('a', 256)], 'max.string', ['max' => 255]); + $this->assertInvalidationInUpdate(['is_active' => 'true'], 'boolean'); } public function testStore() { $values = [ 'name' => 'test1', ]; - $response = $this->json('POST', route('categories.store'), $values); - $id = $response->json('id'); - $category = Category::find($id); - $response->assertStatus(201) - ->assertJson($category->toArray()); - $this->assertTrue($response->json('is_active')); - $this->assertNull($response->json('description')); - $this->assertEquals($values['name'], $response->json('name')); - $newValues = [ + $response = $this->assertStore($values, $values + ['description' => null, 'is_active' => true, 'deleted_at' => null]); + $response->assertJsonStructure(['created_at', 'updated_at']); + $values = [ 'name' => 'test2', 'description' => 'description 1', 'is_active' => false, ]; - $response = $this->json('POST', route('categories.store'), $newValues); - $id = $response->json('id'); - $category = Category::find($id); - $response->assertStatus(201) - ->assertJson($category->toArray()); - $this->assertFalse($response->json('is_active')); - $this->assertEquals($newValues['description'], $response->json('description')); - $this->assertEquals($newValues['name'], $response->json('name')); + $this->assertStore($values, $values + ['deleted_at' => null]); + } public function testUpdate() { - $category = factory(Category::class)->create([ - 'is_active' => false, - 'description' => 'description 1', - ]); $newValues = [ 'name' => 'test 1', 'description' => 'description 2', 'is_active' => true, ]; - $response = $this->json('PUT', route('categories.update', ['category' => $category->id]), $newValues); - $category = Category::find($category->id); - $response->assertStatus(200) - ->assertJson($category->toArray()); - $this->assertTrue($response->json('is_active')); - $this->assertEquals($newValues['description'], $response->json('description')); - $this->assertEquals($newValues['name'], $response->json('name')); - - $response = $this->json('PUT', route('categories.update', ['category' => $category->id]), [ - 'description' => '', - 'name' => $newValues['name'], - ]); - $response->assertStatus(200)->assertJsonFragment(['description' => null]); - - $category->description = 'test description'; - $category->save(); - $response = $this->json('PUT', route('categories.update', ['category' => $category->id]), [ + $response = $this->assertUpdate($newValues, $newValues); + $response->assertJsonStructure(['created_at', 'updated_at']); + $newValues = [ + 'name' => 'test 1', + 'description' => null, + ]; + $response = $this->assertUpdate($newValues, $newValues); + + $this->category->description = 'test description'; + $this->category->save(); + $newValues = [ + 'name' => 'test description', 'description' => null, - 'name' => $newValues['name'], - ]); - $response->assertStatus(200)->assertJsonFragment(['description' => null]); + ]; + $response = $this->assertUpdate($newValues, $newValues); } public function testDelete() { - $category = factory(Category::class)->create(); - $this->json('DELETE', route('categories.destroy', ['category' => $category->id])); - $category = Category::find($category->id); - $this->assertNull($category); + $response = $this->json('DELETE', route('categories.destroy', ['category' => $this->category->id])); + $response->assertStatus(204); + $this->assertNull(Category::find($this->category->id)); + $this->assertNotNull(Category::withTrashed()->find($this->category->id)); } protected function assertInvalidationRequired(TestResponse $response) { - $response->assertStatus(422) - ->assertJsonValidationErrors(['name']) - ->assertJsonMissingValidationErrors(['is_active']) - ->assertJsonFragment( - [\Lang::get('validation.required', ['attribute' => 'name'])] - ); + $this->AssertInvalidationFields($response, ['name'], 'required', []); + $response->assertJsonMissingValidationErrors(['is_active']); } - protected function assertInvalidationName(TestResponse $response) { - $response->assertStatus(422) - ->assertJsonValidationErrors(['name']) - ->assertJsonFragment( - [\Lang::get('validation.max.string', ['attribute' => 'name', 'max' => 255])] - ); + protected function routeStore(): string + { + return route('categories.store'); } - protected function assertInvalidationIsActive(TestResponse $response) { - $response->assertStatus(422) - ->assertJsonValidationErrors(['is_active']) - ->assertJsonFragment( - [\Lang::get('validation.boolean', ['attribute' => 'is active'])] - ); + protected function routeUpdate(): string + { + return route('categories.update', ['category' => $this->category->id]); + } + + protected function model(): string + { + return Category::class; } } diff --git a/tests/Feature/Http/Controllers/Api/GenreControllerTest.php b/tests/Feature/Http/Controllers/Api/GenreControllerTest.php index a9f81f82c..c84d773c8 100644 --- a/tests/Feature/Http/Controllers/Api/GenreControllerTest.php +++ b/tests/Feature/Http/Controllers/Api/GenreControllerTest.php @@ -96,9 +96,10 @@ public function testUpdate() public function testDelete() { $genre = factory(Genre::class)->create(); - $this->json('DELETE', route('genres.destroy', ['genre' => $genre->id])); - $genre = Genre::find($genre->id); - $this->assertNull($genre); + $response = $this->json('DELETE', route('genres.destroy', ['genre' => $genre->id])); + $response->assertStatus(204); + $this->assertNull(Genre::find($genre->id)); + $this->assertNotNull(Genre::withTrashed()->find($genre->id)); } protected function assertInvalidationRequired(TestResponse $response) diff --git a/tests/Stubs/Controllers/CategoryControllerStubs.php b/tests/Stubs/Controllers/CategoryControllerStubs.php new file mode 100644 index 000000000..a19c8212c --- /dev/null +++ b/tests/Stubs/Controllers/CategoryControllerStubs.php @@ -0,0 +1,22 @@ + 'required|max:255', + 'description' => 'nullable', + ]; + } +} diff --git a/tests/Stubs/Models/CategoryStub.php b/tests/Stubs/Models/CategoryStub.php new file mode 100644 index 000000000..e728d3bb5 --- /dev/null +++ b/tests/Stubs/Models/CategoryStub.php @@ -0,0 +1,28 @@ +bigIncrements('id'); + $table->string('name'); + $table->text('description')->nullable(); + $table->timestamps(); + }); + } + + public static function dropTable() + { + Schema::dropIfExists('category_stubs'); + } +} diff --git a/tests/Traits/TestSaves.php b/tests/Traits/TestSaves.php new file mode 100644 index 000000000..10f6d7966 --- /dev/null +++ b/tests/Traits/TestSaves.php @@ -0,0 +1,41 @@ +json('POST', $this->routeStore(), $sendData); + if ($response->status() !== 201) { + throw new \Exception("Response status must be 201, found: {$response->status()}:\n{$response->content()}"); + } + $this->assertData($response, $testDatabase, $testJson); + return $response; + } + + protected function assertUpdate(array $sendData, array $testDatabase, array $testJson = null): TestResponse + { + $response = $this->json('PUT', $this->routeUpdate(), $sendData); + if ($response->status() !== 200) { + throw new \Exception("Response status must be 200, found: {$response->status()}:\n{$response->content()}"); + } + $this->assertData($response, $testDatabase, $testJson); + return $response; + } + + private function assertData(TestResponse $response, array $testDatabase, array $testJson = null) + { + $model = $this->model(); + $table = (new $model)->getTable(); + $this->assertDatabaseHas($table, $testDatabase + ['id' => $response->json('id')]); + $testResponse = $testJson ?? $testDatabase; + $response->assertJsonFragment($testResponse + ['id' => $response->json('id')]); + } +} \ No newline at end of file diff --git a/tests/Traits/TestValidations.php b/tests/Traits/TestValidations.php new file mode 100644 index 000000000..21383d4b3 --- /dev/null +++ b/tests/Traits/TestValidations.php @@ -0,0 +1,47 @@ +json('POST', $this->routeStore(), $data); + $fields = array_keys($data); + $this->AssertInvalidationFields($response, $fields, $rule, $ruleParams); + } + + protected function assertInvalidationInUpdate( + array $data, + string $rule, + array $ruleParams = [] + ) { + $response = $this->json('PUT', $this->routeUpdate(), $data); + $fields = array_keys($data); + $this->AssertInvalidationFields($response, $fields, $rule, $ruleParams); + } + + protected function AssertInvalidationFields( + TestResponse $response, + array $fields, + string $rule, + array $ruleParams = [] + ) { + $response->assertStatus(422) + ->assertJsonValidationErrors($fields); + foreach($fields as $fieldName) { + $fieldName = str_replace('_', ' ', $fieldName); + $response->assertJsonFragment([ + \Lang::get("validation.{$rule}", ['attribute' => $fieldName] + $ruleParams) + ]); + } + } +} \ No newline at end of file diff --git a/tests/Unit/Models/GenreTes.php b/tests/Unit/Models/GenreTest.php similarity index 100% rename from tests/Unit/Models/GenreTes.php rename to tests/Unit/Models/GenreTest.php From caec38417e37538a1e8628e70f353e2130d217ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sat, 11 Dec 2021 17:24:44 -0300 Subject: [PATCH 05/18] Tentativa de resolver erro Erro a ser contornado: Step #1 - "chown in /var/www": Error: No such container: app --- cloudbuild.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index ad48d1a09..df75dda90 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -6,28 +6,28 @@ steps: - id: "chown in /var/www" name: 'docker' - args: ['exec','-u','root','-t','app','chown','-R','www-data:www-data','/var/www'] + args: ['exec','-u','root','-t','micro-videos-app','chown','-R','www-data:www-data','/var/www'] - id: "instalando com composer" name: 'docker' - args: ['exec','-t','app','composer', 'install'] + args: ['exec','-t','micro-videos-app','composer', 'install'] - id: "copiando .env" name: 'docker' - args: ['exec','-t','app','cp', '.env.example', '.env'] + args: ['exec','-t','micro-videos-app','cp', '.env.example', '.env'] - id: "copiando .env.testing" name: 'docker' - args: ['exec','-t','app','cp', '.env.example.testing', '.env.testing'] + args: ['exec','-t','micro-videos-app','cp', '.env.example.testing', '.env.testing'] - id: "gerando chaves Laravel" name: 'docker' - args: ['exec','-t','app','php', '/var/www/artisan', 'key:generate'] + args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'key:generate'] - id: "rodando migrations" name: 'docker' - args: ['exec','-t','app','php', '/var/www/artisan', 'migrate'] + args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'migrate'] - id: "rodando PHP unit" name: 'docker' - args: ['exec','-t','app','php', '/var/www/vendor/bin/phpunit', '-c', '/var/www/phpunit.xml'] + args: ['exec','-t','micro-videos-app','php', '/var/www/vendor/bin/phpunit', '-c', '/var/www/phpunit.xml'] From d70a7ff6f265bf640cdd70be81e77e6ff37c3eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sat, 11 Dec 2021 17:44:47 -0300 Subject: [PATCH 06/18] =?UTF-8?q?Corre=C3=A7=C3=A3o=20em=20nome=20de=20arq?= =?UTF-8?q?uivo=20e=20adi=C3=A7=C3=A3o=20de=20vari=C3=A1vel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .docker/entrypoint.sh | 2 +- docker-compose.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.docker/entrypoint.sh b/.docker/entrypoint.sh index 09634e5c4..23c73e6bb 100755 --- a/.docker/entrypoint.sh +++ b/.docker/entrypoint.sh @@ -2,7 +2,7 @@ #On error no such file entrypoint.sh, execute in terminal - dos2unix .docker\entrypoint.sh cp .env.example .env -cp .env.example.testing .env.testing +cp .env.testing.example .env.testing chown -R www-data:www-data . composer install php artisan key:generate diff --git a/docker-compose.yaml b/docker-compose.yaml index 2839a97f8..d39469027 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -46,6 +46,7 @@ services: - ./.docker/mysql:/docker-entrypoint-initdb.d environment: - MYSQL_ROOT_PASSWORD=root + - MYSQL_ROOT=root networks: - app-network From 9a3ba07a7959414cf7490dfccb930e9e5529552a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sat, 11 Dec 2021 17:55:32 -0300 Subject: [PATCH 07/18] =?UTF-8?q?Tentativa=20de=20confirmar=20se=20=C3=A9?= =?UTF-8?q?=20redundante=20com=20entrypoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cloudbuild.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index df75dda90..b0666dd56 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -16,17 +16,17 @@ steps: name: 'docker' args: ['exec','-t','micro-videos-app','cp', '.env.example', '.env'] -- id: "copiando .env.testing" - name: 'docker' - args: ['exec','-t','micro-videos-app','cp', '.env.example.testing', '.env.testing'] +# - id: "copiando .env.testing" +# name: 'docker' +# args: ['exec','-t','micro-videos-app','cp', '.env.example.testing', '.env.testing'] -- id: "gerando chaves Laravel" - name: 'docker' - args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'key:generate'] +# - id: "gerando chaves Laravel" +# name: 'docker' +# args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'key:generate'] -- id: "rodando migrations" - name: 'docker' - args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'migrate'] +# - id: "rodando migrations" +# name: 'docker' +# args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'migrate'] - id: "rodando PHP unit" name: 'docker' From ce8f7830413be2ee2c282dce58c5d34556a9235a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sat, 11 Dec 2021 18:04:44 -0300 Subject: [PATCH 08/18] =?UTF-8?q?Retorno=20de=20passos=20com=20corre=C3=A7?= =?UTF-8?q?=C3=A3o=20em=20nome=20de=20arquivo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ cloudbuild.yaml | 18 +++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 0faa0260e..7ef96efb2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ /vendor .env .env.testing +.env.example +.env.testing.example .env.backup .phpunit.result.cache Homestead.json diff --git a/cloudbuild.yaml b/cloudbuild.yaml index b0666dd56..58ae14b77 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -16,17 +16,17 @@ steps: name: 'docker' args: ['exec','-t','micro-videos-app','cp', '.env.example', '.env'] -# - id: "copiando .env.testing" -# name: 'docker' -# args: ['exec','-t','micro-videos-app','cp', '.env.example.testing', '.env.testing'] +- id: "copiando .env.testing" + name: 'docker' + args: ['exec','-t','micro-videos-app','cp', '.env.testing.example', '.env.testing'] -# - id: "gerando chaves Laravel" -# name: 'docker' -# args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'key:generate'] +- id: "gerando chaves Laravel" + name: 'docker' + args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'key:generate'] -# - id: "rodando migrations" -# name: 'docker' -# args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'migrate'] +- id: "rodando migrations" + name: 'docker' + args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'migrate'] - id: "rodando PHP unit" name: 'docker' From 25bf4fa508b75d6708d8da6d9099a66fdfa65c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sat, 11 Dec 2021 18:11:17 -0300 Subject: [PATCH 09/18] =?UTF-8?q?Corre=C3=A7=C3=A3o=20no=20nome=20do=20ban?= =?UTF-8?q?co=20de=20dados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 20558db27..9f6b72583 100644 --- a/.env.example +++ b/.env.example @@ -9,7 +9,7 @@ LOG_CHANNEL=stack DB_CONNECTION=mysql DB_HOST=db DB_PORT=3306 -DB_DATABASE=code-micro-videos +DB_DATABASE=code_micro_videos DB_USERNAME=root DB_PASSWORD=root From e855cb1fa97b75822dddebe7c82353bdf9a94a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sat, 11 Dec 2021 18:16:45 -0300 Subject: [PATCH 10/18] Outro teste com entrypoint --- cloudbuild.yaml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 58ae14b77..9fcbaaa53 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -4,29 +4,29 @@ steps: name: 'docker/compose' args: ['-f', 'docker-compose.prod.yaml', 'up', '-d'] -- id: "chown in /var/www" - name: 'docker' - args: ['exec','-u','root','-t','micro-videos-app','chown','-R','www-data:www-data','/var/www'] +# - id: "chown in /var/www" +# name: 'docker' +# args: ['exec','-u','root','-t','micro-videos-app','chown','-R','www-data:www-data','/var/www'] -- id: "instalando com composer" - name: 'docker' - args: ['exec','-t','micro-videos-app','composer', 'install'] +# - id: "instalando com composer" +# name: 'docker' +# args: ['exec','-t','micro-videos-app','composer', 'install'] -- id: "copiando .env" - name: 'docker' - args: ['exec','-t','micro-videos-app','cp', '.env.example', '.env'] +# - id: "copiando .env" +# name: 'docker' +# args: ['exec','-t','micro-videos-app','cp', '.env.example', '.env'] -- id: "copiando .env.testing" - name: 'docker' - args: ['exec','-t','micro-videos-app','cp', '.env.testing.example', '.env.testing'] +# - id: "copiando .env.testing" +# name: 'docker' +# args: ['exec','-t','micro-videos-app','cp', '.env.testing.example', '.env.testing'] -- id: "gerando chaves Laravel" - name: 'docker' - args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'key:generate'] +# - id: "gerando chaves Laravel" +# name: 'docker' +# args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'key:generate'] -- id: "rodando migrations" - name: 'docker' - args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'migrate'] +# - id: "rodando migrations" +# name: 'docker' +# args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'migrate'] - id: "rodando PHP unit" name: 'docker' From d82667c20b9027e40cfa301ac93d5f43002a35fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sat, 11 Dec 2021 18:20:23 -0300 Subject: [PATCH 11/18] Retirando .envs do gitignore --- .docker/app/.env.testing | 44 ++++++++++++++++++++++++++++++++++++++++ .env | 44 ++++++++++++++++++++++++++++++++++++++++ .env.testing | 44 ++++++++++++++++++++++++++++++++++++++++ .gitignore | 4 ---- cloudbuild.yaml | 36 ++++++++++++++++---------------- 5 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 .docker/app/.env.testing create mode 100644 .env create mode 100644 .env.testing diff --git a/.docker/app/.env.testing b/.docker/app/.env.testing new file mode 100644 index 000000000..3a874f24f --- /dev/null +++ b/.docker/app/.env.testing @@ -0,0 +1,44 @@ +APP_NAME=Laravel +APP_ENV=testing +APP_KEY=base64:KXriJ44+bmjwlMw6u8kD2cvxoNssPsfu+E0oMb/DcQE= +APP_DEBUG=true +APP_URL=http://localhost + +LOG_CHANNEL=stack + +DB_CONNECTION=mysql +DB_HOST={{ .Env._DB_HOST }} +DB_PORT=3306 +DB_DATABASE={{ .Env._TEST_DB_DATABASE }} +DB_USERNAME={{ .Env._DB_USERNAME }} +DB_PASSWORD={{ .Env._DB_PASSWORD }} + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +QUEUE_CONNECTION=sync +SESSION_DRIVER=file +SESSION_LIFETIME=120 + +REDIS_HOST=redis +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_DRIVER=smtp +MAIL_HOST=smtp.mailtrap.io +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= + +PUSHER_APP_ID= +PUSHER_APP_KEY= +PUSHER_APP_SECRET= +PUSHER_APP_CLUSTER=mt1 + +MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" diff --git a/.env b/.env new file mode 100644 index 000000000..ee1de618a --- /dev/null +++ b/.env @@ -0,0 +1,44 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY=base64:wbuwsRwxPyKfaPvbPb0VG+hFKYKM2icH2pHHTqbY9dQ= +APP_DEBUG=true +APP_URL=http://localhost + +LOG_CHANNEL=stack + +DB_CONNECTION=mysql +DB_HOST=db +DB_PORT=3306 +DB_DATABASE=code_micro_videos +DB_USERNAME=root +DB_PASSWORD=root + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +QUEUE_CONNECTION=sync +SESSION_DRIVER=file +SESSION_LIFETIME=120 + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_DRIVER=smtp +MAIL_HOST=smtp.mailtrap.io +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= + +PUSHER_APP_ID= +PUSHER_APP_KEY= +PUSHER_APP_SECRET= +PUSHER_APP_CLUSTER=mt1 + +MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" diff --git a/.env.testing b/.env.testing new file mode 100644 index 000000000..e2a785e5b --- /dev/null +++ b/.env.testing @@ -0,0 +1,44 @@ +APP_NAME=Laravel +APP_ENV=testing +APP_KEY=base64:KXriJ44+bmjwlMw6u8kD2cvxoNssPsfu+E0oMb/DcQE= +APP_DEBUG=true +APP_URL=http://localhost + +LOG_CHANNEL=stack + +DB_CONNECTION=mysql +DB_HOST=db +DB_PORT=3306 +DB_DATABASE=code_micro_videos +DB_USERNAME=root +DB_PASSWORD=root + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +QUEUE_CONNECTION=sync +SESSION_DRIVER=file +SESSION_LIFETIME=120 + +REDIS_HOST=redis +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_DRIVER=smtp +MAIL_HOST=smtp.mailtrap.io +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= + +PUSHER_APP_ID= +PUSHER_APP_KEY= +PUSHER_APP_SECRET= +PUSHER_APP_CLUSTER=mt1 + +MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" diff --git a/.gitignore b/.gitignore index 7ef96efb2..ca7c41aa4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,6 @@ /public/storage /storage/*.key /vendor -.env -.env.testing -.env.example -.env.testing.example .env.backup .phpunit.result.cache Homestead.json diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 9fcbaaa53..58ae14b77 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -4,29 +4,29 @@ steps: name: 'docker/compose' args: ['-f', 'docker-compose.prod.yaml', 'up', '-d'] -# - id: "chown in /var/www" -# name: 'docker' -# args: ['exec','-u','root','-t','micro-videos-app','chown','-R','www-data:www-data','/var/www'] +- id: "chown in /var/www" + name: 'docker' + args: ['exec','-u','root','-t','micro-videos-app','chown','-R','www-data:www-data','/var/www'] -# - id: "instalando com composer" -# name: 'docker' -# args: ['exec','-t','micro-videos-app','composer', 'install'] +- id: "instalando com composer" + name: 'docker' + args: ['exec','-t','micro-videos-app','composer', 'install'] -# - id: "copiando .env" -# name: 'docker' -# args: ['exec','-t','micro-videos-app','cp', '.env.example', '.env'] +- id: "copiando .env" + name: 'docker' + args: ['exec','-t','micro-videos-app','cp', '.env.example', '.env'] -# - id: "copiando .env.testing" -# name: 'docker' -# args: ['exec','-t','micro-videos-app','cp', '.env.testing.example', '.env.testing'] +- id: "copiando .env.testing" + name: 'docker' + args: ['exec','-t','micro-videos-app','cp', '.env.testing.example', '.env.testing'] -# - id: "gerando chaves Laravel" -# name: 'docker' -# args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'key:generate'] +- id: "gerando chaves Laravel" + name: 'docker' + args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'key:generate'] -# - id: "rodando migrations" -# name: 'docker' -# args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'migrate'] +- id: "rodando migrations" + name: 'docker' + args: ['exec','-t','micro-videos-app','php', '/var/www/artisan', 'migrate'] - id: "rodando PHP unit" name: 'docker' From 9f4e9b896ae27061168025cd9f57d2e005693301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sat, 11 Dec 2021 18:24:57 -0300 Subject: [PATCH 12/18] =?UTF-8?q?For=C3=A7ando=20recria=C3=A7=C3=A3o=20das?= =?UTF-8?q?=20imagens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 58ae14b77..3ea037be5 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -2,7 +2,7 @@ steps: - id: "rodando docker-compose" name: 'docker/compose' - args: ['-f', 'docker-compose.prod.yaml', 'up', '-d'] + args: ['-f', 'docker-compose.prod.yaml', 'up', '--build', '-force-recreate', '-d'] - id: "chown in /var/www" name: 'docker' From af39468568280edabb67c8913540e93bcd50c7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sat, 11 Dec 2021 18:25:59 -0300 Subject: [PATCH 13/18] Update cloudbuild.yaml --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 3ea037be5..90a63b427 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -2,7 +2,7 @@ steps: - id: "rodando docker-compose" name: 'docker/compose' - args: ['-f', 'docker-compose.prod.yaml', 'up', '--build', '-force-recreate', '-d'] + args: ['-f', 'docker-compose.prod.yaml', 'up', '--build', '--force-recreate', '-d'] - id: "chown in /var/www" name: 'docker' From 091fe3f7f4a03b7fd26be2d5928a580e192f3ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Mon, 13 Dec 2021 13:30:32 -0300 Subject: [PATCH 14/18] =?UTF-8?q?corre=C3=A7=C3=B5es=20no=20abstract=20cru?= =?UTF-8?q?d=20e=20testes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- .../Controllers/Api/CastMemberController.php | 2 +- app/Models/CastMember.php | 11 ++ database/factories/CastMemberFactory.php | 2 +- .../Api/BasicCrudControllerTest.php | 24 ++++ .../Http/Controllers/Api/CastMemberTest.php | 9 +- .../Controllers/Api/GenreControllerTest.php | 125 ++++++++---------- tests/Unit/Models/CastMemberUnitTest.php | 55 ++++++++ ...{CategoryTest.php => CategoryUnitTest.php} | 0 .../{GenreTest.php => GenreUnitTest.php} | 0 10 files changed, 151 insertions(+), 79 deletions(-) create mode 100644 tests/Unit/Models/CastMemberUnitTest.php rename tests/Unit/Models/{CategoryTest.php => CategoryUnitTest.php} (100%) rename tests/Unit/Models/{GenreTest.php => GenreUnitTest.php} (100%) diff --git a/.env b/.env index ee1de618a..f931d11f1 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ APP_NAME=Laravel APP_ENV=local -APP_KEY=base64:wbuwsRwxPyKfaPvbPb0VG+hFKYKM2icH2pHHTqbY9dQ= +APP_KEY=base64:C3hmKIv89IGF1v6w9FiuMUIaJh2oqUp7eK1H1YWbI44= APP_DEBUG=true APP_URL=http://localhost diff --git a/app/Http/Controllers/Api/CastMemberController.php b/app/Http/Controllers/Api/CastMemberController.php index 07467836a..ff394ec13 100644 --- a/app/Http/Controllers/Api/CastMemberController.php +++ b/app/Http/Controllers/Api/CastMemberController.php @@ -16,7 +16,7 @@ protected function rulesStore(): array { return [ 'name' => 'required|max:255', - 'type' => 'numeric|min:1|max:2', + 'type' => 'numeric|in:' . implode(',', CastMember::getPossibleMembers()), 'is_active' => 'boolean', ]; } diff --git a/app/Models/CastMember.php b/app/Models/CastMember.php index b8b7b18c6..3e6fba4bf 100644 --- a/app/Models/CastMember.php +++ b/app/Models/CastMember.php @@ -7,10 +7,21 @@ class CastMember extends Model { + const TYPE_DYRECTOR = 1; + const TYPE_MEMBER = 2; + use SoftDeletes, Traits\Uuid; protected $fillable = ['name', 'type', 'is_active']; protected $dates = ['deleted_at']; public $incrementing = false; protected $keyType = 'string'; protected $casts = ['is_active' => 'boolean']; + + public static function getPossibleMembers(): array + { + return [ + self::TYPE_MEMBER, + self::TYPE_DYRECTOR, + ]; + } } diff --git a/database/factories/CastMemberFactory.php b/database/factories/CastMemberFactory.php index 44973456f..8cfa0766a 100644 --- a/database/factories/CastMemberFactory.php +++ b/database/factories/CastMemberFactory.php @@ -8,6 +8,6 @@ $factory->define(CastMember::class, function (Faker $faker) { return [ 'name' => $faker->firstName() . ' ' . $faker->lastName, - 'type' => rand(1, 2), + 'type' => array_rand([CastMember::TYPE_DYRECTOR, CastMember::TYPE_MEMBER]), ]; }); diff --git a/tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php b/tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php index f76d3f776..ee84b820d 100644 --- a/tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php +++ b/tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php @@ -36,6 +36,30 @@ public function testIndex() $this->assertEquals([$category->toArray()], $result); } + public function testShow() + { + $category = CategoryStub::create(['name' => 'test name', 'description' => 'description test']); + $result = $this->controller->show($category->id)->toArray(); + $this->assertEquals($category->toArray(), $result); + } + + public function testUpdate() + { + $category = CategoryStub::create(['name' => 'test name', 'description' => 'description test']); + $request = \Mockery::mock(Request::class); + $request->shouldReceive('all') + ->andReturn(['name' => 'tested name', 'description' => 'description tested']); + $result = $this->controller->update($request, $category->id)->toArray(); + $this->assertEquals($result, CategoryStub::find(1)->first()->toArray()); + } + + public function testDestroy() + { + $category = CategoryStub::create(['name' => 'test name', 'description' => 'description test']); + $response = $this->controller->destroy($category->id); + $this->createTestResponse($response)->assertStatus(204); + $this->assertCount(0, CategoryStub::all()); + } public function testInvalidationDataSore() { diff --git a/tests/Feature/Http/Controllers/Api/CastMemberTest.php b/tests/Feature/Http/Controllers/Api/CastMemberTest.php index fd8c1e9b7..523e393ab 100644 --- a/tests/Feature/Http/Controllers/Api/CastMemberTest.php +++ b/tests/Feature/Http/Controllers/Api/CastMemberTest.php @@ -34,12 +34,11 @@ public function testShow() $response->assertStatus(200)->assertJson($this->castMember->toArray()); } - public function testInvalidationDataPost() { $this->assertInvalidationInStore(['name' => ''], 'required'); - $this->assertInvalidationInStore(['type' => 0], 'min.numeric', ['min' => 1]); - $this->assertInvalidationInStore(['type' => 3], 'max.numeric', ['max' => 2]); + $this->assertInvalidationInStore(['type' => 0], 'in'); + $this->assertInvalidationInStore(['type' => 3], 'in'); $this->assertInvalidationInStore(['name' => str_repeat('a', 256)], 'max.string', ['max' => 255]); $this->assertInvalidationInStore(['is_active' => 'true'], 'boolean'); } @@ -47,8 +46,8 @@ public function testInvalidationDataPost() public function testInvalidationDataPut() { $this->assertInvalidationInUpdate(['name' => ''], 'required'); - $this->assertInvalidationInUpdate(['type' => 0], 'min.numeric', ['min' => 1]); - $this->assertInvalidationInUpdate(['type' => 3], 'max.numeric', ['max' => 2]); + $this->assertInvalidationInUpdate(['type' => 0], 'in'); + $this->assertInvalidationInUpdate(['type' => 3], 'in'); $this->assertInvalidationInUpdate(['name' => str_repeat('a', 256)], 'max.string', ['max' => 255]); $this->assertInvalidationInUpdate(['is_active' => 'true'], 'boolean'); } diff --git a/tests/Feature/Http/Controllers/Api/GenreControllerTest.php b/tests/Feature/Http/Controllers/Api/GenreControllerTest.php index c84d773c8..63089a7ed 100644 --- a/tests/Feature/Http/Controllers/Api/GenreControllerTest.php +++ b/tests/Feature/Http/Controllers/Api/GenreControllerTest.php @@ -6,125 +6,108 @@ use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\TestResponse; use Tests\TestCase; +use Tests\Traits\TestSaves; +use Tests\Traits\TestValidations; class GenreControllerTest extends TestCase { - use DatabaseMigrations; + use DatabaseMigrations, TestValidations, TestSaves; + + private $genre; + + protected function setUp(): void + { + parent::setUp(); + $this->genre = factory(Genre::class)->create(); + } public function testIndex() { - $genre = factory(Genre::class)->create(); $response = $this->get(route('genres.index')); - - $response->assertStatus(200)->assertJson([$genre->toArray()]); + $response->assertStatus(200)->assertJson([$this->genre->toArray()]); } public function testShow() { - $genre = factory(Genre::class)->create(); - $response = $this->get(route('genres.show', ['genre' => $genre->id])); - - $response->assertStatus(200)->assertJson($genre->toArray()); + $response = $this->get(route('genres.show', ['genre' => $this->genre->id])); + $response->assertStatus(200)->assertJson($this->genre->toArray()); } public function testInvalidationDataPost() { - $response = $this->json('POST', route('genres.store'), []); - $this->assertInvalidationRequired($response); - $response = $this->json('POST', route('genres.store'), [ - 'name' => str_repeat('a', 256), - 'is_active' => 'true', - ]); - $this->assertInvalidationName($response); - $this->assertInvalidationIsActive($response); + $this->assertInvalidationInStore(['name' => ''], 'required'); + $this->assertInvalidationInStore(['name' => str_repeat('a', 256)], 'max.string', ['max' => 255]); + $this->assertInvalidationInStore(['is_active' => 'true'], 'boolean'); } public function testInvalidationDataPut() { - $genre = factory(Genre::class)->create(); - $response = $this->json('PUT', route('genres.update', ['genre' => $genre->id]), []); - $this->assertInvalidationRequired($response); - $response = $this->json('PUT', route('genres.update', ['genre' => $genre->id]), [ - 'name' => str_repeat('a', 256), - 'is_active' => 'true', - ]); - $this->assertInvalidationName($response); - $this->assertInvalidationIsActive($response); + $this->assertInvalidationInUpdate(['name' => ''], 'required'); + $this->assertInvalidationInUpdate(['name' => str_repeat('a', 256)], 'max.string', ['max' => 255]); + $this->assertInvalidationInUpdate(['is_active' => 'true'], 'boolean'); } public function testStore() { $values = [ 'name' => 'test1', ]; - $response = $this->json('POST', route('genres.store'), $values); - $id = $response->json('id'); - $genre = Genre::find($id); - $response->assertStatus(201) - ->assertJson($genre->toArray()); - $this->assertTrue($response->json('is_active')); - $this->assertEquals($values['name'], $response->json('name')); - $newValues = [ + $response = $this->assertStore($values, $values + ['is_active' => true, 'deleted_at' => null]); + $response->assertJsonStructure(['created_at', 'updated_at']); + $values = [ 'name' => 'test2', 'is_active' => false, ]; - $response = $this->json('POST', route('genres.store'), $newValues); - $id = $response->json('id'); - $genre = Genre::find($id); - $response->assertStatus(201) - ->assertJson($genre->toArray()); - $this->assertFalse($response->json('is_active')); - $this->assertEquals($newValues['name'], $response->json('name')); + $this->assertStore($values, $values + ['deleted_at' => null]); + } public function testUpdate() { - $genre = factory(Genre::class)->create([ - 'is_active' => false, - ]); $newValues = [ 'name' => 'test 1', 'is_active' => true, ]; - $response = $this->json('PUT', route('genres.update', ['genre' => $genre->id]), $newValues); - $genre = Genre::find($genre->id); - $response->assertStatus(200) - ->assertJson($genre->toArray()); - $this->assertTrue($response->json('is_active')); - $this->assertEquals($newValues['name'], $response->json('name')); + $response = $this->assertUpdate($newValues, $newValues); + $response->assertJsonStructure(['created_at', 'updated_at']); + $newValues = [ + 'name' => 'test 1', + ]; + $response = $this->assertUpdate($newValues, $newValues); + + $this->genre->name = 'test2'; + $this->genre->save(); + $newValues = [ + 'name' => 'test name', + ]; + $response = $this->assertUpdate($newValues, $newValues); } public function testDelete() { - $genre = factory(Genre::class)->create(); - $response = $this->json('DELETE', route('genres.destroy', ['genre' => $genre->id])); + $response = $this->json('DELETE', route('genres.destroy', ['genre' => $this->genre->id])); $response->assertStatus(204); - $this->assertNull(Genre::find($genre->id)); - $this->assertNotNull(Genre::withTrashed()->find($genre->id)); + $this->assertNull(Genre::find($this->genre->id)); + $this->assertNotNull(Genre::withTrashed()->find($this->genre->id)); } protected function assertInvalidationRequired(TestResponse $response) { - $response->assertStatus(422) - ->assertJsonValidationErrors(['name']) - ->assertJsonMissingValidationErrors(['is_active']) - ->assertJsonFragment( - [\Lang::get('validation.required', ['attribute' => 'name'])] - ); + $this->AssertInvalidationFields($response, ['name'], 'required', []); + $response->assertJsonMissingValidationErrors(['is_active']); } - protected function assertInvalidationName(TestResponse $response) { - $response->assertStatus(422) - ->assertJsonValidationErrors(['name']) - ->assertJsonFragment( - [\Lang::get('validation.max.string', ['attribute' => 'name', 'max' => 255])] - ); + protected function routeStore(): string + { + return route('genres.store'); } - protected function assertInvalidationIsActive(TestResponse $response) { - $response->assertStatus(422) - ->assertJsonValidationErrors(['is_active']) - ->assertJsonFragment( - [\Lang::get('validation.boolean', ['attribute' => 'is active'])] - ); + protected function routeUpdate(): string + { + return route('genres.update', ['genre' => $this->genre->id]); + } + + protected function model(): string + { + return Genre::class; } } diff --git a/tests/Unit/Models/CastMemberUnitTest.php b/tests/Unit/Models/CastMemberUnitTest.php new file mode 100644 index 000000000..5c67fe072 --- /dev/null +++ b/tests/Unit/Models/CastMemberUnitTest.php @@ -0,0 +1,55 @@ +castMember = new CastMember(); + } + + protected function tearDown(): void + { + parent::tearDown(); + $this->castMember = new CastMember(); + } + + public function testFillable() + { + $fillable = ['name', 'type', 'is_active']; + $this->assertEquals($this->castMember->getFillable(), $fillable); + } + + public function testUseOfTrais() + { + $traits = [softDeletes::class, uuid::class]; + $castMemberTraits = array_keys(class_uses(CastMember::class)); + $this->assertEqualsCanonicalizing($traits, $castMemberTraits); + } + + public function testCasts() + { + $casts = ['is_active' => 'boolean']; + $this->assertEqualsCanonicalizing($casts, $this->castMember->getCasts()); + } + + public function testDates() + { + $dates = ['created_at', 'deleted_at', 'updated_at']; + $this->assertEqualsCanonicalizing($dates, $this->castMember->getDates()); + } + + public function testNotIncreminting() + { + $this->assertFalse($this->castMember->incrementing); + } +} diff --git a/tests/Unit/Models/CategoryTest.php b/tests/Unit/Models/CategoryUnitTest.php similarity index 100% rename from tests/Unit/Models/CategoryTest.php rename to tests/Unit/Models/CategoryUnitTest.php diff --git a/tests/Unit/Models/GenreTest.php b/tests/Unit/Models/GenreUnitTest.php similarity index 100% rename from tests/Unit/Models/GenreTest.php rename to tests/Unit/Models/GenreUnitTest.php From 7ec7c40d27945ce42ce1e819aef3454deaf80867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Sat, 18 Dec 2021 10:32:33 -0300 Subject: [PATCH 15/18] =?UTF-8?q?adi=C3=A7=C3=A3o=20de=20testes=20de=20cri?= =?UTF-8?q?a=C3=A7=C3=A3o=20e=20atualiza=C3=A7=C3=A3o=20de=20cast=20member?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Feature/Models/CastMemberTest.php | 73 +++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/Feature/Models/CastMemberTest.php diff --git a/tests/Feature/Models/CastMemberTest.php b/tests/Feature/Models/CastMemberTest.php new file mode 100644 index 000000000..a7738f799 --- /dev/null +++ b/tests/Feature/Models/CastMemberTest.php @@ -0,0 +1,73 @@ +create(); + $categories = CastMember::all(); + $fields = array_keys($categories->first()->getAttributes()); + $this->assertCount(1, $categories); + $this->assertEqualsCanonicalizing([ + 'name', + 'type', + 'is_active', + 'created_at', + 'deleted_at', + 'updated_at', + "id", + ], $fields); + } + + public function testCreate() + { + $castMember = CastMember::create([ + 'name' => 'test1', + 'type' => CastMember::TYPE_DYRECTOR, + ]); + $castMember->refresh(); + $this->assertEquals('test1', $castMember->name); + $this->assertEquals(CastMember::TYPE_DYRECTOR, $castMember->type); + $this->assertTrue($castMember->is_active); + $this->assertRegExp('/[a-f0-9]{8}\-[a-f0-9]{4}\-4[a-f0-9]{3}\-(8|9|a|b)[a-f0-9]{3}\-[a-f0-9]{12}/', $castMember->id); + $castMember->refresh(); + } + + public function testUpdate() + { + $castMember = factory(CastMember::class, 1)->create([ + 'name' => 'test name', + 'type' => CastMember::TYPE_MEMBER, + 'is_active' => false, + ])->first(); + + $data = [ + 'name' => 'test update', + 'is_active' => true, + ]; + $castMember->update($data); + + foreach($data as $key => $value) { + $this->assertEquals($value, $castMember->{$key}); + } + } + + public function testDelete() + { + $castMember = factory(CastMember::class, 1)->create()->first(); + $castMember->delete(); + $this->assertNull(CastMember::find($castMember->id)); + } +} From ac13d7cec13cb7d0d4aee0d054099460d12c1ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Tue, 21 Dec 2021 21:47:20 -0300 Subject: [PATCH 16/18] =?UTF-8?q?adicionada=20classe=20para=20v=C3=ADdeos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/VideoController.php | 26 +++++++++++++ app/Models/Video.php | 30 +++++++++++++++ database/factories/VideoFactory.php | 22 +++++++++++ .../2021_12_13_163935_create_videos_table.php | 38 +++++++++++++++++++ database/seeds/DatabaseSeeder.php | 1 + database/seeds/VideoSeeder.php | 16 ++++++++ routes/api.php | 1 + 7 files changed, 134 insertions(+) create mode 100644 app/Http/Controllers/Api/VideoController.php create mode 100644 app/Models/Video.php create mode 100644 database/factories/VideoFactory.php create mode 100644 database/migrations/2021_12_13_163935_create_videos_table.php create mode 100644 database/seeds/VideoSeeder.php diff --git a/app/Http/Controllers/Api/VideoController.php b/app/Http/Controllers/Api/VideoController.php new file mode 100644 index 000000000..98875b98b --- /dev/null +++ b/app/Http/Controllers/Api/VideoController.php @@ -0,0 +1,26 @@ + 'required|max:255', + 'descrption' => 'required', + 'year_launched' => 'required|date_format:Y', + 'opened' => 'boolean', + 'rating' => 'required|in:' . implode(',', ModelsVideo::RATING_LIST), + 'duration' => 'required|integer', + ]; + } +} diff --git a/app/Models/Video.php b/app/Models/Video.php new file mode 100644 index 000000000..ae19df112 --- /dev/null +++ b/app/Models/Video.php @@ -0,0 +1,30 @@ + 'boolean', + 'year_launched' => 'integer', + 'duration' => 'integer', + ]; +} diff --git a/database/factories/VideoFactory.php b/database/factories/VideoFactory.php new file mode 100644 index 000000000..f26703353 --- /dev/null +++ b/database/factories/VideoFactory.php @@ -0,0 +1,22 @@ +define(Video::class, function (Faker $faker) { + return [ + 'title' => $faker->sentence(3), + 'description' => $faker->sentence(10), + 'year_launched' => rand(1895, 2022), + 'opened' => rand(0, 1), + 'rating' => Video::RATING_LIST[array_rand(Video::RATING_LIST)], + 'duration' => rand(1, 30), + // 'thumb_file' => null, + // 'banner_file' => null, + // 'trailer_file' => null, + // 'video_file' => null, + // 'published' => rand(0, 1), + ]; +}); diff --git a/database/migrations/2021_12_13_163935_create_videos_table.php b/database/migrations/2021_12_13_163935_create_videos_table.php new file mode 100644 index 000000000..3157e1485 --- /dev/null +++ b/database/migrations/2021_12_13_163935_create_videos_table.php @@ -0,0 +1,38 @@ +uuid('id')->primary(); + $table->string('title'); + $table->text('description'); + $table->smallInteger('year_launched'); + $table->boolean('opened')->default(false); + $table->string('rating', 3); + $table->smallInteger('duration'); + $table->softDeletes(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('videos'); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index cacb53c6d..dfd179e6b 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -15,5 +15,6 @@ public function run() $this->call(CategorySeeder::class); $this->call(GenreSeeder::class); $this->call(CastMemberSeeder::class); + $this->call(VideoSeeder::class); } } diff --git a/database/seeds/VideoSeeder.php b/database/seeds/VideoSeeder.php new file mode 100644 index 000000000..4320d54f6 --- /dev/null +++ b/database/seeds/VideoSeeder.php @@ -0,0 +1,16 @@ +create(); + } +} diff --git a/routes/api.php b/routes/api.php index a5d340d17..fe4646582 100644 --- a/routes/api.php +++ b/routes/api.php @@ -22,4 +22,5 @@ Route::resource('categories', 'CategoryController', $exceptCreateAndEdit); Route::resource('genres', 'GenreController', $exceptCreateAndEdit); Route::resource('cast_members', 'CastMemberController', $exceptCreateAndEdit); + Route::resource('videos', 'VideoController', $exceptCreateAndEdit); }); \ No newline at end of file From 5b03caf3ceabf0daa8c7225493beb18a1585615d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Tue, 28 Dec 2021 11:52:58 -0300 Subject: [PATCH 17/18] =?UTF-8?q?implementa=C3=A7=C3=B5es=20at=C3=A9=20o?= =?UTF-8?q?=20in=C3=ADcio=20do=20exerc=C3=ADcio=20para=20recursos=20de=20v?= =?UTF-8?q?=C3=ADdeo=20e=20relacionamento?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- .../Controllers/Api/BasicCrudController.php | 12 +- .../Controllers/Api/CastMemberController.php | 22 +- .../Controllers/Api/CategoryController.php | 22 +- app/Http/Controllers/Api/GenreController.php | 20 +- app/Http/Controllers/Api/VideoController.php | 67 +++++- app/Models/Video.php | 12 +- database/factories/VideoFactory.php | 2 +- ..._24_170602_create_category_video_table.php | 34 +++ ..._12_24_171219_create_genre_video_table.php | 34 +++ tests/Exceptions/TestException.php | 8 + .../Api/BasicCrudControllerTest.php | 1 + .../Controllers/Api/VideoControllerTest.php | 197 ++++++++++++++++++ tests/Feature/Models/VideoTest.php | 94 +++++++++ .../Controllers/CategoryControllerStubs.php | 20 +- tests/Unit/Models/CastMemberUnitTest.php | 2 +- tests/Unit/Models/CategoryUnitTest.php | 2 +- tests/Unit/Models/GenreUnitTest.php | 2 +- tests/Unit/Models/VideoUnitTest.php | 66 ++++++ 19 files changed, 578 insertions(+), 41 deletions(-) create mode 100644 database/migrations/2021_12_24_170602_create_category_video_table.php create mode 100644 database/migrations/2021_12_24_171219_create_genre_video_table.php create mode 100644 tests/Exceptions/TestException.php create mode 100644 tests/Feature/Http/Controllers/Api/VideoControllerTest.php create mode 100644 tests/Feature/Models/VideoTest.php create mode 100644 tests/Unit/Models/VideoUnitTest.php diff --git a/.env b/.env index f931d11f1..c6d070bfb 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ APP_NAME=Laravel APP_ENV=local -APP_KEY=base64:C3hmKIv89IGF1v6w9FiuMUIaJh2oqUp7eK1H1YWbI44= +APP_KEY=base64:dxDMR1Sa85l6aj2I7uS0YiB562B1U8IMQHOWf9h3WWk= APP_DEBUG=true APP_URL=http://localhost diff --git a/app/Http/Controllers/Api/BasicCrudController.php b/app/Http/Controllers/Api/BasicCrudController.php index 97427f1c0..f578f0c24 100644 --- a/app/Http/Controllers/Api/BasicCrudController.php +++ b/app/Http/Controllers/Api/BasicCrudController.php @@ -2,10 +2,8 @@ namespace App\Http\Controllers\Api; -use App\Models\Category; use Illuminate\Http\Request; use App\Http\Controllers\Controller; -use Illuminate\Database\Eloquent\Model; abstract class BasicCrudController extends Controller { @@ -14,6 +12,8 @@ protected abstract function model(): string; protected abstract function rulesStore(): array; + protected abstract function rulesUpdate(): array; + public function index() { return $this->model()::all(); @@ -22,9 +22,9 @@ public function index() public function store(Request $request) { $validatedData = $this->validate($request, $this->rulesStore()); - $obj = $this->model()::create($validatedData); - $obj->refresh(); - return $obj; + $record = $this->model()::create($validatedData); + $record->refresh(); + return $record; } protected function findOrFail(string $id) @@ -41,7 +41,7 @@ public function show($id) public function update(Request $request, $id) { - $this->validate($request, $this->rulesStore()); + $this->validate($request, $this->rulesUpdate()); $record = $this->findOrFail($id); $record->update($request->all()); $record->refresh(); diff --git a/app/Http/Controllers/Api/CastMemberController.php b/app/Http/Controllers/Api/CastMemberController.php index ff394ec13..eb0f68a36 100644 --- a/app/Http/Controllers/Api/CastMemberController.php +++ b/app/Http/Controllers/Api/CastMemberController.php @@ -7,6 +7,17 @@ class CastMemberController extends BasicCrudController { + private $rules; + + public function __construct() + { + $this->rules = [ + 'name' => 'required|max:255', + 'type' => 'numeric|in:' . implode(',', CastMember::getPossibleMembers()), + 'is_active' => 'boolean', + ]; + } + protected function model(): string { return CastMember::class; @@ -14,10 +25,11 @@ protected function model(): string protected function rulesStore(): array { - return [ - 'name' => 'required|max:255', - 'type' => 'numeric|in:' . implode(',', CastMember::getPossibleMembers()), - 'is_active' => 'boolean', - ]; + return $this->rules; + } + + protected function rulesUpdate(): array + { + return $this->rules; } } diff --git a/app/Http/Controllers/Api/CategoryController.php b/app/Http/Controllers/Api/CategoryController.php index 0ab191579..847095b49 100644 --- a/app/Http/Controllers/Api/CategoryController.php +++ b/app/Http/Controllers/Api/CategoryController.php @@ -7,6 +7,17 @@ class CategoryController extends BasicCrudController { + private $rules; + + public function __construct() + { + $this->rules = [ + 'name' => 'required|max:255', + 'description' => 'nullable', + 'is_active' => 'boolean', + ]; + } + protected function model(): string { return Category::class; @@ -14,10 +25,11 @@ protected function model(): string protected function rulesStore(): array { - return [ - 'name' => 'required|max:255', - 'description' => 'nullable', - 'is_active' => 'boolean', - ]; + return $this->rules; + } + + protected function rulesUpdate(): array + { + return $this->rules; } } diff --git a/app/Http/Controllers/Api/GenreController.php b/app/Http/Controllers/Api/GenreController.php index 3891d108b..9d78189d8 100644 --- a/app/Http/Controllers/Api/GenreController.php +++ b/app/Http/Controllers/Api/GenreController.php @@ -7,6 +7,16 @@ class GenreController extends BasicCrudController { + private $rules; + + public function __construct() + { + $this->rules = [ + 'name' => 'required|max:255', + 'is_active' => 'boolean', + ]; + } + protected function model(): string { return Genre::class; @@ -14,9 +24,11 @@ protected function model(): string protected function rulesStore(): array { - return [ - 'name' => 'required|max:255', - 'is_active' => 'boolean', - ]; + return $this->rules; + } + + protected function rulesUpdate(): array + { + return $this->rules; } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/VideoController.php b/app/Http/Controllers/Api/VideoController.php index 98875b98b..a639ddfcc 100644 --- a/app/Http/Controllers/Api/VideoController.php +++ b/app/Http/Controllers/Api/VideoController.php @@ -2,25 +2,70 @@ namespace App\Http\Controllers\Api; -use App\Models\Video as ModelsVideo; -use App\Video; - +use App\Models\Video; +use Illuminate\Http\Request; class VideoController extends BasicCrudController { - protected function model(): string - { - return Video::class; - } + private $rules; - protected function rulesStore(): array + public function __construct() { - return [ + $this->rules = [ 'title' => 'required|max:255', - 'descrption' => 'required', + 'description' => 'required', 'year_launched' => 'required|date_format:Y', 'opened' => 'boolean', - 'rating' => 'required|in:' . implode(',', ModelsVideo::RATING_LIST), + 'rating' => 'required|in:' . implode(',', Video::RATING_LIST), 'duration' => 'required|integer', + 'categories_id' => 'required|array|exists:categories,id', + 'genres_id' => 'required|array|exists:genres,id', ]; } + + public function store(Request $request) + { + $validatedData = $this->validate($request, $this->rulesStore()); + $self = $this; + $record = \DB::transaction(function () use ($request, $validatedData, $self) { + $record = $this->model()::create($validatedData); + $self->handleRelations($request, $record); + return $record; + }); + $record->refresh(); + return $record; + } + + public function update(Request $request, $id) + { + $validatedData = $this->validate($request, $this->rulesUpdate()); + $record = $this->findOrFail($id); + $self = $this; + $record = \DB::transaction(function () use ($request, $validatedData, $self, $record) { + $record->update($validatedData); + $self->handleRelations($request, $record); + return $record; + }); + $record->refresh(); + return $record; + } + + protected function handleRelations(Request $request, $record) { + $record->categories()->sync($request->get('categories_id')); + $record->genres()->sync($request->get('genres_id')); + } + + protected function model(): string + { + return Video::class; + } + + protected function rulesStore(): array + { + return $this->rules; + } + + protected function rulesUpdate(): array + { + return $this->rules; + } } diff --git a/app/Models/Video.php b/app/Models/Video.php index ae19df112..5a3f33945 100644 --- a/app/Models/Video.php +++ b/app/Models/Video.php @@ -10,7 +10,7 @@ class Video extends Model use SoftDeletes, Traits\Uuid; const RATING_FREE = 'L'; - const RATING_LIST = [self::RATING_FREE, '10', '12', '16', '18']; + const RATING_LIST = [self::RATING_FREE, '10', '12', '14', '16', '18']; protected $fillable = [ 'title', 'description', @@ -27,4 +27,14 @@ class Video extends Model 'year_launched' => 'integer', 'duration' => 'integer', ]; + + public function categories() + { + return $this->belongsToMany(Category::class); + } + + public function genres() + { + return $this->belongsToMany(Genre::class); + } } diff --git a/database/factories/VideoFactory.php b/database/factories/VideoFactory.php index f26703353..a21668d50 100644 --- a/database/factories/VideoFactory.php +++ b/database/factories/VideoFactory.php @@ -9,7 +9,7 @@ return [ 'title' => $faker->sentence(3), 'description' => $faker->sentence(10), - 'year_launched' => rand(1895, 2022), + 'year_launched' => rand(1895, date('Y')), 'opened' => rand(0, 1), 'rating' => Video::RATING_LIST[array_rand(Video::RATING_LIST)], 'duration' => rand(1, 30), diff --git a/database/migrations/2021_12_24_170602_create_category_video_table.php b/database/migrations/2021_12_24_170602_create_category_video_table.php new file mode 100644 index 000000000..17e7951e8 --- /dev/null +++ b/database/migrations/2021_12_24_170602_create_category_video_table.php @@ -0,0 +1,34 @@ +uuid('category_id'); + $table->foreign('category_id')->references('id')->on('categories'); + $table->uuid('video_id'); + $table->foreign('video_id')->references('id')->on('videos'); + $table->unique(['category_id', 'video_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('category_video'); + } +} diff --git a/database/migrations/2021_12_24_171219_create_genre_video_table.php b/database/migrations/2021_12_24_171219_create_genre_video_table.php new file mode 100644 index 000000000..1facf67eb --- /dev/null +++ b/database/migrations/2021_12_24_171219_create_genre_video_table.php @@ -0,0 +1,34 @@ +uuid('genre_id'); + $table->foreign('genre_id')->references('id')->on('genres'); + $table->uuid('video_id'); + $table->foreign('video_id')->references('id')->on('videos'); + $table->unique(['genre_id', 'video_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('genre_video'); + } +} diff --git a/tests/Exceptions/TestException.php b/tests/Exceptions/TestException.php new file mode 100644 index 000000000..b194cf202 --- /dev/null +++ b/tests/Exceptions/TestException.php @@ -0,0 +1,8 @@ + 'test name', 'description' => 'description test']); $request = \Mockery::mock(Request::class); + /** @var Request $request */ $request->shouldReceive('all') ->andReturn(['name' => 'tested name', 'description' => 'description tested']); $result = $this->controller->update($request, $category->id)->toArray(); diff --git a/tests/Feature/Http/Controllers/Api/VideoControllerTest.php b/tests/Feature/Http/Controllers/Api/VideoControllerTest.php new file mode 100644 index 000000000..3b5009976 --- /dev/null +++ b/tests/Feature/Http/Controllers/Api/VideoControllerTest.php @@ -0,0 +1,197 @@ +video = factory(Video::class)->create(); + } + + public function testIndex() + { + $response = $this->get(route('videos.index')); + $response->assertStatus(200)->assertJson([$this->video->toArray()]); + } + + public function testShow() + { + $response = $this->get(route('videos.show', ['video' => $this->video->id])); + $response->assertStatus(200)->assertJson($this->video->toArray()); + } + + public function testInvalidationDataPost() + { + $this->assertInvalidationInStore(['title' => ''], 'required'); + $this->assertInvalidationInStore(['description' => ''], 'required'); + $this->assertInvalidationInStore(['rating' => ''], 'required'); + $this->assertInvalidationInStore(['genres_id' => ''], 'required'); + $this->assertInvalidationInStore(['genres_id' => 'test'], 'array'); + $this->assertInvalidationInStore(['genres_id' => [100]], 'exists'); + $this->assertInvalidationInStore(['categories_id' => ''], 'required'); + $this->assertInvalidationInStore(['categories_id' => 'test'], 'array'); + $this->assertInvalidationInStore(['categories_id' => [100]], 'exists'); + $this->assertInvalidationInStore(['year_launched' => ''], 'required'); + $this->assertInvalidationInStore(['year_launched' => '01/01/2000'], 'date_format', ['format' => 'Y']); + $this->assertInvalidationInStore(['duration' => ''], 'required'); + $this->assertInvalidationInStore(['title' => str_repeat('a', 256)], 'max.string', ['max' => 255]); + $this->assertInvalidationInStore(['opened' => 'true'], 'boolean'); + $this->assertInvalidationInStore(['rating' => 'na'], 'in'); + } + + public function testInvalidationDataPut() + { + $this->assertInvalidationInUpdate(['title' => ''], 'required'); + $this->assertInvalidationInUpdate(['description' => ''], 'required'); + $this->assertInvalidationInUpdate(['rating' => ''], 'required'); + $this->assertInvalidationInUpdate(['genres_id' => ''], 'required'); + $this->assertInvalidationInUpdate(['genres_id' => 'test'], 'array'); + $this->assertInvalidationInUpdate(['genres_id' => [100]], 'exists'); + $this->assertInvalidationInUpdate(['categories_id' => ''], 'required'); + $this->assertInvalidationInUpdate(['categories_id' => 'test'], 'array'); + $this->assertInvalidationInUpdate(['categories_id' => [100]], 'exists'); + $this->assertInvalidationInUpdate(['year_launched' => ''], 'required'); + $this->assertInvalidationInUpdate(['year_launched' => '01/01/2000'], 'date_format', ['format' => 'Y']); + $this->assertInvalidationInUpdate(['duration' => ''], 'required'); + $this->assertInvalidationInUpdate(['title' => str_repeat('a', 256)], 'max.string', ['max' => 255]); + $this->assertInvalidationInUpdate(['opened' => 'true'], 'boolean'); + $this->assertInvalidationInUpdate(['rating' => 'na'], 'in'); + } + + public function testStore() { + $category = factory(Category::class)->create(); + $genre = factory(Genre::class)->create(); + $values = [ + 'title' => 'test1', + 'description' => 'description1', + 'year_launched' => 2000, + 'rating' => Video::RATING_FREE, + 'duration' => 120, + 'opened' => true, + ]; + $response = $this->assertStore($values + ['categories_id' => [$category->id], 'genres_id' => [$genre->id]], $values + ['deleted_at' => null]); + $response->assertJsonStructure(['created_at', 'updated_at']); + $values = [ + 'title' => 'test2', + 'description' => 'description 1', + 'year_launched' => 2020, + 'rating' => '12', + 'duration' => 120, + 'opened' => false, + ]; + $this->assertStore($values + ['categories_id' => [$category->id], 'genres_id' => [$genre->id]], $values + ['deleted_at' => null]); + + } + + public function testUpdate() + { + $category = factory(Category::class)->create(); + $genre = factory(Genre::class)->create(); + $newValues = [ + 'title' => 'test1', + 'description' => 'description 1', + 'year_launched' => 2020, + 'rating' => '12', + 'duration' => 120, + 'opened' => true, + ]; + $response = $this->assertUpdate($newValues + ['categories_id' => [$category->id], 'genres_id' => [$genre->id]], $newValues); + $response->assertJsonStructure(['created_at', 'updated_at']); + $category = factory(Category::class)->create(); + $genre = factory(Genre::class)->create(); + $newValues = [ + 'title' => 'test 2', + 'description' => 'description 2', + 'year_launched' => 2020, + 'rating' => '18', + 'duration' => 120, + 'opened' => false, + ]; + $response = $this->assertUpdate($newValues + ['categories_id' => [$category->id], 'genres_id' => [$genre->id]], $newValues); + + $this->video->description = 'test description'; + $this->video->save(); + $newValues = [ + 'title' => 'test description', + 'description' => 'test description', + 'year_launched' => 1985, + 'rating' => '16', + 'duration' => 200, + 'opened' => true, + ]; + $response = $this->assertUpdate($newValues + ['categories_id' => [$category->id], 'genres_id' => [$genre->id]], $newValues); + } + + public function testRollbackStore(): void + { + $category = factory(Category::class)->create(); + $genre = factory(Genre::class)->create(); + $values = [ + 'title' => 'test1', + 'description' => 'description1', + 'year_launched' => 2000, + 'rating' => Video::RATING_FREE, + 'duration' => 120, + 'opened' => true, + ]; + $controller = \Mockery::mock(VideoController::class) + ->makePartial() + ->shouldAllowMockingProtectedMethods(); + $controller->shouldReceive('handleRelations')->once()->andThrows(new TestException('intencional')); + $controller->shouldReceive('validate')->withAnyArgs()->andReturn($values); + $controller->shouldReceive('rulesStore')->withAnyArgs()->andReturn([]); + $request = \Mockery::mock(Request::class); + try { + $controller->store($request); + } catch(TestException $e) { + $this->assertCount(1, Video::all()); + } + } + + public function testDelete() + { + $response = $this->json('DELETE', route('videos.destroy', ['video' => $this->video->id])); + $response->assertStatus(204); + $this->assertNull(Video::find($this->video->id)); + $this->assertNotNull(Video::withTrashed()->find($this->video->id)); + } + + protected function assertInvalidationRequired(TestResponse $response) + { + $this->AssertInvalidationFields($response, ['title', 'description'], 'required', []); + $response->assertJsonMissingValidationErrors(['is_active']); + } + + protected function routeStore(): string + { + return route('videos.store'); + } + + protected function routeUpdate(): string + { + return route('videos.update', ['video' => $this->video->id]); + } + + protected function model(): string + { + return Video::class; + } +} diff --git a/tests/Feature/Models/VideoTest.php b/tests/Feature/Models/VideoTest.php new file mode 100644 index 000000000..0bf053361 --- /dev/null +++ b/tests/Feature/Models/VideoTest.php @@ -0,0 +1,94 @@ +create(); + $videos = Video::all(); + $fields = array_keys($videos->first()->getAttributes()); + $this->assertCount(1, $videos); + $this->assertEqualsCanonicalizing([ + 'title', + 'description', + 'year_launched', + 'opened', + 'rating', + 'duration', + 'created_at', + 'deleted_at', + 'updated_at', + "id", + ], $fields); + } + + public function testCreate() + { + $video = Video::create([ + 'title' => 'test1', + 'description' => 'description 1', + 'duration' => 180, + 'year_launched' => 1990, + 'rating' => '14', + ]); + $video->refresh(); + $this->assertEquals('test1', $video->title); + $this->assertFalse($video->opened); + $this->assertRegExp('/[a-f0-9]{8}\-[a-f0-9]{4}\-4[a-f0-9]{3}\-(8|9|a|b)[a-f0-9]{3}\-[a-f0-9]{12}/', $video->id); + $video = Video::create([ + 'title' => 'test1', + 'description' => 'description 1', + 'duration' => 180, + 'year_launched' => 1990, + 'rating' => '14', + 'opened' => true, + ]); + $video->refresh(); + $this->assertTrue($video->opened); + } + + public function testUpdate() + { + $video = factory(Video::class, 1)->create([ + 'title' => 'test1', + 'description' => 'description 1', + 'duration' => 180, + 'year_launched' => 1990, + 'rating' => '14', + 'opened' => true, + ])->first(); + + $data = [ + 'title' => 'test update', + 'opened' => false, + 'description' => 'update test', + 'duration' => 120, + 'year_launched' => 2000, + 'rating' => Video::RATING_FREE, + ]; + $video->update($data); + + foreach($data as $key => $value) { + $this->assertEquals($value, $video->{$key}); + } + } + + public function testDelete() + { + $video = factory(Video::class, 1)->create()->first(); + $video->delete(); + $this->assertNull(Video::find($video->id)); + } +} diff --git a/tests/Stubs/Controllers/CategoryControllerStubs.php b/tests/Stubs/Controllers/CategoryControllerStubs.php index a19c8212c..316dd3a56 100644 --- a/tests/Stubs/Controllers/CategoryControllerStubs.php +++ b/tests/Stubs/Controllers/CategoryControllerStubs.php @@ -7,6 +7,16 @@ class CategoryControllerStubs extends BasicCrudController { + private $rules; + + public function __construct() + { + $this->rules = [ + 'name' => 'required|max:255', + 'description' => 'nullable', + ]; + } + protected function model(): string { return CategoryStub::class; @@ -14,9 +24,11 @@ protected function model(): string protected function rulesStore(): array { - return [ - 'name' => 'required|max:255', - 'description' => 'nullable', - ]; + return $this->rules; + } + + protected function rulesUpdate(): array + { + return $this->rules; } } diff --git a/tests/Unit/Models/CastMemberUnitTest.php b/tests/Unit/Models/CastMemberUnitTest.php index 5c67fe072..5b7d1470a 100644 --- a/tests/Unit/Models/CastMemberUnitTest.php +++ b/tests/Unit/Models/CastMemberUnitTest.php @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; use App\Models\Traits\Uuid; -class CastMemberTest extends TestCase +class CastMemberUnitTest extends TestCase { private $castMember; diff --git a/tests/Unit/Models/CategoryUnitTest.php b/tests/Unit/Models/CategoryUnitTest.php index 61e78317c..d4dd530ae 100644 --- a/tests/Unit/Models/CategoryUnitTest.php +++ b/tests/Unit/Models/CategoryUnitTest.php @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; use App\Models\Traits\Uuid; -class CategoryTest extends TestCase +class CategoryUnitTest extends TestCase { private $category; diff --git a/tests/Unit/Models/GenreUnitTest.php b/tests/Unit/Models/GenreUnitTest.php index eda330be5..46a157f3a 100644 --- a/tests/Unit/Models/GenreUnitTest.php +++ b/tests/Unit/Models/GenreUnitTest.php @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; use App\Models\Traits\Uuid; -class GenreTest extends TestCase +class GenreUnitTest extends TestCase { private $genre; diff --git a/tests/Unit/Models/VideoUnitTest.php b/tests/Unit/Models/VideoUnitTest.php new file mode 100644 index 000000000..65a500971 --- /dev/null +++ b/tests/Unit/Models/VideoUnitTest.php @@ -0,0 +1,66 @@ +video = new Video(); + } + + protected function tearDown(): void + { + parent::tearDown(); + $this->video = new Video(); + } + + public function testFillable() + { + $fillable = [ + 'title', + 'description', + 'year_launched', + 'opened', + 'rating', + 'duration', + ]; + $this->assertEquals($this->video->getFillable(), $fillable); + } + + public function testUseOfTrais() + { + $traits = [softDeletes::class, uuid::class]; + $videoTraits = array_keys(class_uses(Video::class)); + $this->assertEqualsCanonicalizing($traits, $videoTraits); + } + + public function testCasts() + { + $casts = [ + 'opened' => 'boolean', + 'year_launched' => 'integer', + 'duration' => 'integer', + ]; + $this->assertEqualsCanonicalizing($casts, $this->video->getCasts()); + } + + public function testDates() + { + $dates = ['created_at', 'deleted_at', 'updated_at']; + $this->assertEqualsCanonicalizing($dates, $this->video->getDates()); + } + + public function testNotIncreminting() + { + $this->assertFalse($this->video->incrementing); + } +} From a5718fb7ee7819c2ea44923efec50d63c18d7b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ricles=20Luz?= Date: Tue, 28 Dec 2021 21:18:07 -0300 Subject: [PATCH 18/18] =?UTF-8?q?adi=C3=A7=C3=A3o=20de=20relacionamento=20?= =?UTF-8?q?e=20testes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- .../Controllers/Api/CategoryController.php | 1 - app/Http/Controllers/Api/GenreController.php | 34 +++++++++++++- app/Models/Genre.php | 6 +++ ..._24_170602_create_category_video_table.php | 4 +- ..._28_213759_create_category_genre_table.php | 34 ++++++++++++++ .../Api/BasicCrudControllerTest.php | 1 + .../Http/Controllers/Api/CastMemberTest.php | 6 +++ .../Api/CategoryControllerTest.php | 7 +++ .../Controllers/Api/GenreControllerTest.php | 47 +++++++++++++++++-- .../Controllers/Api/VideoControllerTest.php | 8 +++- 11 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 database/migrations/2021_12_28_213759_create_category_genre_table.php diff --git a/.env b/.env index c6d070bfb..87513012a 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ APP_NAME=Laravel APP_ENV=local -APP_KEY=base64:dxDMR1Sa85l6aj2I7uS0YiB562B1U8IMQHOWf9h3WWk= +APP_KEY=base64:/MUbL7KQrYPhO0YwI/GWoxjluiRp1ebUZ0xn84GzVrs= APP_DEBUG=true APP_URL=http://localhost diff --git a/app/Http/Controllers/Api/CategoryController.php b/app/Http/Controllers/Api/CategoryController.php index 847095b49..0af0b92fe 100644 --- a/app/Http/Controllers/Api/CategoryController.php +++ b/app/Http/Controllers/Api/CategoryController.php @@ -2,7 +2,6 @@ namespace App\Http\Controllers\Api; -use App\Http\Controllers\Api\BasicCrudController; use App\Models\Category; class CategoryController extends BasicCrudController diff --git a/app/Http/Controllers/Api/GenreController.php b/app/Http/Controllers/Api/GenreController.php index 9d78189d8..6bce494b1 100644 --- a/app/Http/Controllers/Api/GenreController.php +++ b/app/Http/Controllers/Api/GenreController.php @@ -3,7 +3,7 @@ namespace App\Http\Controllers\Api; use App\Models\Genre; -use App\Http\Controllers\Api\BasicCrudController; +use Illuminate\Http\Request; class GenreController extends BasicCrudController { @@ -14,9 +14,41 @@ public function __construct() $this->rules = [ 'name' => 'required|max:255', 'is_active' => 'boolean', + 'categories_id' => 'required|array|exists:categories,id', ]; } + public function store(Request $request) + { + $validatedData = $this->validate($request, $this->rulesStore()); + $self = $this; + $record = \DB::transaction(function () use ($request, $validatedData, $self) { + $record = $this->model()::create($validatedData); + $self->handleRelations($request, $record); + return $record; + }); + $record->refresh(); + return $record; + } + + public function update(Request $request, $id) + { + $validatedData = $this->validate($request, $this->rulesUpdate()); + $record = $this->findOrFail($id); + $self = $this; + $record = \DB::transaction(function () use ($request, $validatedData, $self, $record) { + $record->update($validatedData); + $self->handleRelations($request, $record); + return $record; + }); + $record->refresh(); + return $record; + } + + protected function handleRelations(Request $request, $record) { + $record->categories()->sync($request->get('categories_id')); + } + protected function model(): string { return Genre::class; diff --git a/app/Models/Genre.php b/app/Models/Genre.php index 0edd5ea5f..3515060d7 100644 --- a/app/Models/Genre.php +++ b/app/Models/Genre.php @@ -13,4 +13,10 @@ class Genre extends Model public $incrementing = false; protected $keyType = 'string'; protected $casts = ['is_active' => 'boolean']; + + public function categories() + { + return $this->belongsToMany(Category::class); + } + } diff --git a/database/migrations/2021_12_24_170602_create_category_video_table.php b/database/migrations/2021_12_24_170602_create_category_video_table.php index 17e7951e8..0280482c6 100644 --- a/database/migrations/2021_12_24_170602_create_category_video_table.php +++ b/database/migrations/2021_12_24_170602_create_category_video_table.php @@ -14,10 +14,10 @@ class CreateCategoryVideoTable extends Migration public function up() { Schema::create('category_video', function (Blueprint $table) { - $table->uuid('category_id'); - $table->foreign('category_id')->references('id')->on('categories'); $table->uuid('video_id'); $table->foreign('video_id')->references('id')->on('videos'); + $table->uuid('category_id'); + $table->foreign('category_id')->references('id')->on('categories'); $table->unique(['category_id', 'video_id']); }); } diff --git a/database/migrations/2021_12_28_213759_create_category_genre_table.php b/database/migrations/2021_12_28_213759_create_category_genre_table.php new file mode 100644 index 000000000..8ec457e69 --- /dev/null +++ b/database/migrations/2021_12_28_213759_create_category_genre_table.php @@ -0,0 +1,34 @@ +uuid('genre_id'); + $table->foreign('genre_id')->references('id')->on('genres'); + $table->uuid('category_id'); + $table->foreign('category_id')->references('id')->on('categories'); + $table->unique(['genre_id', 'category_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('category_genre'); + } +} diff --git a/tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php b/tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php index 389b672b8..26023b911 100644 --- a/tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php +++ b/tests/Feature/Http/Controllers/Api/BasicCrudControllerTest.php @@ -26,6 +26,7 @@ protected function setUp(): void protected function tearDown(): void { CategoryStub::dropTable(); + \Mockery::close(); parent::tearDown(); } diff --git a/tests/Feature/Http/Controllers/Api/CastMemberTest.php b/tests/Feature/Http/Controllers/Api/CastMemberTest.php index 523e393ab..fa07695b2 100644 --- a/tests/Feature/Http/Controllers/Api/CastMemberTest.php +++ b/tests/Feature/Http/Controllers/Api/CastMemberTest.php @@ -22,6 +22,12 @@ protected function setUp(): void $this->castMember = factory(CastMember::class)->create(); } + public function tearDown(): void + { + \Mockery::close(); + parent::tearDown(); + } + public function testIndex() { $response = $this->get(route('cast_members.index')); diff --git a/tests/Feature/Http/Controllers/Api/CategoryControllerTest.php b/tests/Feature/Http/Controllers/Api/CategoryControllerTest.php index edcafaa82..dda18adbf 100644 --- a/tests/Feature/Http/Controllers/Api/CategoryControllerTest.php +++ b/tests/Feature/Http/Controllers/Api/CategoryControllerTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Http\Controllers\Api; use App\Models\Category; +use App\Models\Genre; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\TestResponse; use Tests\TestCase; @@ -21,6 +22,12 @@ protected function setUp(): void $this->category = factory(Category::class)->create(); } + public function tearDown(): void + { + \Mockery::close(); + parent::tearDown(); + } + public function testIndex() { $response = $this->get(route('categories.index')); diff --git a/tests/Feature/Http/Controllers/Api/GenreControllerTest.php b/tests/Feature/Http/Controllers/Api/GenreControllerTest.php index 63089a7ed..1ac0d76e4 100644 --- a/tests/Feature/Http/Controllers/Api/GenreControllerTest.php +++ b/tests/Feature/Http/Controllers/Api/GenreControllerTest.php @@ -2,12 +2,16 @@ namespace Tests\Feature\Http\Controllers\Api; +use App\Models\Category; use App\Models\Genre; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\TestResponse; use Tests\TestCase; use Tests\Traits\TestSaves; use Tests\Traits\TestValidations; +use App\Http\Controllers\Api\GenreController; +use Illuminate\Http\Request; +use Tests\Exceptions\TestException; class GenreControllerTest extends TestCase { @@ -21,6 +25,12 @@ protected function setUp(): void $this->genre = factory(Genre::class)->create(); } + public function tearDown(): void + { + \Mockery::close(); + parent::tearDown(); + } + public function testIndex() { $response = $this->get(route('genres.index')); @@ -35,6 +45,9 @@ public function testShow() public function testInvalidationDataPost() { + $this->assertInvalidationInStore(['categories_id' => ''], 'required'); + $this->assertInvalidationInStore(['categories_id' => 'test'], 'array'); + $this->assertInvalidationInStore(['categories_id' => [100]], 'exists'); $this->assertInvalidationInStore(['name' => ''], 'required'); $this->assertInvalidationInStore(['name' => str_repeat('a', 256)], 'max.string', ['max' => 255]); $this->assertInvalidationInStore(['is_active' => 'true'], 'boolean'); @@ -42,22 +55,45 @@ public function testInvalidationDataPost() public function testInvalidationDataPut() { + $this->assertInvalidationInUpdate(['categories_id' => ''], 'required'); + $this->assertInvalidationInUpdate(['categories_id' => 'test'], 'array'); + $this->assertInvalidationInUpdate(['categories_id' => [100]], 'exists'); $this->assertInvalidationInUpdate(['name' => ''], 'required'); $this->assertInvalidationInUpdate(['name' => str_repeat('a', 256)], 'max.string', ['max' => 255]); $this->assertInvalidationInUpdate(['is_active' => 'true'], 'boolean'); } + public function testRollbackStore(): void + { + $values = [ + 'name' => 'test1', + ]; + $controller = \Mockery::mock(GenreController::class) + ->makePartial() + ->shouldAllowMockingProtectedMethods(); + $controller->shouldReceive('handleRelations')->once()->andThrows(new TestException('intencional')); + $controller->shouldReceive('validate')->withAnyArgs()->andReturn($values); + $controller->shouldReceive('rulesStore')->withAnyArgs()->andReturn([]); + $request = \Mockery::mock(Request::class); + try { + $controller->store($request); + } catch(TestException $e) { + $this->assertCount(1, Genre::all()); + } + } + public function testStore() { $values = [ 'name' => 'test1', ]; - $response = $this->assertStore($values, $values + ['is_active' => true, 'deleted_at' => null]); + $category = factory(Category::class)->create(); + $response = $this->assertStore($values + ['categories_id' => [$category->id]], $values + ['is_active' => true, 'deleted_at' => null]); $response->assertJsonStructure(['created_at', 'updated_at']); $values = [ 'name' => 'test2', 'is_active' => false, ]; - $this->assertStore($values, $values + ['deleted_at' => null]); + $this->assertStore($values + ['categories_id' => [$category->id]], $values + ['deleted_at' => null]); } @@ -67,19 +103,20 @@ public function testUpdate() 'name' => 'test 1', 'is_active' => true, ]; - $response = $this->assertUpdate($newValues, $newValues); + $category = factory(Category::class)->create(); + $response = $this->assertUpdate($newValues + ['categories_id' => [$category->id]], $newValues); $response->assertJsonStructure(['created_at', 'updated_at']); $newValues = [ 'name' => 'test 1', ]; - $response = $this->assertUpdate($newValues, $newValues); + $response = $this->assertUpdate($newValues + ['categories_id' => [$category->id]], $newValues); $this->genre->name = 'test2'; $this->genre->save(); $newValues = [ 'name' => 'test name', ]; - $response = $this->assertUpdate($newValues, $newValues); + $response = $this->assertUpdate($newValues + ['categories_id' => [$category->id]], $newValues); } public function testDelete() diff --git a/tests/Feature/Http/Controllers/Api/VideoControllerTest.php b/tests/Feature/Http/Controllers/Api/VideoControllerTest.php index 3b5009976..c35d8b74c 100644 --- a/tests/Feature/Http/Controllers/Api/VideoControllerTest.php +++ b/tests/Feature/Http/Controllers/Api/VideoControllerTest.php @@ -26,6 +26,12 @@ protected function setUp(): void $this->video = factory(Video::class)->create(); } + public function tearDown(): void + { + \Mockery::close(); + parent::tearDown(); + } + public function testIndex() { $response = $this->get(route('videos.index')); @@ -142,8 +148,6 @@ public function testUpdate() public function testRollbackStore(): void { - $category = factory(Category::class)->create(); - $genre = factory(Genre::class)->create(); $values = [ 'title' => 'test1', 'description' => 'description1',