get(route('admin.master.suppliers.index')); $response->assertRedirect(route('login')); }); test('authenticated users can visit the supplier index page', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.master.suppliers.index')); $response->assertOk(); }); /* |-------------------------------------------------------------------------- | INDEX PAGE |-------------------------------------------------------------------------- */ test('supplier index page displays suppliers', function () { $user = User::factory()->create(); $this->actingAs($user); Supplier::factory()->count(3)->create(); $response = $this->get(route('admin.master.suppliers.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/supplier/index') ->has('suppliers', 3) ); }); test('index page works with zero suppliers', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.master.suppliers.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/supplier/index') ->has('suppliers', 0) ); }); test('index page does not display soft-deleted suppliers', function () { $user = User::factory()->create(); $this->actingAs($user); Supplier::factory()->create(['name' => 'Active Supplier']); Supplier::factory()->create(['name' => 'Deleted Supplier'])->delete(); $response = $this->get(route('admin.master.suppliers.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/supplier/index') ->has('suppliers', 1) ->where('suppliers.0.name', 'Active Supplier') ); }); test('index page displays correct count after delete', function () { $user = User::factory()->create(); $this->actingAs($user); Supplier::factory()->count(5)->create(); $supplier = Supplier::first(); $this->delete(route('admin.master.suppliers.destroy', $supplier)); $response = $this->get(route('admin.master.suppliers.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/supplier/index') ->has('suppliers', 4) ); }); /* |-------------------------------------------------------------------------- | CREATE / STORE |-------------------------------------------------------------------------- */ test('supplier can be created', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => 'Supplier Test', 'phone_number' => '08123456789', 'address' => 'Jl. Test No. 1', ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.master.suppliers.index')); $this->assertDatabaseHas('suppliers', [ 'name' => 'Supplier Test', 'phone_number' => '08123456789', 'address' => 'Jl. Test No. 1', ]); }); test('supplier can be created without optional fields', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => 'Supplier Minimal', ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.master.suppliers.index')); $this->assertDatabaseHas('suppliers', [ 'name' => 'Supplier Minimal', ]); }); test('supplier name is required', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => '', ]); $response->assertSessionHasErrors('name'); }); test('supplier name must be string', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => 12345, ]); $response->assertSessionHasErrors('name'); }); test('supplier name must not exceed 200 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => str_repeat('a', 201), ]); $response->assertSessionHasErrors('name'); }); test('supplier name exactly 200 characters passes validation', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => str_repeat('a', 200), ]); $response->assertSessionHasNoErrors(); }); test('supplier name exactly 1 character passes validation', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => 'A', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('suppliers', [ 'name' => 'A', ]); }); test('supplier name must be unique', function () { $user = User::factory()->create(); $this->actingAs($user); Supplier::factory()->create(['name' => 'Existing Supplier']); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => 'Existing Supplier', ]); $response->assertSessionHasErrors('name'); }); test('supplier name uniqueness is case sensitive', function () { $user = User::factory()->create(); $this->actingAs($user); Supplier::factory()->create(['name' => 'Supplier ABC']); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => 'Supplier ABC', ]); $response->assertSessionHasErrors('name'); }); test('supplier name with only whitespace is rejected', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => ' ', ]); $response->assertSessionHasErrors('name'); }); test('supplier name with special characters is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => 'Supplier & Co. (PT)', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('suppliers', [ 'name' => 'Supplier & Co. (PT)', ]); }); test('supplier name with HTML tags is accepted as plain text', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => '', ]); $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); $supplier = Supplier::factory()->create(); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Supplier Updated', 'phone_number' => '0987654321', 'address' => 'Jl. Updated No. 2', ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.master.suppliers.index')); $supplier->refresh(); expect($supplier->name)->toBe('Supplier Updated'); expect($supplier->phone_number)->toBe('0987654321'); expect($supplier->address)->toBe('Jl. Updated No. 2'); }); test('supplier name can be updated to itself', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(['name' => 'My Supplier']); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'My Supplier', ]); $response->assertSessionHasNoErrors(); }); test('supplier update name must be unique excluding itself', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(['name' => 'First']); Supplier::factory()->create(['name' => 'Second']); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Second', ]); $response->assertSessionHasErrors('name'); }); test('supplier update name is required', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => '', ]); $response->assertSessionHasErrors('name'); }); test('supplier update name must not exceed 200 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => str_repeat('a', 201), ]); $response->assertSessionHasErrors('name'); }); test('supplier update phone_number must be numeric', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Updated Supplier', 'phone_number' => 'abc123', ]); $response->assertSessionHasErrors('phone_number'); }); test('supplier update phone_number with special characters is rejected', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Updated Supplier', 'phone_number' => '+62-812-3456-7890', ]); $response->assertSessionHasErrors('phone_number'); }); test('supplier can be updated with only name', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(['phone_number' => '08123456789', 'address' => 'Old Address']); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Updated Name Only', ]); $response->assertSessionHasNoErrors(); $supplier->refresh(); expect($supplier->name)->toBe('Updated Name Only'); expect($supplier->phone_number)->toBe('08123456789'); expect($supplier->address)->toBe('Old Address'); }); test('supplier can be updated multiple times', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(['name' => 'Version 1']); $this->put(route('admin.master.suppliers.update', $supplier), ['name' => 'Version 2']); $supplier->refresh(); expect($supplier->name)->toBe('Version 2'); $this->put(route('admin.master.suppliers.update', $supplier), ['name' => 'Version 3']); $supplier->refresh(); expect($supplier->name)->toBe('Version 3'); }); test('update flashes success toast via Inertia', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Updated Name', ]); $response->assertRedirect(); }); test('supplier update with special characters in name is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Supplier & Co. (PT)', ]); $response->assertSessionHasNoErrors(); $supplier->refresh(); expect($supplier->name)->toBe('Supplier & Co. (PT)'); }); test('supplier update with unicode characters is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(['name' => 'Original']); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Supplier Nono', ]); $response->assertSessionHasNoErrors(); $supplier->refresh(); expect($supplier->name)->toBe('Supplier Nono'); }); test('supplier update with only whitespace name is rejected', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => ' ', ]); $response->assertSessionHasErrors('name'); }); test('supplier update can clear phone_number to null', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(['phone_number' => '08123456789']); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => $supplier->name, 'phone_number' => null, ]); $response->assertSessionHasNoErrors(); $supplier->refresh(); expect($supplier->phone_number)->toBeNull(); }); test('supplier update can clear address to null', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(['address' => 'Old Address']); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => $supplier->name, 'address' => null, ]); $response->assertSessionHasNoErrors(); $supplier->refresh(); expect($supplier->address)->toBeNull(); }); test('updating non-existent supplier returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.master.suppliers.update', 999999), [ 'name' => 'Ghost Supplier', ]); $response->assertStatus(404); }); /* |-------------------------------------------------------------------------- | DELETE / DESTROY |-------------------------------------------------------------------------- */ test('supplier can be deleted', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $response = $this->delete(route('admin.master.suppliers.destroy', $supplier)); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.master.suppliers.index')); $this->assertSoftDeleted('suppliers', ['id' => $supplier->id]); }); test('supplier is soft-deleted not permanently removed', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $this->delete(route('admin.master.suppliers.destroy', $supplier)); $this->assertDatabaseHas('suppliers', ['id' => $supplier->id]); $this->assertSoftDeleted('suppliers', ['id' => $supplier->id]); }); test('soft-deleted supplier still exists in database', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $this->delete(route('admin.master.suppliers.destroy', $supplier)); $this->assertDatabaseHas('suppliers', ['id' => $supplier->id]); }); test('soft-deleted supplier has deleted_at timestamp', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $this->delete(route('admin.master.suppliers.destroy', $supplier)); $supplier->refresh(); expect($supplier->deleted_at)->not->toBeNull(); }); test('deleted supplier is excluded from getAll query', function () { $user = User::factory()->create(); $this->actingAs($user); Supplier::factory()->create(['name' => 'Active']); $deleted = Supplier::factory()->create(['name' => 'Deleted']); $deleted->delete(); $response = $this->get(route('admin.master.suppliers.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/supplier/index') ->has('suppliers', 1) ->where('suppliers.0.name', 'Active') ); }); test('delete flashes success toast via Inertia', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $response = $this->delete(route('admin.master.suppliers.destroy', $supplier)); $response->assertRedirect(); }); test('multiple suppliers can be deleted one by one', function () { $user = User::factory()->create(); $this->actingAs($user); $suppliers = Supplier::factory()->count(3)->create(); $this->delete(route('admin.master.suppliers.destroy', $suppliers[0])); $this->delete(route('admin.master.suppliers.destroy', $suppliers[1])); $this->assertSoftDeleted('suppliers', ['id' => $suppliers[0]->id]); $this->assertSoftDeleted('suppliers', ['id' => $suppliers[1]->id]); $this->assertDatabaseHas('suppliers', ['id' => $suppliers[2]->id, 'deleted_at' => null]); }); test('updating soft-deleted supplier via route returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $supplier->delete(); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Should Not Work', ]); $response->assertStatus(404); }); test('deleting soft-deleted supplier via route returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $supplier->delete(); $response = $this->delete(route('admin.master.suppliers.destroy', $supplier)); $response->assertStatus(404); }); test('deleting non-existent supplier returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->delete(route('admin.master.suppliers.destroy', 999999)); $response->assertStatus(404); }); /* |-------------------------------------------------------------------------- | SUPPLIER - PURCHASE RELATIONSHIP |-------------------------------------------------------------------------- */ test('supplier with purchases can be deleted', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); Purchase::factory()->count(3)->create(['supplier_id' => $supplier->id]); $this->delete(route('admin.master.suppliers.destroy', $supplier)); $this->assertSoftDeleted('suppliers', ['id' => $supplier->id]); // Purchases still exist because soft delete doesn't trigger cascade $this->assertDatabaseCount('purchases', 3); }); test('index page works correctly with suppliers that have purchases', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); Purchase::factory()->count(5)->create(['supplier_id' => $supplier->id]); $response = $this->get(route('admin.master.suppliers.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/supplier/index') ->has('suppliers', 1) ); }); /* |-------------------------------------------------------------------------- | AUTHORIZATION - GUEST CANNOT PERFORM ACTIONS |-------------------------------------------------------------------------- */ test('guest cannot create supplier', function () { $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => 'Unauthorized', ]); $response->assertRedirect(route('login')); $this->assertDatabaseMissing('suppliers', ['name' => 'Unauthorized']); }); test('guest cannot update supplier', function () { $supplier = Supplier::factory()->create(); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Unauthorized Update', ]); $response->assertRedirect(route('login')); }); test('guest cannot delete supplier', function () { $supplier = Supplier::factory()->create(); $response = $this->delete(route('admin.master.suppliers.destroy', $supplier)); $response->assertRedirect(route('login')); $this->assertDatabaseHas('suppliers', ['id' => $supplier->id, 'deleted_at' => null]); }); /* |-------------------------------------------------------------------------- | DATA INTEGRITY |-------------------------------------------------------------------------- */ test('created supplier has correct timestamps', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.suppliers.store'), [ 'name' => 'Timestamp Test', ]); $supplier = Supplier::where('name', 'Timestamp Test')->first(); expect($supplier->created_at)->not->toBeNull(); expect($supplier->updated_at)->not->toBeNull(); }); test('updated supplier has updated timestamp changed', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $originalUpdatedAt = $supplier->updated_at; sleep(1); $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Timestamp Changed', ]); $supplier->refresh(); expect($supplier->updated_at->gt($originalUpdatedAt))->toBeTrue(); }); test('supplier ID is auto-incremented', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.suppliers.store'), ['name' => 'First']); $first = Supplier::where('name', 'First')->first(); $this->post(route('admin.master.suppliers.store'), ['name' => 'Second']); $second = Supplier::where('name', 'Second')->first(); expect($second->id)->toBe($first->id + 1); }); test('supplier factory creates valid supplier', function () { $supplier = Supplier::factory()->create(); expect($supplier->name)->not->toBeEmpty(); expect($supplier->id)->toBeInt(); }); test('supplier factory creates unique names', function () { $suppliers = Supplier::factory()->count(20)->create(); $names = $suppliers->pluck('name')->toArray(); expect($names)->toHaveCount(count(array_unique($names))); }); /* |-------------------------------------------------------------------------- | SOFT DELETE & RESTORE |-------------------------------------------------------------------------- */ test('supplier can be restored after soft delete', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(['name' => 'Restored Supplier']); $supplier->delete(); $this->assertSoftDeleted('suppliers', ['id' => $supplier->id]); $supplier->restore(); $this->assertDatabaseHas('suppliers', ['id' => $supplier->id, 'deleted_at' => null]); }); test('restored supplier appears in index again', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(['name' => 'Restored Supplier']); $supplier->delete(); $response = $this->get(route('admin.master.suppliers.index')); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/supplier/index') ->has('suppliers', 0) ); $supplier->restore(); $response = $this->get(route('admin.master.suppliers.index')); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/supplier/index') ->has('suppliers', 1) ->where('suppliers.0.name', 'Restored Supplier') ); }); test('restored supplier can be updated', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(['name' => 'Before Restore']); $supplier->delete(); $supplier->restore(); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'After Restore', ]); $response->assertSessionHasNoErrors(); $supplier->refresh(); expect($supplier->name)->toBe('After Restore'); }); /* |-------------------------------------------------------------------------- | REALISTIC USER SCENARIOS |-------------------------------------------------------------------------- */ test('user creates supplier then immediately edits it', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.suppliers.store'), ['name' => 'Draft Supplier']); $supplier = Supplier::where('name', 'Draft Supplier')->first(); $this->put(route('admin.master.suppliers.update', $supplier), ['name' => 'Final Supplier']); $supplier->refresh(); expect($supplier->name)->toBe('Final Supplier'); }); test('user creates multiple suppliers and deletes one', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.suppliers.store'), ['name' => 'Keep']); $this->post(route('admin.master.suppliers.store'), ['name' => 'Delete Me']); $this->post(route('admin.master.suppliers.store'), ['name' => 'Also Keep']); $toDelete = Supplier::where('name', 'Delete Me')->first(); $this->delete(route('admin.master.suppliers.destroy', $toDelete)); $response = $this->get(route('admin.master.suppliers.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/supplier/index') ->has('suppliers', 2) ); }); test('user renames supplier to match another existing supplier name', function () { $user = User::factory()->create(); $this->actingAs($user); Supplier::factory()->create(['name' => 'Target Name']); $supplier = Supplier::factory()->create(['name' => 'Source Name']); $response = $this->put(route('admin.master.suppliers.update', $supplier), [ 'name' => 'Target Name', ]); $response->assertSessionHasErrors('name'); }); test('user creates supplier with Indonesian characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), [ 'name' => 'PT Maju Jaya Sentosa', 'phone_number' => '0215551234', 'address' => 'Jl. Sudirman No. 123, Jakarta Selatan', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('suppliers', [ 'name' => 'PT Maju Jaya Sentosa', 'phone_number' => '0215551234', 'address' => 'Jl. Sudirman No. 123, Jakarta Selatan', ]); }); test('user tries to create supplier without submitting any data', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), []); $response->assertSessionHasErrors('name'); }); test('user tries to update supplier without submitting any data', function () { $user = User::factory()->create(); $this->actingAs($user); $supplier = Supplier::factory()->create(); $response = $this->put(route('admin.master.suppliers.update', $supplier), []); $response->assertSessionHasErrors('name'); }); test('user rapidly submits same supplier creation twice', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.suppliers.store'), ['name' => 'Rapid Submit']); $response = $this->post(route('admin.master.suppliers.store'), ['name' => 'Rapid Submit']); $response->assertSessionHasErrors('name'); $this->assertDatabaseCount('suppliers', 1); }); test('user creates supplier with name containing chinese characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => '电子产品供应商']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with name containing japanese characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => 'サプライヤー']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with name containing korean characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => '공급업체']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with name containing arabic characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => 'المورد']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with name containing cyrillic characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => 'Поставщик']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with name containing thai characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => 'ซัพพลายเออร์']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with name containing hindi characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => 'आपूर्तिकर्ता']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with name containing greek characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => 'Προμηθευτής']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with name containing vietnamese characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => 'Nhà cung cấp']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with name containing mixed unicode scripts', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => 'Supplier 电子产品']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with name containing emoji', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => '🏢 Supplier']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with very short name', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => 'X']); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('suppliers', [ 'name' => 'X', ]); }); test('user creates supplier with name containing mixed case and numbers', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.suppliers.store'), ['name' => 'PT123ABC456']); $this->assertDatabaseHas('suppliers', [ 'name' => 'PT123ABC456', ]); }); test('user creates supplier with name containing only spaces and numbers', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.suppliers.store'), ['name' => ' 123 ']); $response->assertSessionHasNoErrors(); }); test('user creates supplier with very long name', function () { $user = User::factory()->create(); $this->actingAs($user); $name = str_repeat('a', 200); $response = $this->post(route('admin.master.suppliers.store'), ['name' => $name]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('suppliers', [ 'name' => $name, ]); }); test('user creates supplier then creates another with similar name', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.suppliers.store'), ['name' => 'Supplier ABC']); $this->post(route('admin.master.suppliers.store'), ['name' => 'Supplier XYZ']); $this->assertDatabaseCount('suppliers', 2); $this->assertDatabaseHas('suppliers', ['name' => 'Supplier ABC']); $this->assertDatabaseHas('suppliers', ['name' => 'Supplier XYZ']); }); test('user creates supplier with name containing curly braces', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.suppliers.store'), ['name' => '{code}']); $this->assertDatabaseHas('suppliers', [ 'name' => '{code}', ]); }); test('user creates supplier with name containing angle brackets', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.suppliers.store'), ['name' => '']); $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(); });