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