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', fn ($page) => $page ->has('data') ->etc() ) ->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); }); });