'Bahan Baku Test', 'unit' => 'kg', 'is_active' => true, 'variants' => [ [ 'variant' => 'Varian Default', 'price' => 50000, 'stock' => 100, 'photo_key' => 'raw-material-variant/test-photo.jpg', ], ], ], $overrides); } /* |-------------------------------------------------------------------------- | AUTHENTICATION |-------------------------------------------------------------------------- */ test('guests are redirected to the login page', function () { $response = $this->get(route('admin.master.raw-materials.index')); $response->assertRedirect(route('login')); }); test('guests are redirected when visiting create page', function () { $response = $this->get(route('admin.master.raw-materials.create')); $response->assertRedirect(route('login')); }); test('guests are redirected when visiting edit page', function () { $rawMaterial = RawMaterial::factory()->create(); $response = $this->get(route('admin.master.raw-materials.edit', $rawMaterial)); $response->assertRedirect(route('login')); }); test('guest cannot create raw material', function () { $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload()); $response->assertRedirect(route('login')); $this->assertDatabaseCount('raw_materials', 0); }); test('guest cannot update raw material', function () { $rawMaterial = RawMaterial::factory()->create(); $response = $this->put(route('admin.master.raw-materials.update', $rawMaterial), makeValidRawMaterialPayload()); $response->assertRedirect(route('login')); }); test('guest cannot delete raw material', function () { $rawMaterial = RawMaterial::factory()->create(); $response = $this->delete(route('admin.master.raw-materials.destroy', $rawMaterial)); $response->assertRedirect(route('login')); $this->assertDatabaseHas('raw_materials', ['id' => $rawMaterial->id, 'deleted_at' => null]); }); test('guest cannot toggle raw material status', function () { $rawMaterial = RawMaterial::factory()->create(); $response = $this->post(route('admin.master.raw-materials.toggle-status', $rawMaterial)); $response->assertRedirect(route('login')); }); test('authenticated users can visit the raw material index page', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.master.raw-materials.index')); $response->assertOk(); }); test('authenticated users can visit the raw material create page', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.master.raw-materials.create')); $response->assertOk(); }); test('authenticated users can visit the raw material edit page', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $response = $this->get(route('admin.master.raw-materials.edit', $rawMaterial)); $response->assertOk(); }); /* |-------------------------------------------------------------------------- | INDEX PAGE |-------------------------------------------------------------------------- */ test('raw material index page displays raw materials', function () { $user = User::factory()->create(); $this->actingAs($user); RawMaterial::factory()->count(3)->create(); $response = $this->get(route('admin.master.raw-materials.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/raw-material/index') ->has('rawMaterials.data', 3) ->where('rawMaterials.total', 3) ->where('rawMaterials.current_page', 1) ->where('rawMaterials.per_page', 25) ); }); test('index page works with zero raw materials', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.master.raw-materials.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/raw-material/index') ->has('rawMaterials.data', 0) ->where('rawMaterials.total', 0) ->where('rawMaterials.current_page', 1) ->where('rawMaterials.per_page', 25) ); }); test('index page does not display soft-deleted raw materials', function () { $user = User::factory()->create(); $this->actingAs($user); RawMaterial::factory()->create(['name' => 'Active Material']); RawMaterial::factory()->create(['name' => 'Deleted Material'])->delete(); $response = $this->get(route('admin.master.raw-materials.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/raw-material/index') ->has('rawMaterials.data', 1) ->where('rawMaterials.total', 1) ->where('rawMaterials.data.0.name', 'Active Material') ); }); test('index page displays correct count after delete', function () { $user = User::factory()->create(); $this->actingAs($user); RawMaterial::factory()->count(5)->create(); $rawMaterial = RawMaterial::first(); $this->delete(route('admin.master.raw-materials.destroy', $rawMaterial)); $response = $this->get(route('admin.master.raw-materials.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/raw-material/index') ->has('rawMaterials.data', 4) ->where('rawMaterials.total', 4) ); }); test('index page includes raw material prices (variants)', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $response = $this->get(route('admin.master.raw-materials.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/raw-material/index') ->has('rawMaterials.data.0.raw_material_prices', 1) ); }); /* |-------------------------------------------------------------------------- | CREATE / STORE - BASIC |-------------------------------------------------------------------------- */ test('raw material can be created', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload()); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.master.raw-materials.index')); $this->assertDatabaseHas('raw_materials', [ 'name' => 'Bahan Baku Test', 'unit' => 'kg', 'is_active' => true, ]); $this->assertDatabaseCount('raw_material_prices', 1); }); test('raw material can be created with multiple variants', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'variants' => [ ['variant' => 'Varian 1', 'price' => 50000, 'stock' => 10, 'photo_key' => 'raw-material-variant/v1.jpg'], ['variant' => 'Varian 2', 'price' => 75000, 'stock' => 20, 'photo_key' => 'raw-material-variant/v2.jpg'], ], ])); $response->assertSessionHasNoErrors(); $this->assertDatabaseCount('raw_material_prices', 2); }); test('raw material can be created with different units', function () { $user = User::factory()->create(); $this->actingAs($user); foreach (['kg', 'meter', 'yard'] as $unit) { $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'name' => "Bahan {$unit}", 'unit' => $unit, ])); } $this->assertDatabaseCount('raw_materials', 3); }); test('raw material can be created with zero price', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'variants' => [['variant' => 'Gratis', 'price' => 0, 'stock' => 10, 'photo_key' => 'raw-material-variant/free.jpg']], ])); $response->assertSessionHasNoErrors(); $price = RawMaterialPrice::where('variant', 'Gratis')->first(); expect($price->price)->toBe(0); }); test('raw material can be created with zero stock', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'variants' => [['variant' => 'Kosong', 'price' => 50000, 'stock' => 0, 'photo_key' => 'raw-material-variant/empty.jpg']], ])); $response->assertSessionHasNoErrors(); }); test('raw material can be created with integer stock', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'variants' => [['variant' => 'Bilangan Bulat', 'price' => 50000, 'stock' => 2, 'photo_key' => 'raw-material-variant/integer.jpg']], ])); $response->assertSessionHasNoErrors(); $price = RawMaterialPrice::where('variant', 'Bilangan Bulat')->first(); expect($price->stock)->toBe(2); }); test('raw material can be created as inactive', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload(['is_active' => false])); $response->assertSessionHasNoErrors(); $rawMaterial = RawMaterial::where('name', 'Bahan Baku Test')->first(); expect($rawMaterial->is_active)->toBeFalse(); }); test('raw material default is_active is true', function () { $user = User::factory()->create(); $this->actingAs($user); $payload = makeValidRawMaterialPayload(); unset($payload['is_active']); $response = $this->post(route('admin.master.raw-materials.store'), $payload); $response->assertSessionHasNoErrors(); $rawMaterial = RawMaterial::where('name', 'Bahan Baku Test')->first(); expect($rawMaterial->is_active)->toBeTrue(); }); test('store flashes success toast via Inertia', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload()); $response->assertRedirect(); }); test('store creates new raw material in database', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload()); $this->assertDatabaseCount('raw_materials', 1); $this->assertDatabaseHas('raw_materials', ['name' => 'Bahan Baku Test']); }); test('multiple raw materials can be created sequentially', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload(['name' => 'Bahan 1'])); $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload(['name' => 'Bahan 2'])); $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload(['name' => 'Bahan 3'])); $this->assertDatabaseCount('raw_materials', 3); }); /* |-------------------------------------------------------------------------- | STORE VALIDATION - REQUIRED FIELDS |-------------------------------------------------------------------------- */ test('raw material name is required', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload(['name' => ''])); $response->assertSessionHasErrors('name'); }); test('raw material name must be string', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload(['name' => 12345])); $response->assertSessionHasErrors('name'); }); test('raw material name must not exceed 200 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload(['name' => str_repeat('a', 201)])); $response->assertSessionHasErrors('name'); }); test('raw material name exactly 200 characters passes validation', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload(['name' => str_repeat('a', 200)])); $response->assertSessionHasNoErrors(); }); test('raw material unit is required', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload(['unit' => ''])); $response->assertSessionHasErrors('unit'); }); test('raw material unit must be valid enum value', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload(['unit' => 'invalid_unit'])); $response->assertSessionHasErrors('unit'); }); test('raw material variants is required', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload(['variants' => []])); $response->assertSessionHasErrors('variants'); }); test('raw material variant name is required', function () { $user = User::factory()->create(); $this->actingAs($user); $payload = makeValidRawMaterialPayload(); $payload['variants'][0]['variant'] = ''; $response = $this->post(route('admin.master.raw-materials.store'), $payload); $response->assertSessionHasErrors('variants.0.variant'); }); test('raw material variant price is required', function () { $user = User::factory()->create(); $this->actingAs($user); $payload = makeValidRawMaterialPayload(); unset($payload['variants'][0]['price']); $response = $this->post(route('admin.master.raw-materials.store'), $payload); $response->assertSessionHasErrors('variants.0.price'); }); test('raw material variant price must be integer', function () { $user = User::factory()->create(); $this->actingAs($user); $payload = makeValidRawMaterialPayload(); $payload['variants'][0]['price'] = 'not-a-number'; $response = $this->post(route('admin.master.raw-materials.store'), $payload); $response->assertSessionHasErrors('variants.0.price'); }); test('raw material variant price must be >= 0', function () { $user = User::factory()->create(); $this->actingAs($user); $payload = makeValidRawMaterialPayload(); $payload['variants'][0]['price'] = -100; $response = $this->post(route('admin.master.raw-materials.store'), $payload); $response->assertSessionHasErrors('variants.0.price'); }); test('raw material variant stock is required', function () { $user = User::factory()->create(); $this->actingAs($user); $payload = makeValidRawMaterialPayload(); unset($payload['variants'][0]['stock']); $response = $this->post(route('admin.master.raw-materials.store'), $payload); $response->assertSessionHasErrors('variants.0.stock'); }); test('raw material variant stock must be numeric', function () { $user = User::factory()->create(); $this->actingAs($user); $payload = makeValidRawMaterialPayload(); $payload['variants'][0]['stock'] = 'abc'; $response = $this->post(route('admin.master.raw-materials.store'), $payload); $response->assertSessionHasErrors('variants.0.stock'); }); test('raw material variant stock must be >= 0', function () { $user = User::factory()->create(); $this->actingAs($user); $payload = makeValidRawMaterialPayload(); $payload['variants'][0]['stock'] = -1; $response = $this->post(route('admin.master.raw-materials.store'), $payload); $response->assertSessionHasErrors('variants.0.stock'); }); test('raw material variant photo_key is required', function () { $user = User::factory()->create(); $this->actingAs($user); $payload = makeValidRawMaterialPayload(); unset($payload['variants'][0]['photo_key']); $response = $this->post(route('admin.master.raw-materials.store'), $payload); $response->assertSessionHasErrors('variants.0.photo_key'); }); /* |-------------------------------------------------------------------------- | EDIT PAGE |-------------------------------------------------------------------------- */ test('edit page shows raw material data', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(['name' => 'Bahan Edit']); RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'variant' => 'Varian Edit', 'price' => 50000]); $response = $this->get(route('admin.master.raw-materials.edit', $rawMaterial)); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/raw-material/edit') ->where('rawMaterial.name', 'Bahan Edit') ->has('rawMaterial.raw_material_prices', 1) ); }); /* |-------------------------------------------------------------------------- | UPDATE / PUT |-------------------------------------------------------------------------- */ test('raw material can be updated', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'variant' => 'Existing']); $response = $this->put(route('admin.master.raw-materials.update', $rawMaterial), makeValidRawMaterialPayload([ 'name' => 'Bahan Updated', 'variants' => [[ 'id' => $variant->id, 'variant' => 'Existing Updated', 'price' => 75000, 'stock' => 50, 'photo_key' => 'raw-material-variant/existing.jpg', ]], ])); $response ->assertSessionHasNoErrors() ->assertRedirect(); $rawMaterial->refresh(); expect($rawMaterial->name)->toBe('Bahan Updated'); }); test('raw material update name is required', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $response = $this->put(route('admin.master.raw-materials.update', $rawMaterial), makeValidRawMaterialPayload(['name' => ''])); $response->assertSessionHasErrors('name'); }); test('raw material update name must not exceed 200 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $response = $this->put(route('admin.master.raw-materials.update', $rawMaterial), makeValidRawMaterialPayload(['name' => str_repeat('a', 201)])); $response->assertSessionHasErrors('name'); }); test('updating non-existent raw material returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.master.raw-materials.update', 999999), makeValidRawMaterialPayload(['name' => 'Ghost'])); $response->assertStatus(404); }); test('update can change raw material status', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(['is_active' => true]); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $this->put(route('admin.master.raw-materials.update', $rawMaterial), makeValidRawMaterialPayload([ 'is_active' => false, 'variants' => [[ 'id' => $variant->id, 'variant' => $variant->variant, 'price' => $variant->price, 'stock' => $variant->stock, 'photo_key' => 'raw-material-variant/v1.jpg', ]], ])); $rawMaterial->refresh(); expect($rawMaterial->is_active)->toBeFalse(); }); test('update can change unit', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(['unit' => 'kg']); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $this->put(route('admin.master.raw-materials.update', $rawMaterial), makeValidRawMaterialPayload([ 'unit' => 'meter', 'variants' => [[ 'id' => $variant->id, 'variant' => $variant->variant, 'price' => $variant->price, 'stock' => $variant->stock, 'photo_key' => 'raw-material-variant/v1.jpg', ]], ])); $rawMaterial->refresh(); expect($rawMaterial->unit->value)->toBe('meter'); }); test('update can add new variants', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $existingVariant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'variant' => 'Existing']); $this->put(route('admin.master.raw-materials.update', $rawMaterial), makeValidRawMaterialPayload([ 'variants' => [ ['id' => $existingVariant->id, 'variant' => 'Existing', 'price' => 50000, 'stock' => 10, 'photo_key' => 'raw-material-variant/existing.jpg'], ['variant' => 'New Variant', 'price' => 75000, 'stock' => 20, 'photo_key' => 'raw-material-variant/new.jpg'], ], ])); $this->assertDatabaseCount('raw_material_prices', 2); }); test('update can delete variants', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant1 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'variant' => 'Keep']); $variant2 = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'variant' => 'Delete']); $this->put(route('admin.master.raw-materials.update', $rawMaterial), makeValidRawMaterialPayload([ 'variants' => [ ['id' => $variant1->id, 'variant' => 'Keep', 'price' => 50000, 'stock' => 10, 'photo_key' => 'raw-material-variant/keep.jpg'], ], ])); $this->assertDatabaseHas('raw_material_prices', ['id' => $variant1->id, 'deleted_at' => null]); $this->assertSoftDeleted('raw_material_prices', ['id' => $variant2->id]); }); test('update can modify existing variant data', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create([ 'raw_material_id' => $rawMaterial->id, 'variant' => 'Original', 'price' => 50000, 'stock' => 10, ]); $this->put(route('admin.master.raw-materials.update', $rawMaterial), makeValidRawMaterialPayload([ 'variants' => [[ 'id' => $variant->id, 'variant' => 'Updated Name', 'price' => 99999, 'stock' => 88, 'photo_key' => 'raw-material-variant/updated.jpg', ]], ])); $variant->refresh(); expect($variant->variant)->toBe('Updated Name'); expect($variant->price)->toBe(99999); expect((float) $variant->stock)->toBe(88.0); }); /* |-------------------------------------------------------------------------- | DELETE / DESTROY |-------------------------------------------------------------------------- */ test('raw material can be deleted', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $response = $this->delete(route('admin.master.raw-materials.destroy', $rawMaterial)); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.master.raw-materials.index')); $this->assertSoftDeleted('raw_materials', ['id' => $rawMaterial->id]); }); test('raw material is soft-deleted not permanently removed', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $this->delete(route('admin.master.raw-materials.destroy', $rawMaterial)); $this->assertDatabaseHas('raw_materials', ['id' => $rawMaterial->id]); $this->assertSoftDeleted('raw_materials', ['id' => $rawMaterial->id]); }); test('soft-deleted raw material has deleted_at timestamp', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $this->delete(route('admin.master.raw-materials.destroy', $rawMaterial)); $rawMaterial->refresh(); expect($rawMaterial->deleted_at)->not->toBeNull(); }); test('delete cascades to raw material prices', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $this->delete(route('admin.master.raw-materials.destroy', $rawMaterial)); $this->assertSoftDeleted('raw_material_prices', ['id' => $variant->id]); }); test('deleting non-existent raw material returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->delete(route('admin.master.raw-materials.destroy', 999999)); $response->assertStatus(404); }); test('updating soft-deleted raw material via route returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $rawMaterial->delete(); $response = $this->put(route('admin.master.raw-materials.update', $rawMaterial), makeValidRawMaterialPayload(['name' => 'Nope'])); $response->assertStatus(404); }); test('deleting soft-deleted raw material via route returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $rawMaterial->delete(); $response = $this->delete(route('admin.master.raw-materials.destroy', $rawMaterial)); $response->assertStatus(404); }); /* |-------------------------------------------------------------------------- | TOGGLE STATUS |-------------------------------------------------------------------------- */ test('toggle status changes active to inactive', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(['is_active' => true]); $this->post(route('admin.master.raw-materials.toggle-status', $rawMaterial)); $rawMaterial->refresh(); expect($rawMaterial->is_active)->toBeFalse(); }); test('toggle status changes inactive to active', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(['is_active' => false]); $this->post(route('admin.master.raw-materials.toggle-status', $rawMaterial)); $rawMaterial->refresh(); expect($rawMaterial->is_active)->toBeTrue(); }); test('toggle status changes active to inactive then back to active', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(['is_active' => true]); $this->post(route('admin.master.raw-materials.toggle-status', $rawMaterial)); $rawMaterial->refresh(); expect($rawMaterial->is_active)->toBeFalse(); $this->post(route('admin.master.raw-materials.toggle-status', $rawMaterial)); $rawMaterial->refresh(); expect($rawMaterial->is_active)->toBeTrue(); }); test('toggle status redirects to index', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(['is_active' => true]); $response = $this->post(route('admin.master.raw-materials.toggle-status', $rawMaterial)); $response->assertRedirect(route('admin.master.raw-materials.index')); }); test('toggle status on non-existent raw material returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.toggle-status', 999999)); $response->assertStatus(404); }); /* |-------------------------------------------------------------------------- | WEIRD / EDGE CASE USER INPUTS |-------------------------------------------------------------------------- */ test('raw material name with special characters is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'name' => 'Bahan & Co. (PT) - Best!', ])); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('raw_materials', ['name' => 'Bahan & Co. (PT) - Best!']); }); test('raw material name with HTML tags is accepted as plain text', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'name' => '', ])); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('raw_materials', ['name' => '']); }); test('raw material name with emoji is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'name' => '🧵 Bahan Kain 🏷️', ])); $response->assertSessionHasNoErrors(); }); test('raw material name with only numbers is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'name' => '12345', ])); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('raw_materials', ['name' => '12345']); }); test('raw material name with SQL injection attempt is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'name' => "'; DROP TABLE raw_materials; --", ])); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('raw_materials', ['name' => "'; DROP TABLE raw_materials; --"]); }); test('raw material variant name at max length 200 is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'variants' => [['variant' => str_repeat('a', 200), 'price' => 50000, 'stock' => 10, 'photo_key' => 'raw-material-variant/maxlen.jpg']], ])); $response->assertSessionHasNoErrors(); }); test('raw material variant name over 200 characters fails validation', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'variants' => [['variant' => str_repeat('a', 201), 'price' => 50000, 'stock' => 10, 'photo_key' => 'raw-material-variant/overmax.jpg']], ])); $response->assertSessionHasErrors('variants.0.variant'); }); test('raw material stock at very large value is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'variants' => [['variant' => 'Varian Besar', 'price' => 50000, 'stock' => 99999999, 'photo_key' => 'raw-material-variant/huge.jpg']], ])); $response->assertSessionHasNoErrors(); }); test('raw material price at very large value is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([ 'variants' => [['variant' => 'Varian Mahal', 'price' => 999999999, 'stock' => 1, 'photo_key' => 'raw-material-variant/expensive.jpg']], ])); $response->assertSessionHasNoErrors(); }); /* |-------------------------------------------------------------------------- | VARIANT EDIT / UPDATE |-------------------------------------------------------------------------- */ test('variant can be updated', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'variant' => 'Old Name', 'price' => 50000, 'stock' => 10]); $response = $this->put(route('admin.master.raw-materials.variants.update', [$rawMaterial, $variant]), [ 'variant' => 'New Name', 'price' => 75000, 'stock' => 20, 'photo_key' => 'raw-material-variant/updated.jpg', ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.master.raw-materials.index')); $variant->refresh(); expect($variant->variant)->toBe('New Name'); expect($variant->price)->toBe(75000); expect((float) $variant->stock)->toBe(20.0); }); test('variant update validation requires variant name', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $response = $this->put(route('admin.master.raw-materials.variants.update', [$rawMaterial, $variant]), [ 'variant' => '', 'price' => 50000, 'stock' => 10, 'photo_key' => 'raw-material-variant/v1.jpg', ]); $response->assertSessionHasErrors('variant'); }); test('variant update validation requires price', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $response = $this->put(route('admin.master.raw-materials.variants.update', [$rawMaterial, $variant]), [ 'variant' => 'Test', 'stock' => 10, 'photo_key' => 'raw-material-variant/v1.jpg', ]); $response->assertSessionHasErrors('price'); }); test('variant update validation requires stock', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $response = $this->put(route('admin.master.raw-materials.variants.update', [$rawMaterial, $variant]), [ 'variant' => 'Test', 'price' => 50000, 'photo_key' => 'raw-material-variant/v1.jpg', ]); $response->assertSessionHasErrors('stock'); }); test('variant update validation requires photo_key', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $response = $this->put(route('admin.master.raw-materials.variants.update', [$rawMaterial, $variant]), [ 'variant' => 'Test', 'price' => 50000, 'stock' => 10, ]); $response->assertSessionHasErrors('photo_key'); }); /* |-------------------------------------------------------------------------- | VARIANT DELETE |-------------------------------------------------------------------------- */ test('variant can be deleted', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $response = $this->delete(route('admin.master.raw-materials.variants.destroy', [$rawMaterial, $variant])); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.master.raw-materials.index')); $this->assertSoftDeleted('raw_material_prices', ['id' => $variant->id]); }); test('deleting variant does not affect raw material', function () { $user = User::factory()->create(); $this->actingAs($user); $rawMaterial = RawMaterial::factory()->create(); $variant = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id]); $this->delete(route('admin.master.raw-materials.variants.destroy', [$rawMaterial, $variant])); $this->assertDatabaseHas('raw_materials', ['id' => $rawMaterial->id, 'deleted_at' => null]); });