get(route('admin.master.categories.index')); $response->assertRedirect(route('login')); }); test('authenticated users can visit the category index page', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.master.categories.index')); $response->assertOk(); }); /* |-------------------------------------------------------------------------- | INDEX PAGE |-------------------------------------------------------------------------- */ test('category index page displays categories', function () { $user = User::factory()->create(); $this->actingAs($user); Category::factory()->count(3)->create(); $response = $this->get(route('admin.master.categories.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/category/index') ->has('categories', 3) ); }); test('index page works with zero categories', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->get(route('admin.master.categories.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/category/index') ->has('categories', 0) ); }); test('index page does not display soft-deleted categories', function () { $user = User::factory()->create(); $this->actingAs($user); Category::factory()->create(['name' => 'Active Category']); Category::factory()->create(['name' => 'Deleted Category'])->delete(); $response = $this->get(route('admin.master.categories.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/category/index') ->has('categories', 1) ->where('categories.0.name', 'Active Category') ); }); test('index page displays correct count after delete', function () { $user = User::factory()->create(); $this->actingAs($user); Category::factory()->count(5)->create(); $category = Category::first(); $this->delete(route('admin.master.categories.destroy', $category)); $response = $this->get(route('admin.master.categories.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/category/index') ->has('categories', 4) ); }); /* |-------------------------------------------------------------------------- | CREATE / STORE |-------------------------------------------------------------------------- */ test('category can be created', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'Kategori Test', ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.master.categories.index')); $this->assertDatabaseHas('categories', [ 'name' => 'Kategori Test', 'slug' => 'kategori-test', ]); }); test('category slug is automatically generated from name', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Kategori Otomatis', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Kategori Otomatis', 'slug' => 'kategori-otomatis', ]); }); test('category name is required', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => '', ]); $response->assertSessionHasErrors('name'); }); test('category name must be string', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 12345, ]); $response->assertSessionHasErrors('name'); }); test('category name must not exceed 100 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => str_repeat('a', 101), ]); $response->assertSessionHasErrors('name'); }); test('category name exactly 100 characters passes validation', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => str_repeat('a', 100), ]); $response->assertSessionHasNoErrors(); }); test('category name exactly 1 character passes validation', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'A', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => 'A', 'slug' => 'a', ]); }); test('category name must be unique', function () { $user = User::factory()->create(); $this->actingAs($user); Category::factory()->create(['name' => 'Existing Category']); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'Existing Category', ]); $response->assertSessionHasErrors('name'); }); test('category name uniqueness is case sensitive', function () { $user = User::factory()->create(); $this->actingAs($user); Category::factory()->create(['name' => 'Makanan']); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'Makanan', ]); $response->assertSessionHasErrors('name'); }); test('category name with only whitespace is rejected', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => ' ', ]); $response->assertSessionHasErrors('name'); }); test('category name with special characters is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'Makanan & Minuman!', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => 'Makanan & Minuman!', 'slug' => 'makanan-minuman', ]); }); test('category name with HTML tags is accepted as plain text', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => '', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => '', ]); }); test('category name with numbers is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'Kategori 123', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => 'Kategori 123', 'slug' => 'kategori-123', ]); }); test('category name with only numbers is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => '12345', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => '12345', 'slug' => '12345', ]); }); test('category name with only punctuation is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => '---', ]); $response->assertSessionHasNoErrors(); }); test('category name with parentheses and brackets is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'Kategori (A) [VIP]', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => 'Kategori (A) [VIP]', 'slug' => 'kategori-a-vip', ]); }); test('category name with unicode characters is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'Kategori Nono', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => 'Kategori Nono', ]); }); test('category name with mixed case is stored as entered', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'makanAN MinUMAN', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => 'makanAN MinUMAN', 'slug' => 'makanan-minuman', ]); }); test('category name with multiple spaces generates correct slug', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'Kategori dengan spasi banyak', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => 'Kategori dengan spasi banyak', 'slug' => 'kategori-dengan-spasi-banyak', ]); }); test('category name with leading and trailing spaces is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => ' Kategori Spasi ', ]); $response->assertSessionHasNoErrors(); }); test('category name with newline characters is accepted', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => "Kategori\nBaru", ]); $response->assertSessionHasNoErrors(); }); test('category can be created with name at exact DB column limit (50 chars)', function () { $user = User::factory()->create(); $this->actingAs($user); $name = str_repeat('a', 50); $response = $this->post(route('admin.master.categories.store'), [ 'name' => $name, ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => $name, ]); }); test('store flashes success toast via Inertia', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'Kategori Toast', ]); $response->assertRedirect(); }); test('store creates new category in database', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Database Check', ]); $this->assertDatabaseCount('categories', 1); $this->assertDatabaseHas('categories', [ 'name' => 'Database Check', 'slug' => 'database-check', ]); }); test('multiple categories can be created sequentially', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'Kategori 1']); $this->post(route('admin.master.categories.store'), ['name' => 'Kategori 2']); $this->post(route('admin.master.categories.store'), ['name' => 'Kategori 3']); $this->assertDatabaseCount('categories', 3); $this->assertDatabaseHas('categories', ['name' => 'Kategori 1', 'slug' => 'kategori-1']); $this->assertDatabaseHas('categories', ['name' => 'Kategori 2', 'slug' => 'kategori-2']); $this->assertDatabaseHas('categories', ['name' => 'Kategori 3', 'slug' => 'kategori-3']); }); /* |-------------------------------------------------------------------------- | UPDATE / PUT |-------------------------------------------------------------------------- */ test('category can be updated', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => 'Kategori Updated', ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.master.categories.index')); $category->refresh(); expect($category->name)->toBe('Kategori Updated'); expect($category->slug)->toBe('kategori-updated'); }); test('category name can be updated to itself', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(['name' => 'My Category']); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => 'My Category', ]); $response->assertSessionHasNoErrors(); }); test('category update name must be unique excluding itself', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(['name' => 'First']); Category::factory()->create(['name' => 'Second']); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => 'Second', ]); $response->assertSessionHasErrors('name'); }); test('category update name is required', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => '', ]); $response->assertSessionHasErrors('name'); }); test('category update name must not exceed 100 characters', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => str_repeat('a', 101), ]); $response->assertSessionHasErrors('name'); }); test('category update with special characters generates correct slug', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(['name' => 'Old Name']); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => 'New Name (Updated) & More!', ]); $response->assertSessionHasNoErrors(); $category->refresh(); expect($category->slug)->toBe('new-name-updated-more'); }); test('category update changes the slug to match new name', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(['name' => 'Original', 'slug' => 'original']); $this->put(route('admin.master.categories.update', $category), [ 'name' => 'Modified', ]); $category->refresh(); expect($category->slug)->toBe('modified'); }); test('category can be updated multiple times', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(['name' => 'Version 1']); $this->put(route('admin.master.categories.update', $category), ['name' => 'Version 2']); $category->refresh(); expect($category->name)->toBe('Version 2'); $this->put(route('admin.master.categories.update', $category), ['name' => 'Version 3']); $category->refresh(); expect($category->name)->toBe('Version 3'); }); test('update flashes success toast via Inertia', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => 'Updated Name', ]); $response->assertRedirect(); }); test('category update with only whitespace is rejected', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => ' ', ]); $response->assertSessionHasErrors('name'); }); test('category can be updated with unicode characters', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(['name' => 'Original']); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => 'Kategori Nono', ]); $response->assertSessionHasNoErrors(); $category->refresh(); expect($category->name)->toBe('Kategori Nono'); }); test('updating non-existent category returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->put(route('admin.master.categories.update', 999999), [ 'name' => 'Ghost Category', ]); $response->assertStatus(404); }); /* |-------------------------------------------------------------------------- | DELETE / DESTROY |-------------------------------------------------------------------------- */ test('category can be deleted', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $response = $this->delete(route('admin.master.categories.destroy', $category)); $response ->assertSessionHasNoErrors() ->assertRedirect(route('admin.master.categories.index')); $this->assertSoftDeleted('categories', ['id' => $category->id]); }); test('category is soft-deleted not permanently removed', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $this->delete(route('admin.master.categories.destroy', $category)); $this->assertDatabaseHas('categories', ['id' => $category->id]); $this->assertSoftDeleted('categories', ['id' => $category->id]); }); test('soft-deleted category still exists in database', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $this->delete(route('admin.master.categories.destroy', $category)); $this->assertDatabaseHas('categories', ['id' => $category->id]); }); test('soft-deleted category has deleted_at timestamp', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $this->delete(route('admin.master.categories.destroy', $category)); $category->refresh(); expect($category->deleted_at)->not->toBeNull(); }); test('deleted category is excluded from getAll query', function () { $user = User::factory()->create(); $this->actingAs($user); Category::factory()->create(['name' => 'Active']); $deleted = Category::factory()->create(['name' => 'Deleted']); $deleted->delete(); $response = $this->get(route('admin.master.categories.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/category/index') ->has('categories', 1) ->where('categories.0.name', 'Active') ); }); test('delete flashes success toast via Inertia', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $response = $this->delete(route('admin.master.categories.destroy', $category)); $response->assertRedirect(); }); test('multiple categories can be deleted one by one', function () { $user = User::factory()->create(); $this->actingAs($user); $categories = Category::factory()->count(3)->create(); $this->delete(route('admin.master.categories.destroy', $categories[0])); $this->delete(route('admin.master.categories.destroy', $categories[1])); $this->assertSoftDeleted('categories', ['id' => $categories[0]->id]); $this->assertSoftDeleted('categories', ['id' => $categories[1]->id]); $this->assertDatabaseHas('categories', ['id' => $categories[2]->id, 'deleted_at' => null]); }); test('updating soft-deleted category via route returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $category->delete(); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => 'Should Not Work', ]); $response->assertStatus(404); }); test('deleting soft-deleted category via route returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $category->delete(); $response = $this->delete(route('admin.master.categories.destroy', $category)); $response->assertStatus(404); }); test('deleting non-existent category returns 404', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->delete(route('admin.master.categories.destroy', 999999)); $response->assertStatus(404); }); /* |-------------------------------------------------------------------------- | CATEGORY - PRODUCT RELATIONSHIP |-------------------------------------------------------------------------- */ test('category with products can be deleted without deleting products', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $product = Product::factory()->create(); DB::table('product_categories')->insert([ 'product_id' => $product->id, 'category_id' => $category->id, ]); $this->delete(route('admin.master.categories.destroy', $category)); $this->assertSoftDeleted('categories', ['id' => $category->id]); $this->assertDatabaseHas('products', ['id' => $product->id, 'deleted_at' => null]); }); test('category-product pivot record remains after soft delete', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $product = Product::factory()->create(); DB::table('product_categories')->insert([ 'product_id' => $product->id, 'category_id' => $category->id, ]); $this->assertDatabaseHas('product_categories', [ 'category_id' => $category->id, 'product_id' => $product->id, ]); $this->delete(route('admin.master.categories.destroy', $category)); // Soft delete doesn't trigger cascade delete, so pivot record remains $this->assertDatabaseHas('product_categories', [ 'category_id' => $category->id, 'product_id' => $product->id, ]); }); test('index page works correctly with categories that have products', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $products = Product::factory()->count(3)->create(); foreach ($products as $product) { DB::table('product_categories')->insert([ 'product_id' => $product->id, 'category_id' => $category->id, ]); } $response = $this->get(route('admin.master.categories.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/category/index') ->has('categories', 1) ); }); test('category with many products can be deleted', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $products = Product::factory()->count(10)->create(); foreach ($products as $product) { DB::table('product_categories')->insert([ 'product_id' => $product->id, 'category_id' => $category->id, ]); } $this->delete(route('admin.master.categories.destroy', $category)); $this->assertSoftDeleted('categories', ['id' => $category->id]); $this->assertDatabaseCount('products', 10); }); test('deleting category does not affect other categories products belong to', function () { $user = User::factory()->create(); $this->actingAs($user); $category1 = Category::factory()->create(); $category2 = Category::factory()->create(); $product = Product::factory()->create(); DB::table('product_categories')->insert([ ['product_id' => $product->id, 'category_id' => $category1->id], ['product_id' => $product->id, 'category_id' => $category2->id], ]); $this->delete(route('admin.master.categories.destroy', $category1)); $this->assertSoftDeleted('categories', ['id' => $category1->id]); $this->assertDatabaseHas('categories', ['id' => $category2->id, 'deleted_at' => null]); $this->assertDatabaseHas('product_categories', [ 'product_id' => $product->id, 'category_id' => $category2->id, ]); }); /* |-------------------------------------------------------------------------- | SLUG EDGE CASES |-------------------------------------------------------------------------- */ test('slug is generated correctly from name with ampersand', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Food & Beverage', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Food & Beverage', 'slug' => Str::slug('Food & Beverage'), ]); }); test('slug is generated correctly from name with dots', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Mr. Category', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Mr. Category', 'slug' => Str::slug('Mr. Category'), ]); }); test('slug is generated correctly from name with slashes', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Food/Drink', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Food/Drink', 'slug' => Str::slug('Food/Drink'), ]); }); test('slug is generated correctly from name with commas', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Makanan, Minuman', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Makanan, Minuman', 'slug' => Str::slug('Makanan, Minuman'), ]); }); test('slug is generated correctly from name with single quotes', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => "It's Category", ]); $this->assertDatabaseHas('categories', [ 'name' => "It's Category", 'slug' => Str::slug("It's Category"), ]); }); test('slug is generated correctly from name with double quotes', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => '"Premium" Category', ]); $this->assertDatabaseHas('categories', [ 'name' => '"Premium" Category', 'slug' => Str::slug('"Premium" Category'), ]); }); test('slug is generated correctly from name with exclamation mark', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Hot Sale!', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Hot Sale!', 'slug' => Str::slug('Hot Sale!'), ]); }); test('slug is generated correctly from name with question mark', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Best Choice?', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Best Choice?', 'slug' => Str::slug('Best Choice?'), ]); }); test('slug is generated correctly from all-caps name', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'ELECTRONICS', ]); $this->assertDatabaseHas('categories', [ 'name' => 'ELECTRONICS', 'slug' => Str::slug('ELECTRONICS'), ]); }); test('slug is generated correctly from name with numbers and letters mixed', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'iPhone 15 Pro', ]); $this->assertDatabaseHas('categories', [ 'name' => 'iPhone 15 Pro', 'slug' => Str::slug('iPhone 15 Pro'), ]); }); test('slug is generated correctly from name with dash', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Food-Grade', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Food-Grade', 'slug' => Str::slug('Food-Grade'), ]); }); test('slug is generated correctly from name with underscore', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Food_Grade', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Food_Grade', 'slug' => Str::slug('Food_Grade'), ]); }); test('slug is generated correctly from name with at symbol', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Category@Home', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Category@Home', 'slug' => Str::slug('Category@Home'), ]); }); test('slug is generated correctly from name with hash symbol', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Category#1', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Category#1', 'slug' => Str::slug('Category#1'), ]); }); test('slug is generated correctly from name with currency symbol', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Rp 50.000', ]); $this->assertDatabaseHas('categories', [ 'name' => 'Rp 50.000', 'slug' => Str::slug('Rp 50.000'), ]); }); test('slug is generated correctly from name with pipe symbol', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'A|B', ]); $this->assertDatabaseHas('categories', [ 'name' => 'A|B', 'slug' => Str::slug('A|B'), ]); }); test('slug is generated correctly from name with mixed special chars', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'A & B (v2.0)!', ]); $this->assertDatabaseHas('categories', [ 'name' => 'A & B (v2.0)!', 'slug' => Str::slug('A & B (v2.0)!'), ]); }); test('slug does not change when category is updated with same name', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(['name' => 'Same Name', 'slug' => 'same-name']); $originalSlug = $category->slug; $this->put(route('admin.master.categories.update', $category), [ 'name' => 'Same Name', ]); $category->refresh(); expect($category->slug)->toBe($originalSlug); }); test('slug changes when category name changes', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(['name' => 'Old Name', 'slug' => 'old-name']); $this->put(route('admin.master.categories.update', $category), [ 'name' => 'New Name', ]); $category->refresh(); expect($category->slug)->toBe('new-name'); expect($category->slug)->not->toBe('old-name'); }); /* |-------------------------------------------------------------------------- | AUTHORIZATION - GUEST CANNOT PERFORM ACTIONS |-------------------------------------------------------------------------- */ test('guest cannot create category', function () { $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'Unauthorized', ]); $response->assertRedirect(route('login')); $this->assertDatabaseMissing('categories', ['name' => 'Unauthorized']); }); test('guest cannot update category', function () { $category = Category::factory()->create(); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => 'Unauthorized Update', ]); $response->assertRedirect(route('login')); }); test('guest cannot delete category', function () { $category = Category::factory()->create(); $response = $this->delete(route('admin.master.categories.destroy', $category)); $response->assertRedirect(route('login')); $this->assertDatabaseHas('categories', ['id' => $category->id, 'deleted_at' => null]); }); /* |-------------------------------------------------------------------------- | DATA INTEGRITY |-------------------------------------------------------------------------- */ test('created category has correct timestamps', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), [ 'name' => 'Timestamp Test', ]); $category = Category::where('name', 'Timestamp Test')->first(); expect($category->created_at)->not->toBeNull(); expect($category->updated_at)->not->toBeNull(); }); test('updated category has updated timestamp changed', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $originalUpdatedAt = $category->updated_at; sleep(1); $this->put(route('admin.master.categories.update', $category), [ 'name' => 'Timestamp Changed', ]); $category->refresh(); expect($category->updated_at->gt($originalUpdatedAt))->toBeTrue(); }); test('category ID is auto-incremented', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'First']); $first = Category::where('name', 'First')->first(); $this->post(route('admin.master.categories.store'), ['name' => 'Second']); $second = Category::where('name', 'Second')->first(); expect($second->id)->toBe($first->id + 1); }); test('category factory creates valid category', function () { $category = Category::factory()->create(); expect($category->name)->not->toBeEmpty(); expect($category->slug)->not->toBeEmpty(); expect($category->id)->toBeInt(); }); test('category factory creates unique names', function () { $categories = Category::factory()->count(20)->create(); $names = $categories->pluck('name')->toArray(); expect($names)->toHaveCount(count(array_unique($names))); }); /* |-------------------------------------------------------------------------- | SOFT DELETE & RESTORE |-------------------------------------------------------------------------- */ test('category can be restored after soft delete', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(['name' => 'Restored Category']); $category->delete(); $this->assertSoftDeleted('categories', ['id' => $category->id]); $category->restore(); $this->assertDatabaseHas('categories', ['id' => $category->id, 'deleted_at' => null]); }); test('restored category appears in index again', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(['name' => 'Restored Category']); $category->delete(); $response = $this->get(route('admin.master.categories.index')); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/category/index') ->has('categories', 0) ); $category->restore(); $response = $this->get(route('admin.master.categories.index')); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/category/index') ->has('categories', 1) ->where('categories.0.name', 'Restored Category') ); }); test('restored category can be updated', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(['name' => 'Before Restore']); $category->delete(); $category->restore(); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => 'After Restore', ]); $response->assertSessionHasNoErrors(); $category->refresh(); expect($category->name)->toBe('After Restore'); }); /* |-------------------------------------------------------------------------- | REALISTIC USER SCENARIOS |-------------------------------------------------------------------------- */ test('user creates category then immediately edits it', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'Draft Category']); $category = Category::where('name', 'Draft Category')->first(); $this->put(route('admin.master.categories.update', $category), ['name' => 'Final Category']); $category->refresh(); expect($category->name)->toBe('Final Category'); expect($category->slug)->toBe('final-category'); }); test('user creates multiple categories and deletes one', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'Keep']); $this->post(route('admin.master.categories.store'), ['name' => 'Delete Me']); $this->post(route('admin.master.categories.store'), ['name' => 'Also Keep']); $toDelete = Category::where('name', 'Delete Me')->first(); $this->delete(route('admin.master.categories.destroy', $toDelete)); $response = $this->get(route('admin.master.categories.index')); $response->assertOk(); $response->assertInertia(fn (Assert $page) => $page ->component('admin/master/category/index') ->has('categories', 2) ); }); test('user renames category to match another existing category name', function () { $user = User::factory()->create(); $this->actingAs($user); Category::factory()->create(['name' => 'Target Name']); $category = Category::factory()->create(['name' => 'Source Name']); $response = $this->put(route('admin.master.categories.update', $category), [ 'name' => 'Target Name', ]); $response->assertSessionHasErrors('name'); }); test('user creates category with Indonesian characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), [ 'name' => 'Makanan Khas Jawa', ]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => 'Makanan Khas Jawa', 'slug' => 'makanan-khas-jawa', ]); }); test('user tries to create category without submitting any data', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), []); $response->assertSessionHasErrors('name'); }); test('user tries to update category without submitting any data', function () { $user = User::factory()->create(); $this->actingAs($user); $category = Category::factory()->create(); $response = $this->put(route('admin.master.categories.update', $category), []); $response->assertSessionHasErrors('name'); }); test('user rapidly submits same category creation twice', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'Rapid Submit']); $response = $this->post(route('admin.master.categories.store'), ['name' => 'Rapid Submit']); $response->assertSessionHasErrors('name'); $this->assertDatabaseCount('categories', 1); }); test('user creates category with name containing chinese characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => '电子产品']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing japanese characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'カテゴリ']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing korean characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => '카테고리']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing arabic characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'الفئة']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing cyrillic characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'Электроника']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing thai characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'หมวดหมู่']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing hindi characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'श्रेणी']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing greek characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'Κατηγορία']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing vietnamese characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'Danh Mục']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing mixed unicode scripts', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'Food 电子产品']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing email-like pattern', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'user@example.com']); $this->assertDatabaseHas('categories', [ 'name' => 'user@example.com', 'slug' => Str::slug('user@example.com'), ]); }); test('user creates category with name containing URL-like pattern', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'https://example.com']); $this->assertDatabaseHas('categories', [ 'name' => 'https://example.com', 'slug' => Str::slug('https://example.com'), ]); }); test('user creates category with name containing date-like pattern', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => '2024-01-15']); $this->assertDatabaseHas('categories', [ 'name' => '2024-01-15', 'slug' => '2024-01-15', ]); }); test('user creates category with name containing emoji', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => '🔥 Hot Category']); $response->assertSessionHasNoErrors(); }); test('user creates category with very short name', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'X']); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => 'X', 'slug' => 'x', ]); }); test('user creates category with name containing multiple dashes', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'A--B']); $this->assertDatabaseHas('categories', [ 'name' => 'A--B', 'slug' => Str::slug('A--B'), ]); }); test('user creates category with name containing mixed case and numbers', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'iPhone15ProMax']); $this->assertDatabaseHas('categories', [ 'name' => 'iPhone15ProMax', 'slug' => Str::slug('iPhone15ProMax'), ]); }); test('user creates category with name containing only spaces and numbers', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => ' 123 ']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing very long single word', function () { $user = User::factory()->create(); $this->actingAs($user); $name = str_repeat('a', 50); $response = $this->post(route('admin.master.categories.store'), ['name' => $name]); $response->assertSessionHasNoErrors(); $this->assertDatabaseHas('categories', [ 'name' => $name, 'slug' => $name, ]); }); test('user creates category then creates another with similar name', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'Makanan']); $this->post(route('admin.master.categories.store'), ['name' => 'Minuman']); $this->assertDatabaseCount('categories', 2); $this->assertDatabaseHas('categories', ['name' => 'Makanan', 'slug' => 'makanan']); $this->assertDatabaseHas('categories', ['name' => 'Minuman', 'slug' => 'minuman']); }); test('user creates category with name containing curly braces', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => '{code}']); $this->assertDatabaseHas('categories', [ 'name' => '{code}', 'slug' => Str::slug('{code}'), ]); }); test('user creates category with name containing angle brackets', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => '']); $this->assertDatabaseHas('categories', [ 'name' => '', 'slug' => Str::slug(''), ]); }); test('user creates category with name containing semicolon', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'A;B']); $this->assertDatabaseHas('categories', [ 'name' => 'A;B', 'slug' => Str::slug('A;B'), ]); }); test('user creates category with name containing colon', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'Time: Now']); $this->assertDatabaseHas('categories', [ 'name' => 'Time: Now', 'slug' => Str::slug('Time: Now'), ]); }); test('user creates category with name containing backtick', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => '`Code`']); $this->assertDatabaseHas('categories', [ 'name' => '`Code`', 'slug' => Str::slug('`Code`'), ]); }); test('user creates category with name containing tilde', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'Caf~']); $this->assertDatabaseHas('categories', [ 'name' => 'Caf~', 'slug' => Str::slug('Caf~'), ]); }); test('user creates category with name containing caret', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'A^B']); $this->assertDatabaseHas('categories', [ 'name' => 'A^B', 'slug' => Str::slug('A^B'), ]); }); test('user creates category with name containing percentage', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => '100% Natural']); $this->assertDatabaseHas('categories', [ 'name' => '100% Natural', 'slug' => Str::slug('100% Natural'), ]); }); test('user creates category with name containing regex special characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => '.*+?^${}()|[]\\']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing fullwidth characters', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'ABCDE']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing fullwidth brackets', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => '()【】']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing hiragana', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'あいうえお']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing katakana', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'アイウエオ']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing hangul', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => '가나다라마']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing hebrew', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'אבגדהו']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing bengali', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'অআইঈউ']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing tamil', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'அஆஇஈஉ']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing superscript', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => 'E=mc2']); $response->assertSessionHasNoErrors(); }); test('user creates category with name containing only newlines', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => "\n\n\n"]); $response->assertSessionHasErrors('name'); }); test('user creates category with name containing only tabs', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => "\t\t\t"]); $response->assertSessionHasErrors('name'); }); test('user creates category with name containing mixed whitespace and visible chars', function () { $user = User::factory()->create(); $this->actingAs($user); $response = $this->post(route('admin.master.categories.store'), ['name' => " \t Hello \n World "]); $response->assertSessionHasNoErrors(); $category = DB::table('categories')->first(); expect($category->name)->not->toBeEmpty(); }); test('user creates category with name containing plus sign', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'A+B']); $this->assertDatabaseHas('categories', [ 'name' => 'A+B', 'slug' => Str::slug('A+B'), ]); }); test('user creates category with name containing equals sign', function () { $user = User::factory()->create(); $this->actingAs($user); $this->post(route('admin.master.categories.store'), ['name' => 'A=B']); $this->assertDatabaseHas('categories', [ 'name' => 'A=B', 'slug' => Str::slug('A=B'), ]); });