From 4f7290711d71f2dccabf012d392e3757d3f4c8f7 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Mon, 22 Jun 2026 15:02:04 +0700 Subject: [PATCH] refactor: update database connection settings in phpunit.xml and clean up CategoryService by removing unused docblocks and reorganizing sorting logic for improved clarity --- app/Services/Master/CategoryService.php | 36 +- phpunit.xml | 5 +- .../pages/admin/master/categories/Index.vue | 4 +- tests/Feature/Admin/Master/CategoryTest.php | 333 ++++++++++++++++++ 4 files changed, 349 insertions(+), 29 deletions(-) create mode 100644 tests/Feature/Admin/Master/CategoryTest.php diff --git a/app/Services/Master/CategoryService.php b/app/Services/Master/CategoryService.php index 9713352..2b473da 100644 --- a/app/Services/Master/CategoryService.php +++ b/app/Services/Master/CategoryService.php @@ -8,9 +8,6 @@ class CategoryService { - /** - * @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery - */ public function paginateForIndex(array $tableQuery): LengthAwarePaginator { $query = Category::query() @@ -28,17 +25,11 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator ->withQueryString(); } - /** - * @param array $validated - */ public function create(array $validated): void { Category::create($validated); } - /** - * @param array $validated - */ public function update(Category $category, array $validated): void { $category->update($validated); @@ -49,6 +40,18 @@ public function delete(Category $category): void $category->delete(); } + public function getSelectOptions(): array + { + return Category::query() + ->orderBy('name') + ->get(['id', 'name']) + ->map(fn (Category $category) => [ + 'value' => $category->id, + 'label' => $category->name, + ]) + ->all(); + } + private function applySorting(Builder $query, string $sort, string $direction): void { if (in_array($sort, ['name', 'slug'], true)) { @@ -59,19 +62,4 @@ private function applySorting(Builder $query, string $sort, string $direction): $query->latest(); } - - /** - * @return list - */ - public function getSelectOptions(): array - { - return Category::query() - ->orderBy('name') - ->get(['id', 'name']) - ->map(fn (Category $category) => [ - 'value' => $category->id, - 'label' => $category->name, - ]) - ->all(); - } } diff --git a/phpunit.xml b/phpunit.xml index e7f0a48..31e5975 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -23,9 +23,8 @@ - - - + + diff --git a/resources/js/pages/admin/master/categories/Index.vue b/resources/js/pages/admin/master/categories/Index.vue index e178fa5..79d7567 100644 --- a/resources/js/pages/admin/master/categories/Index.vue +++ b/resources/js/pages/admin/master/categories/Index.vue @@ -2,8 +2,6 @@ import { Head } from '@inertiajs/vue3'; import { Plus } from '@lucide/vue'; import { computed, ref, watch } from 'vue'; -import CategoryFormModal from './form/CategoryFormModal.vue'; -import { createColumns } from './table/columns'; import { DataTable } from '@/components/data-table'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; @@ -12,6 +10,8 @@ import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataT import AdminLayout from '@/layouts/AdminLayout.vue'; import type { CategoryListItem, PaginatedCategories } from '@/types/category'; import type { DataTableSort } from '@/types/data-table'; +import CategoryFormModal from './form/CategoryFormModal.vue'; +import { createColumns } from './table/columns'; const props = defineProps<{ categories: PaginatedCategories; diff --git a/tests/Feature/Admin/Master/CategoryTest.php b/tests/Feature/Admin/Master/CategoryTest.php new file mode 100644 index 0000000..c6f1616 --- /dev/null +++ b/tests/Feature/Admin/Master/CategoryTest.php @@ -0,0 +1,333 @@ +seed(RolePermissionSeeder::class); +}); + +// ─── Helper ─────────────────────────────────────────────── + +function createUserWithPermission(PermissionEnum ...$permissions): User +{ + $user = User::factory()->create(); + + $user->givePermissionTo( + array_merge( + [PermissionEnum::DASHBOARD_VIEW->value], + array_map(fn (PermissionEnum $p) => $p->value, $permissions) + ) + ); + + $user->forgetCachedPermissions(); + + return $user; +} + +// ─── Index ──────────────────────────────────────────────── + +describe('Category Index', function () { + test('authenticated user with permission can view category index', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW); + + Category::factory()->count(3)->create(); + + $this->actingAs($user) + ->get(route('admin.master.categories.index')) + ->assertOk(); + }); + + test('guest is redirected to login', function () { + $this->get(route('admin.master.categories.index')) + ->assertRedirect(route('login')); + }); + + test('user without permission is forbidden', function () { + $user = User::factory()->create(); + + $this->actingAs($user) + ->get(route('admin.master.categories.index')) + ->assertForbidden(); + }); + + test('index returns categories with pagination', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW); + + Category::factory()->count(15)->create(); + + $response = $this->actingAs($user) + ->get(route('admin.master.categories.index')); + + $response->assertOk(); + }); + + test('index can search categories by name', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW); + + Category::factory()->create(['name' => 'Kaos Polos']); + Category::factory()->create(['name' => 'Kemeja Formal']); + + $this->actingAs($user) + ->get(route('admin.master.categories.index', ['search' => 'Kaos'])) + ->assertOk(); + }); +}); + +// ─── Store ──────────────────────────────────────────────── + +describe('Category Store', function () { + test('authenticated user with permission can create a category', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_CREATE); + + $this->actingAs($user) + ->post(route('admin.master.categories.store'), [ + 'name' => 'Kategori Baru', + ]) + ->assertRedirect(route('admin.master.categories.index')); + + $this->assertDatabaseHas('categories', [ + 'name' => 'Kategori Baru', + 'slug' => 'kategori-baru', + ]); + }); + + test('guest cannot create a category', function () { + $this->post(route('admin.master.categories.store'), [ + 'name' => 'Kategori Baru', + ])->assertRedirect(route('login')); + }); + + test('user without create permission is forbidden', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW); + + $this->actingAs($user) + ->post(route('admin.master.categories.store'), [ + 'name' => 'Kategori Baru', + ]) + ->assertForbidden(); + + $this->assertDatabaseCount('categories', 0); + }); + + test('name is required', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_CREATE); + + $this->actingAs($user) + ->post(route('admin.master.categories.store'), [ + 'name' => '', + ]) + ->assertSessionHasErrors('name'); + + $this->assertDatabaseCount('categories', 0); + }); + + test('name must not exceed 50 characters', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_CREATE); + + $this->actingAs($user) + ->post(route('admin.master.categories.store'), [ + 'name' => str_repeat('a', 51), + ]) + ->assertSessionHasErrors('name'); + + $this->assertDatabaseCount('categories', 0); + }); + + test('name must be a string', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_CREATE); + + $this->actingAs($user) + ->post(route('admin.master.categories.store'), [ + 'name' => ['bukan string'], + ]) + ->assertSessionHasErrors('name'); + + $this->assertDatabaseCount('categories', 0); + }); + + test('slug is auto-generated from name', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_CREATE); + + $this->actingAs($user) + ->post(route('admin.master.categories.store'), [ + 'name' => 'Bahan Katun', + ]); + + $this->assertDatabaseHas('categories', [ + 'name' => 'Bahan Katun', + 'slug' => 'bahan-katun', + ]); + }); +}); + +// ─── Update ─────────────────────────────────────────────── + +describe('Category Update', function () { + test('authenticated user with permission can update a category', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_UPDATE); + + $category = Category::factory()->create(['name' => 'Nama Lama']); + + $this->actingAs($user) + ->put(route('admin.master.categories.update', $category), [ + 'name' => 'Nama Baru', + ]) + ->assertRedirect(route('admin.master.categories.index')); + + $this->assertDatabaseHas('categories', [ + 'id' => $category->id, + 'name' => 'Nama Baru', + ]); + }); + + test('guest cannot update a category', function () { + $category = Category::factory()->create(); + + $this->put(route('admin.master.categories.update', $category), [ + 'name' => 'Nama Baru', + ])->assertRedirect(route('login')); + }); + + test('user without update permission is forbidden', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW); + + $category = Category::factory()->create(['name' => 'Nama Lama']); + + $this->actingAs($user) + ->put(route('admin.master.categories.update', $category), [ + 'name' => 'Nama Baru', + ]) + ->assertForbidden(); + + $this->assertDatabaseHas('categories', [ + 'id' => $category->id, + 'name' => 'Nama Lama', + ]); + }); + + test('name is required on update', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_UPDATE); + + $category = Category::factory()->create(); + + $this->actingAs($user) + ->put(route('admin.master.categories.update', $category), [ + 'name' => '', + ]) + ->assertSessionHasErrors('name'); + }); + + test('name must not exceed 50 characters on update', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_UPDATE); + + $category = Category::factory()->create(); + + $this->actingAs($user) + ->put(route('admin.master.categories.update', $category), [ + 'name' => str_repeat('a', 51), + ]) + ->assertSessionHasErrors('name'); + }); + + test('slug is updated when name changes', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_UPDATE); + + $category = Category::factory()->create(['name' => 'Nama Lama']); + + $this->actingAs($user) + ->put(route('admin.master.categories.update', $category), [ + 'name' => 'Nama Terbaru', + ]); + + $this->assertDatabaseHas('categories', [ + 'id' => $category->id, + 'slug' => 'nama-terbaru', + ]); + }); +}); + +// ─── Destroy ────────────────────────────────────────────── + +describe('Category Destroy', function () { + test('authenticated user with permission can delete a category', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_DELETE); + + $category = Category::factory()->create(); + + $this->actingAs($user) + ->delete(route('admin.master.categories.destroy', $category)) + ->assertRedirect(route('admin.master.categories.index')); + + $this->assertSoftDeleted('categories', [ + 'id' => $category->id, + ]); + }); + + test('guest cannot delete a category', function () { + $category = Category::factory()->create(); + + $this->delete(route('admin.master.categories.destroy', $category)) + ->assertRedirect(route('login')); + + $this->assertNotSoftDeleted('categories', [ + 'id' => $category->id, + ]); + }); + + test('user without delete permission is forbidden', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW); + + $category = Category::factory()->create(); + + $this->actingAs($user) + ->delete(route('admin.master.categories.destroy', $category)) + ->assertForbidden(); + + $this->assertNotSoftDeleted('categories', [ + 'id' => $category->id, + ]); + }); + + test('deleted category is soft deleted', function () { + $user = createUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_DELETE); + + $category = Category::factory()->create(); + + $this->actingAs($user) + ->delete(route('admin.master.categories.destroy', $category)); + + $this->assertSoftDeleted('categories', ['id' => $category->id]); + $this->assertDatabaseHas('categories', ['id' => $category->id]); + }); +}); + +// ─── Category Model ─────────────────────────────────────── + +describe('Category Model', function () { + test('category has slug auto-generated via attribute', function () { + $category = Category::factory()->create(['name' => 'Test Kategori', 'slug' => null]); + + expect($category->fresh()->slug)->toBe('test-kategori'); + }); + + test('category uses soft deletes', function () { + $category = Category::factory()->create(); + + $category->delete(); + + expect($category->trashed())->toBeTrue(); + }); + + test('category can have products', function () { + $category = Category::factory()->create(); + + expect($category->products)->toBeInstanceOf(Collection::class); + }); +});