dstpabuaran.com/tests/Feature/Admin/Master/CategoryTest.php
Yoga Pangestu f562f2babe Refactor SupplierTest to use permission setup function and enhance phone number validation tests
- Introduced `giveSupplierPermissions` function to streamline user permission setup for supplier tests.
- Updated all supplier-related tests to utilize the new permission setup function.
- Enhanced phone number validation tests to accept various formats and special characters.
- Removed redundant tests related to unsupported phone number formats.
- Consolidated tests for supplier creation and updates to improve readability and maintainability.
2026-08-22 13:46:22 +07:00

1715 lines
51 KiB
PHP

<?php
use App\Models\Category;
use App\Models\Product;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
function giveCategoryPermissions(): User
{
$seeder = new RolePermissionSeeder;
$seeder->run();
$user = User::factory()->create();
$user->givePermissionTo([
'categories.view',
'categories.create',
'categories.update',
'categories.delete',
]);
return $user;
}
/*
|--------------------------------------------------------------------------
| AUTHENTICATION
|--------------------------------------------------------------------------
*/
test('guests are redirected to the login page', function () {
$response = $this->get(route('admin.master.categories.index'));
$response->assertRedirect(route('login'));
});
test('authenticated users can visit the category index page', function () {
$user = giveCategoryPermissions();
$this->actingAs($user);
$response = $this->get(route('admin.master.categories.index'));
$response->assertOk();
});
/*
|--------------------------------------------------------------------------
| INDEX PAGE
|--------------------------------------------------------------------------
*/
test('category index page displays categories', function () {
$user = giveCategoryPermissions();
$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.data', 3)
->where('categories.total', 3)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
});
test('index page works with zero categories', function () {
$user = giveCategoryPermissions();
$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.data', 0)
->where('categories.total', 0)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
});
test('index page does not display soft-deleted categories', function () {
$user = giveCategoryPermissions();
$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.data', 1)
->where('categories.total', 1)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
->where('categories.data.0.name', 'Active Category')
);
});
test('index page displays correct count after delete', function () {
$user = giveCategoryPermissions();
$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.data', 4)
->where('categories.total', 4)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
});
/*
|--------------------------------------------------------------------------
| CREATE / STORE
|--------------------------------------------------------------------------
*/
test('category can be created', function () {
$user = giveCategoryPermissions();
$this->actingAs($user);
$response = $this->post(route('admin.master.categories.store'), [
'name' => 'Kategori Test',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect();
$this->assertDatabaseHas('categories', [
'name' => 'Kategori Test',
'slug' => 'kategori-test',
]);
});
test('category slug is automatically generated from name', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$this->actingAs($user);
$response = $this->post(route('admin.master.categories.store'), [
'name' => '',
]);
$response->assertSessionHasErrors('name');
});
test('category name must be string', function () {
$user = giveCategoryPermissions();
$this->actingAs($user);
$response = $this->post(route('admin.master.categories.store'), [
'name' => 12345,
]);
$response->assertSessionHasErrors('name');
});
test('category name must not exceed 50 characters', function () {
$user = giveCategoryPermissions();
$this->actingAs($user);
$response = $this->post(route('admin.master.categories.store'), [
'name' => str_repeat('a', 51),
]);
$response->assertSessionHasErrors('name');
});
test('category name exactly 50 characters passes validation', function () {
$user = giveCategoryPermissions();
$this->actingAs($user);
$response = $this->post(route('admin.master.categories.store'), [
'name' => str_repeat('a', 50),
]);
$response->assertSessionHasNoErrors();
});
test('category name exactly 1 character passes validation', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$this->actingAs($user);
$response = $this->post(route('admin.master.categories.store'), [
'name' => '<script>alert("xss")</script>',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('categories', [
'name' => '<script>alert("xss")</script>',
]);
});
test('category name with numbers is accepted', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$this->actingAs($user);
$category = Category::factory()->create();
$response = $this->put(route('admin.master.categories.update', $category), [
'name' => 'Kategori Updated',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect();
$category->refresh();
expect($category->name)->toBe('Kategori Updated');
expect($category->slug)->toBe('kategori-updated');
});
test('category name can be updated to itself', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 50 characters', function () {
$user = giveCategoryPermissions();
$this->actingAs($user);
$category = Category::factory()->create();
$response = $this->put(route('admin.master.categories.update', $category), [
'name' => str_repeat('a', 51),
]);
$response->assertSessionHasErrors('name');
});
test('category update with special characters generates correct slug', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$this->actingAs($user);
$category = Category::factory()->create();
$response = $this->delete(route('admin.master.categories.destroy', $category));
$response
->assertSessionHasNoErrors()
->assertRedirect();
$this->assertSoftDeleted('categories', ['id' => $category->id]);
});
test('category is soft-deleted not permanently removed', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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.data', 1)
->where('categories.total', 1)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
->where('categories.data.0.name', 'Active')
);
});
test('delete flashes success toast via Inertia', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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.data', 1)
->where('categories.total', 1)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
});
test('category with many products can be deleted', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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.data', 0)
->where('categories.total', 0)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
$category->restore();
$response = $this->get(route('admin.master.categories.index'));
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/category/index')
->has('categories.data', 1)
->where('categories.total', 1)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
->where('categories.data.0.name', 'Restored Category')
);
});
test('restored category can be updated', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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.data', 2)
->where('categories.total', 2)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
});
test('user renames category to match another existing category name', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 email-like pattern', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 very short name', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$this->actingAs($user);
$this->post(route('admin.master.categories.store'), ['name' => '<html>']);
$this->assertDatabaseHas('categories', [
'name' => '<html>',
'slug' => Str::slug('<html>'),
]);
});
test('user creates category with name containing semicolon', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$this->actingAs($user);
$response = $this->post(route('admin.master.categories.store'), ['name' => '.*+?^${}()|[]\\']);
$response->assertSessionHasNoErrors();
});
test('user creates category with name containing superscript', function () {
$user = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$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 = giveCategoryPermissions();
$this->actingAs($user);
$this->post(route('admin.master.categories.store'), ['name' => 'A=B']);
$this->assertDatabaseHas('categories', [
'name' => 'A=B',
'slug' => Str::slug('A=B'),
]);
});