diff --git a/tests/Feature/Admin/Master/ProductTest.php b/tests/Feature/Admin/Master/ProductTest.php new file mode 100644 index 0000000..0e7599b --- /dev/null +++ b/tests/Feature/Admin/Master/ProductTest.php @@ -0,0 +1,273 @@ +assertRedirect(route('login')); + }); + + it('returns 403 when user has no permission to view products', function () { + actingAs(createUnauthorizedUser()) + ->get(route('product.index')) + ->assertStatus(403); + }); +}); + +describe('Product Module - Authorized Actions', function () { + beforeEach(function () { + $user = createAuthorizedUser([ + 'View:Product', + 'Create:Product', + 'Edit:Product', + 'Delete:Product', + 'DeleteAny:Product', + 'ToggleStatus:Product', + ]); + actingAs($user); + }); + + it('can access product index page', function () { + get(route('product.index')) + ->assertOk() + ->assertInertia(fn ($page) => $page + ->component('admin/master/product/index') + ->has('products') + ->has('categories') + ); + }); + + it('can access product create page', function () { + get(route('product.create')) + ->assertOk() + ->assertInertia(fn ($page) => $page + ->component('admin/master/product/create') + ->has('categories') + ); + }); + + it('can store a new product', function () { + Storage::fake('public'); + $categories = Category::factory()->count(2)->create(['is_active' => true]); + + $data = [ + 'name' => 'New Awesome Product', + 'description' => 'Product description', + 'category_ids' => $categories->pluck('id')->toArray(), + 'prices' => [ + 'purchase' => 10000, + 'distributor' => 11000, + 'agent' => 12000, + 'reseller' => 13000, + 'retail' => 15000, + ], + 'stock' => 10, + 'thumbnail' => UploadedFile::fake()->image('thumbnail.jpg'), + ]; + + postJson(route('product.store'), $data) + ->assertRedirect(route('product.index')) + ->assertSessionHas('success'); + + assertDatabaseHas('products', [ + 'name' => 'New Awesome Product', + 'description' => 'Product description', + ]); + + $product = Product::where('name', 'New Awesome Product')->first(); + + // Check categories + expect($product->categories)->toHaveCount(2); + + // Check prices + foreach ($data['prices'] as $type => $price) { + assertDatabaseHas('product_prices', [ + 'product_id' => $product->id, + 'price_type' => $type, + 'price' => $price, + ]); + } + }); + + it('validates product creation', function () { + postJson(route('product.store'), []) + ->assertStatus(422) + ->assertJsonValidationErrors(['name', 'category_ids', 'prices', 'stock']); + }); + + it('can access product edit page', function () { + $product = Product::factory()->create(); + + get(route('product.edit', $product)) + ->assertOk() + ->assertInertia(fn ($page) => $page + ->component('admin/master/product/edit') + ->has('product') + ->has('categories') + ); + }); + + it('can update a product', function () { + $product = Product::factory()->create(['name' => 'Old Product Name']); + $categories = Category::factory()->count(1)->create(['is_active' => true]); + + $newData = [ + 'name' => 'Updated Product Name', + 'description' => 'Updated description', + 'category_ids' => $categories->pluck('id')->toArray(), + 'prices' => [ + 'purchase' => 20000, + 'distributor' => 21000, + 'agent' => 22000, + 'reseller' => 23000, + 'retail' => 25000, + ], + 'stock' => 50, + ]; + + patchJson(route('product.update', $product), $newData) + ->assertRedirect(route('product.index')) + ->assertSessionHas('success'); + + assertDatabaseHas('products', [ + 'id' => $product->id, + 'name' => 'Updated Product Name', + 'stock' => 50, + ]); + }); + + it('can delete a product', function () { + $product = Product::factory()->create(); + + deleteJson(route('product.destroy', $product)) + ->assertRedirect() + ->assertSessionHas('success'); + + assertSoftDeleted('products', ['id' => $product->id]); + }); + + it('can delete products in bulk', function () { + $products = Product::factory()->count(3)->create(); + $ids = $products->pluck('id')->toArray(); + + deleteJson(route('product.bulkDestroy'), ['ids' => $ids]) + ->assertRedirect() + ->assertSessionHas('success'); + + foreach ($ids as $id) { + assertSoftDeleted('products', ['id' => $id]); + } + }); + + it('can toggle product status', function () { + $product = Product::factory()->create(['is_active' => true]); + + patchJson(route('product.toggleStatus', $product)) + ->assertRedirect() + ->assertSessionHas('success'); + + expect($product->fresh()->is_active)->toBeFalse(); + + patchJson(route('product.toggleStatus', $product)) + ->assertRedirect(); + + expect($product->fresh()->is_active)->toBeTrue(); + }); + + it('can manage product images', function () { + Storage::fake('public'); + $product = Product::factory()->create(); + $categories = Category::factory()->count(1)->create(['is_active' => true]); + + // 1. Upload multiple images + $images = [ + UploadedFile::fake()->image('image1.jpg'), + UploadedFile::fake()->image('image2.jpg'), + ]; + + $data = [ + 'name' => $product->name, + 'description' => 'Some description', + 'category_ids' => $categories->pluck('id')->toArray(), + 'prices' => [ + 'purchase' => 10000, + 'distributor' => 11000, + 'agent' => 12000, + 'reseller' => 13000, + 'retail' => 15000, + ], + 'images' => $images, + ]; + + patch(route('product.update', $product), $data) + ->assertRedirect() + ->assertSessionHas('success'); + + $product->refresh(); + expect($product->getMedia('images'))->toHaveCount(2); + + // 2. Delete one image + $mediaIdToDelete = $product->getMedia('images')->first()->id; + + $deleteData = array_merge($data, [ + 'delete_image_ids' => [$mediaIdToDelete], + 'images' => [], // Don't upload new ones + ]); + + patch(route('product.update', $product), $deleteData) + ->assertRedirect() + ->assertSessionHas('success'); + + $product->refresh(); + expect($product->getMedia('images'))->toHaveCount(1); + expect($product->getMedia('images')->first()->id)->not->toBe($mediaIdToDelete); + }); +}); + +describe('Product Module - Unauthorized Actions', function () { + beforeEach(function () { + actingAs(createUnauthorizedUser()); + }); + + it('cannot store a product without permission', function () { + postJson(route('product.store'), ['name' => 'Unauthorized']) + ->assertStatus(403); + }); + + it('cannot update a product without permission', function () { + $product = Product::factory()->create(); + patchJson(route('product.update', $product), ['name' => 'Unauthorized']) + ->assertStatus(403); + }); + + it('cannot delete a product without permission', function () { + $product = Product::factory()->create(); + deleteJson(route('product.destroy', $product)) + ->assertStatus(403); + }); + + it('cannot toggle product status without permission', function () { + $product = Product::factory()->create(); + patchJson(route('product.toggleStatus', $product)) + ->assertStatus(403); + }); +});