dstpabuaran.com/tests/Feature/Admin/Master/RawMaterialTest.php
Yoga Pangestu 384460686e feat: implement raw material management features including CRUD operations and draft handling
- Added RawMaterialController and RawMaterialVariantController for managing raw materials and their variants.
- Introduced RawMaterialRequest and RawMaterialVariantRequest for validation.
- Implemented RawMaterialService and RawMaterialVariantService for business logic.
- Created hooks for saving drafts of raw materials.
- Developed UI components for creating, editing, and listing raw materials and variants.
2026-08-02 00:54:05 +07:00

1070 lines
37 KiB
PHP

<?php
use App\Models\RawMaterial;
use App\Models\RawMaterialPrice;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
/*
|--------------------------------------------------------------------------
| HELPERS
|--------------------------------------------------------------------------
*/
function makeValidRawMaterialPayload(array $overrides = []): array
{
return array_merge([
'name' => '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 decimal stock', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.raw-materials.store'), makeValidRawMaterialPayload([
'variants' => [['variant' => 'Desimal', 'price' => 50000, 'stock' => 1.5, 'photo_key' => 'raw-material-variant/decimal.jpg']],
]));
$response->assertSessionHasNoErrors();
$price = RawMaterialPrice::where('variant', 'Desimal')->first();
expect((float) $price->stock)->toBe(1.5);
});
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' => '<script>alert("xss")</script>',
]));
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('raw_materials', ['name' => '<script>alert("xss")</script>']);
});
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]);
});