refactor: optimize employee status management by refining toggleStatus method and ensuring it processes validated request data

This commit is contained in:
Yoga Pangestu 2026-06-22 18:30:51 +07:00
parent e3b138d410
commit 31fdfba2d1

View File

@ -0,0 +1,647 @@
<?php
use App\Enums\Permission as PermissionEnum;
use App\Models\Category;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->seed(RolePermissionSeeder::class);
Storage::fake('public');
});
// ─── Helper ───────────────────────────────────────────────
function createProductUserWithPermission(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;
}
function createProductWithVariants(?Category $category = null): Product
{
$category ??= Category::factory()->create();
$product = Product::factory()->create();
$product->categories()->attach($category->id);
$variants = ProductVariant::factory()->count(2)->create(['product_id' => $product->id]);
foreach ($variants as $variant) {
$variant->addMedia(UploadedFile::fake()->image("{$variant->name}.jpg", 100, 100))
->toMediaCollection('images');
}
return $product;
}
function variantWithImage(string $name = 'All Size', int $stock = 10): array
{
return [
'name' => $name,
'stock' => $stock,
'images' => [UploadedFile::fake()->image("{$name}.jpg", 100, 100)],
];
}
// ─── Index ────────────────────────────────────────────────
describe('Product Index', function () {
test('authenticated user with permission can view product index', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW);
createProductWithVariants();
$this->actingAs($user)
->get(route('admin.master.products.index'))
->assertOk();
});
test('guest is redirected to login', function () {
$this->get(route('admin.master.products.index'))
->assertRedirect(route('login'));
});
test('user without permission is forbidden', function () {
$user = User::factory()->create();
$this->actingAs($user)
->get(route('admin.master.products.index'))
->assertForbidden();
});
test('index displays products with variants and categories', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW);
createProductWithVariants();
createProductWithVariants();
$this->actingAs($user)
->get(route('admin.master.products.index'))
->assertOk();
});
test('index can search products by name', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW);
$category = Category::factory()->create();
$product = Product::factory()->create(['name' => 'Batik Modern']);
$product->categories()->attach($category->id);
ProductVariant::factory()->create(['product_id' => $product->id]);
$this->actingAs($user)
->get(route('admin.master.products.index', ['search' => 'Batik']))
->assertOk();
});
test('index can filter by active status', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW);
createProductWithVariants();
$this->actingAs($user)
->get(route('admin.master.products.index', ['is_active' => '1']))
->assertOk();
});
test('index can filter by category', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW);
$category = Category::factory()->create();
createProductWithVariants($category);
$this->actingAs($user)
->get(route('admin.master.products.index', ['category_id' => $category->id]))
->assertOk();
});
});
// ─── Create ───────────────────────────────────────────────
describe('Product Create', function () {
test('authenticated user with permission can view create form', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$this->actingAs($user)
->get(route('admin.master.products.create'))
->assertOk();
});
test('guest is redirected to login', function () {
$this->get(route('admin.master.products.create'))
->assertRedirect(route('login'));
});
test('user without create permission is forbidden', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW);
$this->actingAs($user)
->get(route('admin.master.products.create'))
->assertForbidden();
});
});
// ─── Store ────────────────────────────────────────────────
describe('Product Store', function () {
test('authenticated user with permission can create a product', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$category = Category::factory()->create();
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => 'Produk Baru',
'description' => 'Deskripsi produk',
'category_ids' => [$category->id],
'variants' => [
variantWithImage('All Size', 10),
],
])
->assertRedirect(route('admin.master.products.index'));
$this->assertDatabaseHas('products', [
'name' => 'Produk Baru',
]);
});
test('guest cannot create a product', function () {
$category = Category::factory()->create();
$this->post(route('admin.master.products.store'), [
'name' => 'Produk Baru',
'category_ids' => [$category->id],
'variants' => [variantWithImage()],
])->assertRedirect(route('login'));
});
test('user without create permission is forbidden', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW);
$category = Category::factory()->create();
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => 'Produk Baru',
'category_ids' => [$category->id],
'variants' => [variantWithImage()],
])
->assertForbidden();
});
test('name is required', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$category = Category::factory()->create();
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => '',
'category_ids' => [$category->id],
'variants' => [variantWithImage()],
])
->assertSessionHasErrors('name');
});
test('name must not exceed 200 characters', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$category = Category::factory()->create();
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => str_repeat('a', 201),
'category_ids' => [$category->id],
'variants' => [variantWithImage()],
])
->assertSessionHasErrors('name');
});
test('category_ids is required', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => 'Produk Baru',
'category_ids' => [],
'variants' => [variantWithImage()],
])
->assertSessionHasErrors('category_ids');
});
test('category must exist', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => 'Produk Baru',
'category_ids' => [99999],
'variants' => [variantWithImage()],
])
->assertSessionHasErrors('category_ids.0');
});
test('variants is required', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$category = Category::factory()->create();
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => 'Produk Baru',
'category_ids' => [$category->id],
'variants' => [],
])
->assertSessionHasErrors('variants');
});
test('variant name is required', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$category = Category::factory()->create();
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => 'Produk Baru',
'category_ids' => [$category->id],
'variants' => [variantWithImage('', 10)],
])
->assertSessionHasErrors('variants.0.name');
});
test('variant stock is required', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$category = Category::factory()->create();
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => 'Produk Baru',
'category_ids' => [$category->id],
'variants' => [['name' => 'All Size', 'stock' => '', 'images' => [UploadedFile::fake()->image('test.jpg')]]],
])
->assertSessionHasErrors('variants.0.stock');
});
test('variant stock must be non-negative integer', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$category = Category::factory()->create();
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => 'Produk Baru',
'category_ids' => [$category->id],
'variants' => [variantWithImage('All Size', -5)],
])
->assertSessionHasErrors('variants.0.stock');
});
test('creating product also creates variants and syncs categories', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$cat1 = Category::factory()->create();
$cat2 = Category::factory()->create();
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => 'Produk Lengkap',
'description' => 'Deskripsi lengkap',
'category_ids' => [$cat1->id, $cat2->id],
'variants' => [
variantWithImage('S', 5),
variantWithImage('M', 10),
variantWithImage('L', 15),
],
]);
$product = Product::where('name', 'Produk Lengkap')->first();
expect($product)->not->toBeNull();
expect($product->categories)->toHaveCount(2);
expect($product->variants)->toHaveCount(3);
});
test('product slug is auto-generated', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE);
$category = Category::factory()->create();
$this->actingAs($user)
->post(route('admin.master.products.store'), [
'name' => 'Batik Modern Elegan',
'category_ids' => [$category->id],
'variants' => [variantWithImage()],
]);
$this->assertDatabaseHas('products', [
'name' => 'Batik Modern Elegan',
'slug' => 'batik-modern-elegan',
]);
});
});
// ─── Edit ─────────────────────────────────────────────────
describe('Product Edit', function () {
test('authenticated user with permission can view edit form', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_UPDATE);
$product = createProductWithVariants();
$this->actingAs($user)
->get(route('admin.master.products.edit', $product))
->assertOk();
});
test('guest is redirected to login', function () {
$product = createProductWithVariants();
$this->get(route('admin.master.products.edit', $product))
->assertRedirect(route('login'));
});
test('user without update permission is forbidden', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW);
$product = createProductWithVariants();
$this->actingAs($user)
->get(route('admin.master.products.edit', $product))
->assertForbidden();
});
});
// ─── Update ───────────────────────────────────────────────
describe('Product Update', function () {
test('authenticated user with permission can update a product', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_UPDATE);
$product = createProductWithVariants();
$category = $product->categories->first();
$variant = $product->variants->first();
$this->actingAs($user)
->put(route('admin.master.products.update', $product), [
'name' => 'Nama Diubah',
'description' => 'Deskripsi baru',
'category_ids' => [$category->id],
'variants' => [
['id' => $variant->id, 'name' => 'New Variant', 'stock' => 20],
],
])
->assertRedirect(route('admin.master.products.index'));
expect($product->fresh()->name)->toBe('Nama Diubah');
expect($variant->fresh()->name)->toBe('New Variant');
});
test('guest cannot update a product', function () {
$product = createProductWithVariants();
$variant = $product->variants->first();
$this->put(route('admin.master.products.update', $product), [
'name' => 'Nama Baru',
'category_ids' => [$product->categories->first()->id],
'variants' => [['id' => $variant->id, 'name' => $variant->name, 'stock' => $variant->stock]],
])->assertRedirect(route('login'));
});
test('user without update permission is forbidden', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW);
$product = createProductWithVariants();
$variant = $product->variants->first();
$this->actingAs($user)
->put(route('admin.master.products.update', $product), [
'name' => 'Nama Baru',
'category_ids' => [$product->categories->first()->id],
'variants' => [['id' => $variant->id, 'name' => $variant->name, 'stock' => $variant->stock]],
])
->assertForbidden();
});
test('name is required on update', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_UPDATE);
$product = createProductWithVariants();
$category = $product->categories->first();
$variant = $product->variants->first();
$this->actingAs($user)
->put(route('admin.master.products.update', $product), [
'name' => '',
'category_ids' => [$category->id],
'variants' => [
['id' => $variant->id, 'name' => $variant->name, 'stock' => $variant->stock],
],
])
->assertSessionHasErrors('name');
});
test('category_ids is required on update', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_UPDATE);
$product = createProductWithVariants();
$variant = $product->variants->first();
$this->actingAs($user)
->put(route('admin.master.products.update', $product), [
'name' => 'Nama Produk',
'category_ids' => [],
'variants' => [
['id' => $variant->id, 'name' => $variant->name, 'stock' => $variant->stock],
],
])
->assertSessionHasErrors('category_ids');
});
test('unsubmitted variants are removed on update', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_UPDATE);
$product = createProductWithVariants();
$category = $product->categories->first();
$variantToKeep = $product->variants->first();
$this->actingAs($user)
->put(route('admin.master.products.update', $product), [
'name' => 'Updated Product',
'category_ids' => [$category->id],
'variants' => [
['id' => $variantToKeep->id, 'name' => 'Kept Variant', 'stock' => 5],
],
]);
$product->refresh();
expect($product->variants)->toHaveCount(1);
expect($product->variants->first()->name)->toBe('Kept Variant');
});
});
// ─── Toggle Status ────────────────────────────────────────
describe('Product Toggle Status', function () {
test('authenticated user with permission can toggle product status', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_TOGGLE_STATUS);
$product = createProductWithVariants();
$this->actingAs($user)
->patch(route('admin.master.products.toggle_status', $product), [
'is_active' => false,
])
->assertRedirect();
expect($product->fresh()->is_active)->toBeFalse();
});
test('guest cannot toggle product status', function () {
$product = createProductWithVariants();
$this->patch(route('admin.master.products.toggle_status', $product), [
'is_active' => false,
])->assertRedirect(route('login'));
});
test('user without toggle permission is forbidden', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW);
$product = createProductWithVariants();
$this->actingAs($user)
->patch(route('admin.master.products.toggle_status', $product), [
'is_active' => false,
])
->assertForbidden();
});
test('is_active field is required', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_TOGGLE_STATUS);
$product = createProductWithVariants();
$this->actingAs($user)
->patch(route('admin.master.products.toggle_status', $product), [
'is_active' => null,
])
->assertSessionHasErrors('is_active');
});
});
// ─── Destroy ──────────────────────────────────────────────
describe('Product Destroy', function () {
test('authenticated user with permission can delete a product', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_DELETE);
$product = createProductWithVariants();
$this->actingAs($user)
->delete(route('admin.master.products.destroy', $product))
->assertRedirect(route('admin.master.products.index'));
$this->assertSoftDeleted('products', ['id' => $product->id]);
});
test('guest cannot delete a product', function () {
$product = createProductWithVariants();
$this->delete(route('admin.master.products.destroy', $product))
->assertRedirect(route('login'));
$this->assertNotSoftDeleted('products', ['id' => $product->id]);
});
test('user without delete permission is forbidden', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW);
$product = createProductWithVariants();
$this->actingAs($user)
->delete(route('admin.master.products.destroy', $product))
->assertForbidden();
$this->assertNotSoftDeleted('products', ['id' => $product->id]);
});
test('deleting product also soft deletes variants', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_DELETE);
$product = createProductWithVariants();
$variantIds = $product->variants->pluck('id')->toArray();
$this->actingAs($user)
->delete(route('admin.master.products.destroy', $product));
$this->assertSoftDeleted('products', ['id' => $product->id]);
foreach ($variantIds as $variantId) {
$this->assertSoftDeleted('product_variants', ['id' => $variantId]);
}
});
test('deleting product detaches categories', function () {
$user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_DELETE);
$product = createProductWithVariants();
$this->actingAs($user)
->delete(route('admin.master.products.destroy', $product));
$this->assertDatabaseMissing('product_categories', ['product_id' => $product->id]);
});
});
// ─── Product Model ────────────────────────────────────────
describe('Product Model', function () {
test('product uses soft deletes', function () {
$product = Product::factory()->create();
$product->delete();
expect($product->trashed())->toBeTrue();
});
test('product can have categories', function () {
$product = Product::factory()->create();
$category = Category::factory()->create();
$product->categories()->attach($category->id);
expect($product->fresh()->categories)->toHaveCount(1);
});
test('product can have variants', function () {
$product = Product::factory()->create();
ProductVariant::factory()->count(3)->create(['product_id' => $product->id]);
expect($product->fresh()->variants)->toHaveCount(3);
});
test('product has is_active cast to boolean', function () {
$product = Product::factory()->create(['is_active' => true]);
expect($product->is_active)->toBeTrue();
$product->update(['is_active' => false]);
expect($product->fresh()->is_active)->toBeFalse();
});
});