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

1823 lines
55 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

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

<?php
use App\Models\Category;
use App\Models\Product;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
/*
|--------------------------------------------------------------------------
| AUTHENTICATION
|--------------------------------------------------------------------------
*/
test('guests are redirected to the login page', function () {
$response = $this->get(route('admin.master.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' => '<script>alert("xss")</script>',
]);
$response->assertSessionHasNoErrors();
$this->assertDatabaseHas('categories', [
'name' => '<script>alert("xss")</script>',
]);
});
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' => '<html>']);
$this->assertDatabaseHas('categories', [
'name' => '<html>',
'slug' => Str::slug('<html>'),
]);
});
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' => '']);
$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'),
]);
});