dstpabuaran.com/tests/Feature/Admin/Master/SupplierTest.php

1823 lines
54 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Models\Purchase;
use App\Models\Supplier;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
/*
|--------------------------------------------------------------------------
| AUTHENTICATION
|--------------------------------------------------------------------------
*/
test('guests are redirected to the login page', function () {
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertRedirect(route('login'));
});
test('authenticated users can visit the supplier index page', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertOk();
});
/*
|--------------------------------------------------------------------------
| INDEX PAGE
|--------------------------------------------------------------------------
*/
test('supplier index page displays suppliers', function () {
$user = User::factory()->create();
$this->actingAs($user);
Supplier::factory()->count(3)->create();
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 3)
);
});
test('index page works with zero suppliers', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 0)
);
});
test('index page does not display soft-deleted suppliers', function () {
$user = User::factory()->create();
$this->actingAs($user);
Supplier::factory()->create(['name' => 'Active Supplier']);
Supplier::factory()->create(['name' => 'Deleted Supplier'])->delete();
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 1)
->where('suppliers.0.name', 'Active Supplier')
);
});
test('index page displays correct count after delete', function () {
$user = User::factory()->create();
$this->actingAs($user);
Supplier::factory()->count(5)->create();
$supplier = Supplier::first();
$this->delete(route('admin.master.suppliers.destroy', $supplier));
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 4)
);
});
/*
|--------------------------------------------------------------------------
| CREATE / STORE
|--------------------------------------------------------------------------
*/
test('supplier can be created', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Test',
'phone_number' => '08123456789',
'address' => 'Jl. Test No. 1',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.master.suppliers.index'));
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier Test',
'phone_number' => '08123456789',
'address' => 'Jl. Test No. 1',
]);
});
test('supplier can be created without optional fields', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Minimal',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.master.suppliers.index'));
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier Minimal',
]);
});
test('supplier name is required', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => '',
]);
$response->assertSessionHasErrors('name');
});
test('supplier name must be string', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 12345,
]);
$response->assertSessionHasErrors('name');
});
test('supplier name must not exceed 200 characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => str_repeat('a', 201),
]);
$response->assertSessionHasErrors('name');
});
test('supplier name exactly 200 characters passes validation', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => str_repeat('a', 200),
]);
$response->assertSessionHasNoErrors();
});
test('supplier name exactly 1 character passes validation', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'A',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'A',
]);
});
test('supplier name must be unique', function () {
$user = User::factory()->create();
$this->actingAs($user);
Supplier::factory()->create(['name' => 'Existing Supplier']);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Existing Supplier',
]);
$response->assertSessionHasErrors('name');
});
test('supplier name uniqueness is case sensitive', function () {
$user = User::factory()->create();
$this->actingAs($user);
Supplier::factory()->create(['name' => 'Supplier ABC']);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier ABC',
]);
$response->assertSessionHasErrors('name');
});
test('supplier name with only whitespace is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => ' ',
]);
$response->assertSessionHasErrors('name');
});
test('supplier name with special characters is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier & Co. (PT)',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier & Co. (PT)',
]);
});
test('supplier name with HTML tags is accepted as plain text', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => '<script>alert("xss")</script>',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => '<script>alert("xss")</script>',
]);
});
test('supplier name with numbers is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier 123',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier 123',
]);
});
test('supplier name with only numbers is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => '12345',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => '12345',
]);
});
test('supplier name with unicode characters is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Nono',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier Nono',
]);
});
test('supplier name with mixed case is stored as entered', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'sUpPlIeR tEsT',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'sUpPlIeR tEsT',
]);
});
test('supplier name with leading and trailing spaces is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => ' Supplier Spasi ',
]);
$response->assertSessionHasNoErrors();
});
test('supplier name with parentheses and brackets is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier (A) [VIP]',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier (A) [VIP]',
]);
});
test('supplier name with multiple spaces is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier dengan spasi banyak',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier dengan spasi banyak',
]);
});
test('supplier can be created with name at exact DB column limit (200 chars)', function () {
$user = User::factory()->create();
$this->actingAs($user);
$name = str_repeat('a', 200);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => $name,
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => $name,
]);
});
test('store flashes success toast via Inertia', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Toast',
]);
$response->assertRedirect();
});
test('store creates new supplier in database', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), [
'name' => 'Database Check',
]);
$this->assertDatabaseCount('suppliers', 1);
$this->assertDatabaseHas('suppliers', [
'name' => 'Database Check',
]);
});
test('multiple suppliers can be created sequentially', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Supplier 1']);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Supplier 2']);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Supplier 3']);
$this->assertDatabaseCount('suppliers', 3);
$this->assertDatabaseHas('suppliers', ['name' => 'Supplier 1']);
$this->assertDatabaseHas('suppliers', ['name' => 'Supplier 2']);
$this->assertDatabaseHas('suppliers', ['name' => 'Supplier 3']);
});
/*
|--------------------------------------------------------------------------
| PHONE NUMBER VALIDATION
|--------------------------------------------------------------------------
*/
test('supplier phone_number must be numeric', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier With Letters',
'phone_number' => 'abc123',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number accepts valid numeric string', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Valid Phone',
'phone_number' => '08123456789012345678',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier Valid Phone',
'phone_number' => '08123456789012345678',
]);
});
test('supplier phone_number must not exceed 20 digits', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Long Phone',
'phone_number' => '081234567890123456789',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number exactly 20 digits passes validation', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Max Phone',
'phone_number' => '12345678901234567890',
]);
$response->assertSessionHasNoErrors();
});
test('supplier phone_number exactly 1 digit passes validation', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Min Phone',
'phone_number' => '1',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier Min Phone',
'phone_number' => '1',
]);
});
test('supplier phone_number can be null', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier No Phone',
'phone_number' => null,
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier No Phone',
'phone_number' => null,
]);
});
test('supplier phone_number with special characters is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Special Phone',
'phone_number' => '+62-812-3456-7890',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number with spaces is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Space Phone',
'phone_number' => '0812 345 678',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number with dashes is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Dash Phone',
'phone_number' => '0812-3456-7890',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number with dots is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Dot Phone',
'phone_number' => '0812.3456.7890',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number with parentheses is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Paren Phone',
'phone_number' => '(0812)3456789',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number with plus sign is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Plus Phone',
'phone_number' => '+6281234567890',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number with hash is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Hash Phone',
'phone_number' => '#08123456789',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number with asterisk is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Star Phone',
'phone_number' => '*08123456789',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number with letters and numbers is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Mixed Phone',
'phone_number' => '0812abc3456',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number with only letters is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Letter Phone',
'phone_number' => 'abcdefghij',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier phone_number with empty string is accepted as nullable', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Empty Phone',
'phone_number' => '',
]);
$response->assertSessionHasNoErrors();
});
/*
|--------------------------------------------------------------------------
| ADDRESS VALIDATION
|--------------------------------------------------------------------------
*/
test('supplier address can be null', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier No Address',
'address' => null,
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier No Address',
'address' => null,
]);
});
test('supplier address accepts long text', function () {
$user = User::factory()->create();
$this->actingAs($user);
$address = str_repeat('Jl. Panjang No. 123, RT 01/RW 02, Kelurahan Sukamaju, Kecamatan Menteng, Jakarta Pusat. ', 10);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Long Address',
'address' => $address,
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier Long Address',
]);
});
test('supplier address with special characters is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Special Address',
'address' => 'Jl. Raya No. 1/2, Lt. 3 (Gedung ABC)',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier Special Address',
'address' => 'Jl. Raya No. 1/2, Lt. 3 (Gedung ABC)',
]);
});
test('supplier address with unicode is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Unicode Address',
'address' => 'Jl. Sudirman No. 123, Jakarta',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier Unicode Address',
'address' => 'Jl. Sudirman No. 123, Jakarta',
]);
});
test('supplier address with newlines is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Multiline Address',
'address' => "Jl. Raya No. 1\nKota Bandung\nJawa Barat",
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier Multiline Address',
]);
});
test('supplier address with HTML is accepted as plain text', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier HTML Address',
'address' => '<b>Jl. Raya</b> No. 1',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier HTML Address',
'address' => '<b>Jl. Raya</b> No. 1',
]);
});
test('supplier address can be empty string', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Empty Address',
'address' => '',
]);
$response->assertSessionHasNoErrors();
});
/*
|--------------------------------------------------------------------------
| UPDATE / PUT
|--------------------------------------------------------------------------
*/
test('supplier can be updated', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Supplier Updated',
'phone_number' => '0987654321',
'address' => 'Jl. Updated No. 2',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.master.suppliers.index'));
$supplier->refresh();
expect($supplier->name)->toBe('Supplier Updated');
expect($supplier->phone_number)->toBe('0987654321');
expect($supplier->address)->toBe('Jl. Updated No. 2');
});
test('supplier name can be updated to itself', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create(['name' => 'My Supplier']);
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'My Supplier',
]);
$response->assertSessionHasNoErrors();
});
test('supplier update name must be unique excluding itself', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create(['name' => 'First']);
Supplier::factory()->create(['name' => 'Second']);
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Second',
]);
$response->assertSessionHasErrors('name');
});
test('supplier update name is required', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => '',
]);
$response->assertSessionHasErrors('name');
});
test('supplier update name must not exceed 200 characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => str_repeat('a', 201),
]);
$response->assertSessionHasErrors('name');
});
test('supplier update phone_number must be numeric', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Updated Supplier',
'phone_number' => 'abc123',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier update phone_number with special characters is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Updated Supplier',
'phone_number' => '+62-812-3456-7890',
]);
$response->assertSessionHasErrors('phone_number');
});
test('supplier can be updated with only name', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create(['phone_number' => '08123456789', 'address' => 'Old Address']);
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Updated Name Only',
]);
$response->assertSessionHasNoErrors();
$supplier->refresh();
expect($supplier->name)->toBe('Updated Name Only');
expect($supplier->phone_number)->toBe('08123456789');
expect($supplier->address)->toBe('Old Address');
});
test('supplier can be updated multiple times', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create(['name' => 'Version 1']);
$this->put(route('admin.master.suppliers.update', $supplier), ['name' => 'Version 2']);
$supplier->refresh();
expect($supplier->name)->toBe('Version 2');
$this->put(route('admin.master.suppliers.update', $supplier), ['name' => 'Version 3']);
$supplier->refresh();
expect($supplier->name)->toBe('Version 3');
});
test('update flashes success toast via Inertia', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Updated Name',
]);
$response->assertRedirect();
});
test('supplier update with special characters in name is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Supplier & Co. (PT)',
]);
$response->assertSessionHasNoErrors();
$supplier->refresh();
expect($supplier->name)->toBe('Supplier & Co. (PT)');
});
test('supplier update with unicode characters is accepted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create(['name' => 'Original']);
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Supplier Nono',
]);
$response->assertSessionHasNoErrors();
$supplier->refresh();
expect($supplier->name)->toBe('Supplier Nono');
});
test('supplier update with only whitespace name is rejected', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => ' ',
]);
$response->assertSessionHasErrors('name');
});
test('supplier update can clear phone_number to null', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create(['phone_number' => '08123456789']);
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => $supplier->name,
'phone_number' => null,
]);
$response->assertSessionHasNoErrors();
$supplier->refresh();
expect($supplier->phone_number)->toBeNull();
});
test('supplier update can clear address to null', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create(['address' => 'Old Address']);
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => $supplier->name,
'address' => null,
]);
$response->assertSessionHasNoErrors();
$supplier->refresh();
expect($supplier->address)->toBeNull();
});
test('updating non-existent supplier returns 404', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->put(route('admin.master.suppliers.update', 999999), [
'name' => 'Ghost Supplier',
]);
$response->assertStatus(404);
});
/*
|--------------------------------------------------------------------------
| DELETE / DESTROY
|--------------------------------------------------------------------------
*/
test('supplier can be deleted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$response = $this->delete(route('admin.master.suppliers.destroy', $supplier));
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.master.suppliers.index'));
$this->assertSoftDeleted('suppliers', ['id' => $supplier->id]);
});
test('supplier is soft-deleted not permanently removed', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$this->delete(route('admin.master.suppliers.destroy', $supplier));
$this->assertDatabaseHas('suppliers', ['id' => $supplier->id]);
$this->assertSoftDeleted('suppliers', ['id' => $supplier->id]);
});
test('soft-deleted supplier still exists in database', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$this->delete(route('admin.master.suppliers.destroy', $supplier));
$this->assertDatabaseHas('suppliers', ['id' => $supplier->id]);
});
test('soft-deleted supplier has deleted_at timestamp', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$this->delete(route('admin.master.suppliers.destroy', $supplier));
$supplier->refresh();
expect($supplier->deleted_at)->not->toBeNull();
});
test('deleted supplier is excluded from getAll query', function () {
$user = User::factory()->create();
$this->actingAs($user);
Supplier::factory()->create(['name' => 'Active']);
$deleted = Supplier::factory()->create(['name' => 'Deleted']);
$deleted->delete();
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 1)
->where('suppliers.0.name', 'Active')
);
});
test('delete flashes success toast via Inertia', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$response = $this->delete(route('admin.master.suppliers.destroy', $supplier));
$response->assertRedirect();
});
test('multiple suppliers can be deleted one by one', function () {
$user = User::factory()->create();
$this->actingAs($user);
$suppliers = Supplier::factory()->count(3)->create();
$this->delete(route('admin.master.suppliers.destroy', $suppliers[0]));
$this->delete(route('admin.master.suppliers.destroy', $suppliers[1]));
$this->assertSoftDeleted('suppliers', ['id' => $suppliers[0]->id]);
$this->assertSoftDeleted('suppliers', ['id' => $suppliers[1]->id]);
$this->assertDatabaseHas('suppliers', ['id' => $suppliers[2]->id, 'deleted_at' => null]);
});
test('updating soft-deleted supplier via route returns 404', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$supplier->delete();
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Should Not Work',
]);
$response->assertStatus(404);
});
test('deleting soft-deleted supplier via route returns 404', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$supplier->delete();
$response = $this->delete(route('admin.master.suppliers.destroy', $supplier));
$response->assertStatus(404);
});
test('deleting non-existent supplier returns 404', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->delete(route('admin.master.suppliers.destroy', 999999));
$response->assertStatus(404);
});
/*
|--------------------------------------------------------------------------
| SUPPLIER - PURCHASE RELATIONSHIP
|--------------------------------------------------------------------------
*/
test('supplier with purchases can be deleted', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
Purchase::factory()->count(3)->create(['supplier_id' => $supplier->id]);
$this->delete(route('admin.master.suppliers.destroy', $supplier));
$this->assertSoftDeleted('suppliers', ['id' => $supplier->id]);
// Purchases still exist because soft delete doesn't trigger cascade
$this->assertDatabaseCount('purchases', 3);
});
test('index page works correctly with suppliers that have purchases', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
Purchase::factory()->count(5)->create(['supplier_id' => $supplier->id]);
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 1)
);
});
/*
|--------------------------------------------------------------------------
| AUTHORIZATION - GUEST CANNOT PERFORM ACTIONS
|--------------------------------------------------------------------------
*/
test('guest cannot create supplier', function () {
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Unauthorized',
]);
$response->assertRedirect(route('login'));
$this->assertDatabaseMissing('suppliers', ['name' => 'Unauthorized']);
});
test('guest cannot update supplier', function () {
$supplier = Supplier::factory()->create();
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Unauthorized Update',
]);
$response->assertRedirect(route('login'));
});
test('guest cannot delete supplier', function () {
$supplier = Supplier::factory()->create();
$response = $this->delete(route('admin.master.suppliers.destroy', $supplier));
$response->assertRedirect(route('login'));
$this->assertDatabaseHas('suppliers', ['id' => $supplier->id, 'deleted_at' => null]);
});
/*
|--------------------------------------------------------------------------
| DATA INTEGRITY
|--------------------------------------------------------------------------
*/
test('created supplier has correct timestamps', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), [
'name' => 'Timestamp Test',
]);
$supplier = Supplier::where('name', 'Timestamp Test')->first();
expect($supplier->created_at)->not->toBeNull();
expect($supplier->updated_at)->not->toBeNull();
});
test('updated supplier has updated timestamp changed', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$originalUpdatedAt = $supplier->updated_at;
sleep(1);
$this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Timestamp Changed',
]);
$supplier->refresh();
expect($supplier->updated_at->gt($originalUpdatedAt))->toBeTrue();
});
test('supplier ID is auto-incremented', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'First']);
$first = Supplier::where('name', 'First')->first();
$this->post(route('admin.master.suppliers.store'), ['name' => 'Second']);
$second = Supplier::where('name', 'Second')->first();
expect($second->id)->toBe($first->id + 1);
});
test('supplier factory creates valid supplier', function () {
$supplier = Supplier::factory()->create();
expect($supplier->name)->not->toBeEmpty();
expect($supplier->id)->toBeInt();
});
test('supplier factory creates unique names', function () {
$suppliers = Supplier::factory()->count(20)->create();
$names = $suppliers->pluck('name')->toArray();
expect($names)->toHaveCount(count(array_unique($names)));
});
/*
|--------------------------------------------------------------------------
| SOFT DELETE & RESTORE
|--------------------------------------------------------------------------
*/
test('supplier can be restored after soft delete', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create(['name' => 'Restored Supplier']);
$supplier->delete();
$this->assertSoftDeleted('suppliers', ['id' => $supplier->id]);
$supplier->restore();
$this->assertDatabaseHas('suppliers', ['id' => $supplier->id, 'deleted_at' => null]);
});
test('restored supplier appears in index again', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create(['name' => 'Restored Supplier']);
$supplier->delete();
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 0)
);
$supplier->restore();
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 1)
->where('suppliers.0.name', 'Restored Supplier')
);
});
test('restored supplier can be updated', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create(['name' => 'Before Restore']);
$supplier->delete();
$supplier->restore();
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'After Restore',
]);
$response->assertSessionHasNoErrors();
$supplier->refresh();
expect($supplier->name)->toBe('After Restore');
});
/*
|--------------------------------------------------------------------------
| REALISTIC USER SCENARIOS
|--------------------------------------------------------------------------
*/
test('user creates supplier then immediately edits it', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Draft Supplier']);
$supplier = Supplier::where('name', 'Draft Supplier')->first();
$this->put(route('admin.master.suppliers.update', $supplier), ['name' => 'Final Supplier']);
$supplier->refresh();
expect($supplier->name)->toBe('Final Supplier');
});
test('user creates multiple suppliers and deletes one', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Keep']);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Delete Me']);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Also Keep']);
$toDelete = Supplier::where('name', 'Delete Me')->first();
$this->delete(route('admin.master.suppliers.destroy', $toDelete));
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 2)
);
});
test('user renames supplier to match another existing supplier name', function () {
$user = User::factory()->create();
$this->actingAs($user);
Supplier::factory()->create(['name' => 'Target Name']);
$supplier = Supplier::factory()->create(['name' => 'Source Name']);
$response = $this->put(route('admin.master.suppliers.update', $supplier), [
'name' => 'Target Name',
]);
$response->assertSessionHasErrors('name');
});
test('user creates supplier with Indonesian characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'PT Maju Jaya Sentosa',
'phone_number' => '0215551234',
'address' => 'Jl. Sudirman No. 123, Jakarta Selatan',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'PT Maju Jaya Sentosa',
'phone_number' => '0215551234',
'address' => 'Jl. Sudirman No. 123, Jakarta Selatan',
]);
});
test('user tries to create supplier without submitting any data', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), []);
$response->assertSessionHasErrors('name');
});
test('user tries to update supplier without submitting any data', function () {
$user = User::factory()->create();
$this->actingAs($user);
$supplier = Supplier::factory()->create();
$response = $this->put(route('admin.master.suppliers.update', $supplier), []);
$response->assertSessionHasErrors('name');
});
test('user rapidly submits same supplier creation twice', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Rapid Submit']);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'Rapid Submit']);
$response->assertSessionHasErrors('name');
$this->assertDatabaseCount('suppliers', 1);
});
test('user creates supplier with name containing chinese characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => '电子产品供应商']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing japanese characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'サプライヤー']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing korean characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => '공급업체']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing arabic characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'المورد']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing cyrillic characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'Поставщик']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing thai characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'ซัพพลายเออร์']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing hindi characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'आपूर्तिकर्ता']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing greek characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'Προμηθευτής']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing vietnamese characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'Nhà cung cấp']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing mixed unicode scripts', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'Supplier 电子产品']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing emoji', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => '🏢 Supplier']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with very short name', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'X']);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'X',
]);
});
test('user creates supplier with name containing mixed case and numbers', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'PT123ABC456']);
$this->assertDatabaseHas('suppliers', [
'name' => 'PT123ABC456',
]);
});
test('user creates supplier with name containing only spaces and numbers', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => ' 123 ']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with very long name', function () {
$user = User::factory()->create();
$this->actingAs($user);
$name = str_repeat('a', 200);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => $name]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => $name,
]);
});
test('user creates supplier then creates another with similar name', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Supplier ABC']);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Supplier XYZ']);
$this->assertDatabaseCount('suppliers', 2);
$this->assertDatabaseHas('suppliers', ['name' => 'Supplier ABC']);
$this->assertDatabaseHas('suppliers', ['name' => 'Supplier XYZ']);
});
test('user creates supplier with name containing curly braces', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => '{code}']);
$this->assertDatabaseHas('suppliers', [
'name' => '{code}',
]);
});
test('user creates supplier with name containing angle brackets', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => '<html>']);
$this->assertDatabaseHas('suppliers', [
'name' => '<html>',
]);
});
test('user creates supplier with name containing semicolon', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'A;B']);
$this->assertDatabaseHas('suppliers', [
'name' => 'A;B',
]);
});
test('user creates supplier with name containing colon', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Time: Now']);
$this->assertDatabaseHas('suppliers', [
'name' => 'Time: Now',
]);
});
test('user creates supplier with name containing backtick', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => '`Code`']);
$this->assertDatabaseHas('suppliers', [
'name' => '`Code`',
]);
});
test('user creates supplier with name containing tilde', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Caf~']);
$this->assertDatabaseHas('suppliers', [
'name' => 'Caf~',
]);
});
test('user creates supplier with name containing caret', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'A^B']);
$this->assertDatabaseHas('suppliers', [
'name' => 'A^B',
]);
});
test('user creates supplier with name containing percentage', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => '100% Natural']);
$this->assertDatabaseHas('suppliers', [
'name' => '100% Natural',
]);
});
test('user creates supplier with name containing fullwidth characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => '']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing fullwidth brackets', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => '()【】']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing hiragana', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'あいうえお']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing katakana', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'アイウエオ']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing hangul', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => '가나다라마']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing hebrew', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'אבגדהו']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing bengali', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'অআইঈউ']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing tamil', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'அஆஇஈஉ']);
$response->assertSessionHasNoErrors();
});
test('user creates supplier with name containing email-like pattern', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'user@example.com']);
$this->assertDatabaseHas('suppliers', [
'name' => 'user@example.com',
]);
});
test('user creates supplier with name containing URL-like pattern', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'https://example.com']);
$this->assertDatabaseHas('suppliers', [
'name' => 'https://example.com',
]);
});
test('user creates supplier with address containing Indonesian address format', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier Alamat',
'address' => "Jl. Raya Bogor Km. 30\nRT 01/02\nKel. Sukamaju\nKec. Menteng\nJakarta Pusat 10310",
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier Alamat',
]);
});
test('user creates supplier with phone number in various Indonesian formats', function () {
$user = User::factory()->create();
$this->actingAs($user);
$phones = [
'081234567890',
'6281234567890',
'0215551234',
'02155512345',
];
foreach ($phones as $phone) {
$response = $this->post(route('admin.master.suppliers.store'), [
'name' => 'Supplier '.$phone,
'phone_number' => $phone,
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier '.$phone,
'phone_number' => $phone,
]);
}
});
test('user creates supplier then deletes it and creates new one with same name', function () {
$user = User::factory()->create();
$this->actingAs($user);
$this->post(route('admin.master.suppliers.store'), ['name' => 'Reusable']);
$supplier = Supplier::where('name', 'Reusable')->first();
$this->delete(route('admin.master.suppliers.destroy', $supplier));
// Soft-deleted name still triggers unique validation
$response = $this->post(route('admin.master.suppliers.store'), ['name' => 'Reusable']);
$response->assertSessionHasErrors('name');
});
test('user creates supplier with name containing regex special characters', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.suppliers.store'), ['name' => '.*+?^${}()|[]\\']);
$response->assertSessionHasNoErrors();
});