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

1823 lines
54 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

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

<?php
use App\Models\Customer;
use App\Models\Purchase;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
/*
|--------------------------------------------------------------------------
| AUTHENTICATION
|--------------------------------------------------------------------------
*/
test('guests are redirected to the login page', function () {
$response = $this->get(route('admin.master.customers.index'));
$response->assertRedirect(route('login'));
});
test('authenticated users can visit the customer index page', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('admin.master.customers.index'));
$response->assertOk();
});
/*
|--------------------------------------------------------------------------
| INDEX PAGE
|--------------------------------------------------------------------------
*/
test('customer index page displays customers', function () {
$user = User::factory()->create();
$this->actingAs($user);
Customer::factory()->count(3)->create();
$response = $this->get(route('admin.master.customers.index'));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/customer/index')
->has('customers', 3)
);
});
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);
$response = $this->post(route('admin.master.customers.store'), [
'name' => 'Customer Test',
'phone_number' => '08123456789',
'address' => 'Jl. Test No. 1',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.master.customers.index'));
$this->assertDatabaseHas('customers', [
'name' => 'Customer Test',
'phone_number' => '08123456789',
'address' => 'Jl. Test No. 1',
]);
});
test('customer can be created without optional fields', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.customers.store'), [
'name' => 'Customer Minimal',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.master.customers.index'));
$this->assertDatabaseHas('customers', [
'name' => 'Customer Minimal',
]);
});
test('customer name is required', function () {
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('admin.master.customers.store'), [
'name' => '',
]);
$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);
$response = $this->post(route('admin.master.customers.store'), [
'name' => str_repeat('a', 201),
]);
$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);
Customer::factory()->create(['name' => 'Existing Customer']);
$response = $this->post(route('admin.master.customers.store'), [
'name' => 'Existing Customer',
]);
$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' => '<script>alert("xss")</script>',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('customers', [
'name' => '<script>alert("xss")</script>',
]);
});
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);
$response = $this->post(route('admin.master.customers.store'), [
'name' => 'Customer With Letters',
'phone_number' => 'abc123',
]);
$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' => '<b>Jl. Raya</b> No. 1',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('customers', [
'name' => 'Customer HTML Address',
'address' => '<b>Jl. Raya</b> 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);
$customer = Customer::factory()->create();
$response = $this->put(route('admin.master.customers.update', $customer), [
'name' => 'Customer Updated',
'phone_number' => '0987654321',
'address' => 'Jl. Updated No. 2',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.master.customers.index'));
$customer->refresh();
expect($customer->name)->toBe('Customer Updated');
expect($customer->phone_number)->toBe('0987654321');
expect($customer->address)->toBe('Jl. Updated No. 2');
});
test('customer name can be updated to itself', function () {
$user = User::factory()->create();
$this->actingAs($user);
$customer = Customer::factory()->create(['name' => 'My Customer']);
$response = $this->put(route('admin.master.customers.update', $customer), [
'name' => 'My Customer',
]);
$response->assertSessionHasNoErrors();
});
test('customer update name must be unique excluding itself', function () {
$user = User::factory()->create();
$this->actingAs($user);
$customer = Customer::factory()->create(['name' => 'First']);
Customer::factory()->create(['name' => 'Second']);
$response = $this->put(route('admin.master.customers.update', $customer), [
'name' => 'Second',
]);
$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);
$customer = Customer::factory()->create();
$response = $this->delete(route('admin.master.customers.destroy', $customer));
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.master.customers.index'));
$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' => '<html>']);
$this->assertDatabaseHas('customers', [
'name' => '<html>',
]);
});
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' => '']);
$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();
});