diff --git a/app/Actions/CreateDefaultCategories.php b/app/Actions/CreateDefaultCategories.php index 0e518678..e24196fe 100644 --- a/app/Actions/CreateDefaultCategories.php +++ b/app/Actions/CreateDefaultCategories.php @@ -11,6 +11,10 @@ class CreateDefaultCategories */ public function handle(User $user): void { + if ($user->categories()->exists()) { + return; + } + $defaultCategories = self::getDefaultCategories(); foreach ($defaultCategories as $category) { diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php index 7f0d9eb6..7f701e5c 100644 --- a/database/factories/CategoryFactory.php +++ b/database/factories/CategoryFactory.php @@ -18,7 +18,7 @@ class CategoryFactory extends Factory public function definition(): array { return [ - 'name' => fake()->randomElement(['Food', 'Transport', 'Entertainment', 'Shopping', 'Healthcare', 'Utilities', 'Travel', 'Education']), + 'name' => fake()->unique()->randomElement(['Food', 'Transport', 'Entertainment', 'Shopping', 'Healthcare', 'Utilities', 'Travel', 'Education', 'Bills', 'Investments', 'Savings', 'Insurance', 'Gifts', 'Personal Care', 'Sports', 'Hobbies', 'Subscriptions', 'Dining', 'Groceries', 'Clothing']), 'icon' => fake()->randomElement(['AlertCircle', 'AlertTriangle', 'ArrowDownCircle', 'ArrowLeftRight', 'ArrowUpCircle', 'Baby', 'Banknote', 'Briefcase', 'Building', 'Building2', 'Car', 'Clock', 'CreditCard', 'Dices', 'FileText', 'Gift', 'GraduationCap', 'HandHeart', 'Heart', 'HelpCircle', 'Home', 'Landmark', 'Mail', 'PiggyBank', 'Plane', 'Receipt', 'ReceiptText', 'Repeat', 'RotateCcw', 'Scale', 'Shield', 'ShieldCheck', 'ShoppingBag', 'TrendingUp', 'Undo2', 'Users', 'Users2', 'Utensils', 'Wallet']), 'color' => fake()->randomElement(['amber', 'blue', 'cyan', 'emerald', 'gray', 'green', 'indigo', 'orange', 'pink', 'purple', 'red', 'slate', 'teal', 'yellow']), 'user_id' => User::factory(), diff --git a/database/migrations/2025_12_01_101443_add_unique_constraint_to_categories_table.php b/database/migrations/2025_12_01_101443_add_unique_constraint_to_categories_table.php new file mode 100644 index 00000000..b4854af5 --- /dev/null +++ b/database/migrations/2025_12_01_101443_add_unique_constraint_to_categories_table.php @@ -0,0 +1,28 @@ +unique(['user_id', 'name']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('categories', function (Blueprint $table) { + $table->dropUnique(['user_id', 'name']); + }); + } +}; diff --git a/tests/Feature/Settings/CategoryTest.php b/tests/Feature/Settings/CategoryTest.php index c151d757..dd044152 100644 --- a/tests/Feature/Settings/CategoryTest.php +++ b/tests/Feature/Settings/CategoryTest.php @@ -188,3 +188,36 @@ test('default categories are created when user registers', function () { $categoryNames = $user->categories->pluck('name')->toArray(); expect($categoryNames)->toContain('Food', 'Transportation', 'Salary', 'Insurance'); }); + +test('default categories are not created twice for the same user', function () { + $user = User::factory()->create(); + + $service = new \App\Actions\CreateDefaultCategories; + $service->handle($user); + + expect($user->categories()->count())->toBe(63); + + $service->handle($user); + + expect($user->categories()->count())->toBe(63); +}); + +test('category names are unique per user', function () { + $user = User::factory()->create(); + + $category = $user->categories()->create([ + 'name' => 'Test Category', + 'icon' => 'Tag', + 'color' => 'red', + ]); + + expect($category)->toBeInstanceOf(\App\Models\Category::class); + + $this->expectException(\Illuminate\Database\UniqueConstraintViolationException::class); + + $user->categories()->create([ + 'name' => 'Test Category', + 'icon' => 'Tag', + 'color' => 'blue', + ]); +});