diff --git a/composer.json b/composer.json
index 02603cf..097b7fb 100755
--- a/composer.json
+++ b/composer.json
@@ -91,7 +91,8 @@
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/",
- "Packages\\Tag\\Tests\\": "packages/Tag/tests/"
+ "Packages\\Tag\\Tests\\": "packages/Tag/tests/",
+ "Packages\\Category\\Tests\\": "packages/Category/tests/"
}
},
"scripts": {
@@ -147,4 +148,4 @@
},
"minimum-stability": "stable",
"prefer-stable": true
-}
+}
\ No newline at end of file
diff --git a/packages/Category/database/seeders/CategorySeeder.php b/packages/Category/database/seeders/CategorySeeder.php
index a2554be..b612f55 100755
--- a/packages/Category/database/seeders/CategorySeeder.php
+++ b/packages/Category/database/seeders/CategorySeeder.php
@@ -5,7 +5,6 @@
use Illuminate\Database\Seeder;
use Packages\Category\Models\Category;
use Packages\Category\Models\CategoryProfile;
-use Packages\React\Models\Follow;
class CategorySeeder extends Seeder
{
@@ -13,7 +12,6 @@ public function run(): void
{
Category::factory(10)->create()->each(function ($category) {
CategoryProfile::factory()->for($category)->create();
- Follow::factory(3)->forModel($category)->create();
});
}
}
diff --git a/packages/Category/tests/Feature/Database/Seeders/CategorySeederTest.php b/packages/Category/tests/Feature/Database/Seeders/CategorySeederTest.php
new file mode 100644
index 0000000..edafe22
--- /dev/null
+++ b/packages/Category/tests/Feature/Database/Seeders/CategorySeederTest.php
@@ -0,0 +1,42 @@
+seed(CategorySeeder::class);
+
+ $this->assertDatabaseCount(Category::class, 10);
+
+ $this->assertDatabaseCount(CategoryProfile::class, 10);
+
+ Category::all()->each(function (Category $category) {
+ $this->assertNotNull(
+ $category->profile,
+ );
+ });
+
+ CategoryProfile::all()->each(function (CategoryProfile $profile) {
+ $this->assertDatabaseHas(Category::class, [
+ 'id' => $profile->category_id,
+ ]);
+ });
+
+ $this->assertEquals(
+ 10,
+ Category::has('profile')->count(),
+ );
+
+ $this->assertEquals(
+ Category::count(),
+ CategoryProfile::count()
+ );
+ }
+}
diff --git a/packages/Category/tests/Feature/Http/Controllers/CategoryControllerTest.php b/packages/Category/tests/Feature/Http/Controllers/CategoryControllerTest.php
new file mode 100644
index 0000000..cbdc352
--- /dev/null
+++ b/packages/Category/tests/Feature/Http/Controllers/CategoryControllerTest.php
@@ -0,0 +1,117 @@
+shouldReceive('getId')
+ ->once()
+ ->with($slug)
+ ->andReturn($categoryId);
+
+ $categoryService->shouldReceive('getData')
+ ->once()
+ ->with($categoryId)
+ ->andReturn($categoryData);
+
+ $categoryService->shouldReceive('getProfileData')
+ ->once()
+ ->with($categoryId)
+ ->andReturn($profileData);
+
+ $categoryService->shouldReceive('getArticlesCount')
+ ->once()
+ ->with($categoryId)
+ ->andReturn(10);
+
+ $categoryService->shouldReceive('getNewsCount')
+ ->once()
+ ->with($categoryId)
+ ->andReturn(5);
+
+ $categoryService->shouldReceive('listTags')
+ ->once()
+ ->with($categoryId)
+ ->andReturn($tags);
+
+ $this->app->instance(CategoryService::class, $categoryService);
+
+ $metadata = Mockery::mock(BuildsMetadata::class);
+ $metadata->shouldReceive('generate')->once()->andReturn('seo-html');
+
+ $seoService = Mockery::mock(SeoService::class);
+ $seoService->shouldReceive('getCategorySeo')
+ ->once()
+ ->with($categoryData, $profileData)
+ ->andReturn($metadata);
+
+ $this->app->instance(SeoService::class, $seoService);
+
+ $response = $this->get(route('category.view', $slug));
+
+ $response->assertStatus(Response::HTTP_OK);
+
+ $response->assertInertia(
+ fn (Assert $page) => $page
+ ->component('Category/Detail')
+
+ ->where('category.id', $categoryId)
+ ->where('category.slug', $slug)
+ ->where('category.name', 'Test Category')
+
+ ->where('profile.description', 'Test Description')
+ ->where('profile.color', '#fff')
+
+ ->where('articles', 10)
+ ->where('news', 5)
+
+ ->has('tags', 1)
+ ->where('tags.0.id', 5)
+ ->where('tags.0.name', 'Test Tag')
+ ->where('tags.0.slug', 'test-tag')
+ );
+
+ $this->assertArrayHasKey('seo', $response->original->getData());
+ }
+}
diff --git a/packages/Category/tests/TestCase.php b/packages/Category/tests/TestCase.php
new file mode 100644
index 0000000..d8c2bab
--- /dev/null
+++ b/packages/Category/tests/TestCase.php
@@ -0,0 +1,10 @@
+assertSame(1, $data->id);
+ $this->assertSame('Test Category', $data->name);
+ $this->assertSame('test-category', $data->slug);
+ $this->assertSame(10, $data->followers);
+ $this->assertTrue($data->isFollowing);
+
+ $this->assertSame('category', $data->type);
+ }
+
+ public function test_it_creates_data_from_model_without_id_by_default(): void
+ {
+ $category = Category::factory()->create([
+ 'name' => 'Inertia',
+ 'slug' => 'inertia',
+ ]);
+
+ $categoryData = CategoryData::fromModel($category);
+
+ $this->assertSame(0, $categoryData->id);
+ $this->assertSame('Inertia', $categoryData->name);
+ $this->assertSame('inertia', $categoryData->slug);
+ $this->assertSame('category', $categoryData->type);
+ }
+
+ public function test_it_sets_id_when_flag_is_true(): void
+ {
+ $category = Category::factory()->create([
+ 'name' => 'Laravel',
+ 'slug' => 'laravel',
+ ]);
+
+ $categoryData = CategoryData::fromModel($category, true);
+
+ $this->assertSame($category->id, $categoryData->id);
+ $this->assertSame('Laravel', $categoryData->name);
+ $this->assertSame('laravel', $categoryData->slug);
+ $this->assertSame('category', $categoryData->type);
+ }
+
+ public function test_it_uses_model_methods_for_followers_and_follow_state(): void
+ {
+ $userId = 123;
+ Auth::shouldReceive('id')->once()->andReturn($userId);
+
+ $category = Mockery::mock(Category::class)->makePartial();
+ $category->id = 99;
+ $category->name = 'PHP';
+ $category->slug = 'php';
+
+ $category->shouldReceive('followersCount')
+ ->once()
+ ->andReturn(42);
+
+ $category->shouldReceive('isFollowedBy')
+ ->once()
+ ->with($userId)
+ ->andReturn(true);
+
+ $data = CategoryData::fromModel($category, true);
+
+ $this->assertSame(99, $data->id);
+ $this->assertSame(42, $data->followers);
+ $this->assertTrue($data->isFollowing);
+ }
+
+ public function test_it_handles_guest_user(): void
+ {
+ Auth::shouldReceive('id')->once()->andReturn(null);
+
+ $category = Mockery::mock(Category::class)->makePartial();
+ $category->id = 1;
+ $category->name = 'Guest';
+ $category->slug = 'guest';
+
+ $category->shouldReceive('followersCount')->once()->andReturn(0);
+ $category->shouldReceive('isFollowedBy')
+ ->once()
+ ->with(null)
+ ->andReturn(false);
+
+ $data = CategoryData::fromModel($category);
+
+ $this->assertFalse($data->isFollowing);
+ }
+}
diff --git a/packages/Category/tests/Unit/Data/CategoryProfileDataTest.php b/packages/Category/tests/Unit/Data/CategoryProfileDataTest.php
new file mode 100644
index 0000000..68b4434
--- /dev/null
+++ b/packages/Category/tests/Unit/Data/CategoryProfileDataTest.php
@@ -0,0 +1,34 @@
+assertSame(1, $data->id);
+ $this->assertSame('Test Description', $data->description);
+ $this->assertSame('#ffffff', $data->color);
+ }
+
+ public function test_it_sets_id_when_flag_is_true(): void
+ {
+ $profile = CategoryProfile::factory()->create();
+
+ $data = CategoryProfileData::fromModel($profile, true);
+
+ $this->assertSame($profile->id, $data->id);
+ $this->assertSame($profile->description, $data->description);
+ $this->assertSame($profile->color, $data->color);
+ }
+}
diff --git a/packages/Category/tests/Unit/Database/Factories/CategoryFactoryTest.php b/packages/Category/tests/Unit/Database/Factories/CategoryFactoryTest.php
new file mode 100644
index 0000000..e8dfd15
--- /dev/null
+++ b/packages/Category/tests/Unit/Database/Factories/CategoryFactoryTest.php
@@ -0,0 +1,65 @@
+create();
+
+ $this->assertInstanceOf(Category::class, $category);
+
+ $this->assertNotEmpty($category->name);
+ $this->assertNotEmpty($category->slug);
+ }
+
+ public function test_name_starts_with_uppercase_letter(): void
+ {
+ $category = Category::factory()->create();
+
+ $this->assertSame(
+ ucfirst(strtolower($category->name)),
+ $category->name
+ );
+ }
+
+ public function test_slug_is_a_valid_slug(): void
+ {
+ $category = Category::factory()->create();
+
+ $this->assertSame(
+ Str::slug($category->name),
+ $category->slug
+ );
+ }
+
+ public function test_slug_contains_only_slug_characters(): void
+ {
+ $category = Category::factory()->create();
+
+ $this->assertMatchesRegularExpression(
+ '/^[a-z0-9]+(?:-[a-z0-9]+)*$/',
+ $category->slug
+ );
+ }
+
+ public function test_factory_creates_unique_categories(): void
+ {
+ $categories = Category::factory()->count(10)->create();
+
+ $this->assertCount(
+ 10,
+ $categories->pluck('name')->unique()
+ );
+
+ $this->assertCount(
+ 10,
+ $categories->pluck('slug')->unique()
+ );
+ }
+}
diff --git a/packages/Category/tests/Unit/Database/Factories/CategoryProfileFactoryTest.php b/packages/Category/tests/Unit/Database/Factories/CategoryProfileFactoryTest.php
new file mode 100644
index 0000000..3fb297b
--- /dev/null
+++ b/packages/Category/tests/Unit/Database/Factories/CategoryProfileFactoryTest.php
@@ -0,0 +1,50 @@
+create();
+
+ $this->assertInstanceOf(CategoryProfile::class, $profile);
+
+ $this->assertNotEmpty($profile->description);
+
+ $this->assertNotEmpty($profile->color);
+ $this->assertMatchesRegularExpression(
+ '/^#[0-9a-fA-F]{6}$/',
+ $profile->color
+ );
+
+ $this->assertDatabaseHas('categories', ['id' => $profile->category_id]);
+ }
+
+ public function test_it_creates_profile_for_given_category(): void
+ {
+ $category = Category::factory()->create();
+
+ $profile = CategoryProfile::factory()->create([
+ 'category_id' => $category->id,
+ ]);
+
+ $this->assertSame($category->id, $profile->category_id);
+ $this->assertTrue($profile->category->is($category));
+ }
+
+ public function test_factory_creates_profiles_with_valid_foreign_keys(): void
+ {
+ $profiles = CategoryProfile::factory()->count(5)->create();
+
+ foreach ($profiles as $profile) {
+ $this->assertDatabaseHas('categories', [
+ 'id' => $profile->category_id,
+ ]);
+ }
+ }
+}
diff --git a/packages/Category/tests/Unit/Models/CategoryProfileTest.php b/packages/Category/tests/Unit/Models/CategoryProfileTest.php
new file mode 100644
index 0000000..b0082f5
--- /dev/null
+++ b/packages/Category/tests/Unit/Models/CategoryProfileTest.php
@@ -0,0 +1,117 @@
+assertSame(
+ ['description', 'color', 'category_id'],
+ $profile->getFillable()
+ );
+ }
+
+ public function test_it_can_be_created_via_factory(): void
+ {
+ $profile = CategoryProfile::factory()->create();
+
+ $this->assertInstanceOf(CategoryProfile::class, $profile);
+ $this->assertNotEmpty($profile->description);
+ $this->assertNotEmpty($profile->color);
+ }
+
+ public function test_it_can_be_mass_assigned(): void
+ {
+ $category = Category::factory()->create();
+
+ $data = [
+ 'description' => 'test-description',
+ 'color' => '#ffffff',
+ 'category_id' => $category->id,
+ ];
+
+ $profile = CategoryProfile::create($data);
+
+ $this->assertDatabaseHas(CategoryProfile::class, $data);
+ $this->assertInstanceOf(CategoryProfile::class, $profile);
+ $this->assertEquals($data['description'], $profile->description);
+ $this->assertEquals($data['color'], $profile->color);
+ }
+
+ public function test_it_belongs_to_a_category(): void
+ {
+ $category = Category::factory()->create();
+ $profile = CategoryProfile::create([
+ 'description' => 'test-description',
+ 'color' => '#ffffff',
+ 'category_id' => $category->id,
+ ]);
+
+ $this->assertInstanceOf(
+ \Illuminate\Database\Eloquent\Relations\BelongsTo::class,
+ $profile->category()
+ );
+
+ $this->assertTrue($profile->category->is($category));
+ }
+
+ public function test_it_logs_activity_on_create(): void
+ {
+ $category = Category::factory()->create();
+ CategoryProfile::create([
+ 'description' => 'test-description',
+ 'color' => '#ffffff',
+ 'category_id' => $category->id,
+ ]);
+
+ $activity = Activity::orderBy('id', 'desc')->first();
+
+ $this->assertSame('category_profile', $activity->log_name);
+ $this->assertSame('created', $activity->description);
+ $this->assertArrayHasKey('description', $activity->properties['attributes']);
+ $this->assertArrayHasKey('color', $activity->properties['attributes']);
+ $this->assertSame('test-description', $activity->properties['attributes']['description']);
+ $this->assertSame('#ffffff', $activity->properties['attributes']['color']);
+ }
+
+ public function test_it_logs_only_dirty_attributes_on_update(): void
+ {
+ $category = Category::factory()->create();
+ $profile = CategoryProfile::create([
+ 'description' => 'Old',
+ 'color' => '#ffffff',
+ 'category_id' => $category->id,
+ ]);
+
+ $profile->update([
+ 'description' => 'New',
+ ]);
+
+ $activity = Activity::orderBy('id', 'desc')->first();
+
+ $this->assertSame('category_profile', $activity->log_name);
+ $this->assertSame('updated', $activity->description);
+ $this->assertArrayHasKey('description', $activity->properties['attributes']);
+ $this->assertSame('New', $activity->properties['attributes']['description']);
+ $this->assertSame('Old', $activity->properties['old']['description']);
+ }
+
+ public function test_it_does_not_log_when_nothing_changes(): void
+ {
+ $category = CategoryProfile::factory()->create();
+
+ $initialCount = Activity::count();
+
+ $category->update(['description' => $category->description]);
+
+ $this->assertSame($initialCount, Activity::count());
+ }
+}
diff --git a/packages/Category/tests/Unit/Models/CategoryTest.php b/packages/Category/tests/Unit/Models/CategoryTest.php
new file mode 100644
index 0000000..e77e059
--- /dev/null
+++ b/packages/Category/tests/Unit/Models/CategoryTest.php
@@ -0,0 +1,113 @@
+assertSame(
+ ['name', 'slug'],
+ $category->getFillable()
+ );
+ }
+
+ public function test_it_can_be_created_via_factory(): void
+ {
+ $category = Category::factory()->create();
+
+ $this->assertInstanceOf(Category::class, $category);
+ $this->assertNotEmpty($category->name);
+ $this->assertNotEmpty($category->slug);
+ }
+
+ public function test_it_can_be_mass_assigned(): void
+ {
+ $data = [
+ 'name' => 'Laravel',
+ 'slug' => 'laravel',
+ ];
+
+ $category = Category::create($data);
+
+ $this->assertDatabaseHas(Category::class, $data);
+ $this->assertInstanceOf(Category::class, $category);
+ $this->assertEquals($data['name'], $category->name);
+ $this->assertEquals($data['slug'], $category->slug);
+ }
+
+ public function test_it_has_a_profile_relationship(): void
+ {
+ $category = Category::factory()->create();
+ $profile = CategoryProfile::factory()->create(['category_id' => $category->id]);
+
+ $this->assertTrue($category->profile()->exists());
+ $this->assertInstanceOf(CategoryProfile::class, $category->profile);
+ $this->assertEquals($profile->id, $category->profile->id);
+ }
+
+ public function test_it_can_have_many_articles(): void
+ {
+ $category = Category::factory()->create();
+ $articles = Article::factory()->count(3)->create();
+
+ $category->articles()->attach($articles->pluck('id'));
+
+ $this->assertCount(3, $category->articles);
+ $this->assertInstanceOf(Article::class, $category->articles->first());
+ }
+
+ public function test_it_logs_activity_on_create(): void
+ {
+ Category::factory()->create([
+ 'name' => 'PHP',
+ 'slug' => 'php',
+ ]);
+
+ $activity = Activity::latest()->first();
+
+ $this->assertSame('category', $activity->log_name);
+ $this->assertSame('created', $activity->description);
+ $this->assertArrayHasKey('name', $activity->properties['attributes']);
+ $this->assertArrayHasKey('slug', $activity->properties['attributes']);
+ $this->assertSame('PHP', $activity->properties['attributes']['name']);
+ $this->assertSame('php', $activity->properties['attributes']['slug']);
+ }
+
+ public function test_it_logs_only_dirty_attributes_on_update(): void
+ {
+ $category = Category::factory()->create([
+ 'name' => 'Old',
+ 'slug' => 'old',
+ ]);
+
+ $category->update(['name' => 'New']);
+
+ $activity = Activity::orderBy('id', 'desc')->first();
+
+ $this->assertSame('updated', $activity->description);
+ $this->assertArrayHasKey('name', $activity->properties['attributes']);
+ $this->assertArrayNotHasKey('slug', $activity->properties['attributes']);
+ $this->assertSame('New', $activity->properties['attributes']['name']);
+ $this->assertSame('Old', $activity->properties['old']['name']);
+ }
+
+ public function test_it_does_not_log_when_nothing_changes(): void
+ {
+ $category = Category::factory()->create();
+
+ $initialCount = Activity::count();
+
+ $category->update(['name' => $category->name]);
+
+ $this->assertSame($initialCount, Activity::count());
+ }
+}
diff --git a/packages/Category/tests/Unit/Observers/CategoryObserverTest.php b/packages/Category/tests/Unit/Observers/CategoryObserverTest.php
new file mode 100644
index 0000000..50419c8
--- /dev/null
+++ b/packages/Category/tests/Unit/Observers/CategoryObserverTest.php
@@ -0,0 +1,78 @@
+once()
+ ->with('category:old-slug:id');
+
+ Cache::shouldReceive('forget')
+ ->once()
+ ->with('category:1');
+
+ $category = new Category();
+ $category->id = 1;
+ $category->slug = 'old-slug';
+ $category->syncOriginal();
+
+ $category->slug = 'new-slug';
+
+ (new CategoryObserver())->updated($category);
+ }
+
+ public function test_it_does_not_clear_slug_cache_if_slug_not_changed(): void
+ {
+ Cache::shouldReceive('forget')
+ ->once()
+ ->with('category:1');
+
+ Cache::shouldReceive('forget')
+ ->never()
+ ->with('category:slug:id');
+
+ $category = new Category();
+ $category->id = 1;
+ $category->slug = 'slug';
+ $category->syncOriginal();
+
+ (new CategoryObserver())->updated($category);
+ }
+
+ public function test_update_always_clears_id_cache(): void
+ {
+ Cache::shouldReceive('forget')
+ ->once()
+ ->with('category:1');
+
+ $category = new Category();
+ $category->id = 1;
+ $category->slug = 'slug';
+ $category->syncOriginal();
+
+ (new CategoryObserver())->updated($category);
+ }
+
+ public function test_it_clears_all_related_cache_on_delete(): void
+ {
+ Cache::shouldReceive('forget')->once()->with('category:slug:id');
+ Cache::shouldReceive('forget')->once()->with('category:1');
+ Cache::shouldReceive('forget')->once()->with('category:1:articles');
+ Cache::shouldReceive('forget')->once()->with('category:1:news');
+ Cache::shouldReceive('forget')->once()->with('category:1:followers');
+
+ $category = new Category();
+ $category->id = 1;
+ $category->slug = 'slug';
+
+ (new CategoryObserver())->deleted($category);
+ }
+}
diff --git a/packages/Category/tests/Unit/Observers/CategoryProfileObserverTest.php b/packages/Category/tests/Unit/Observers/CategoryProfileObserverTest.php
new file mode 100644
index 0000000..712df4b
--- /dev/null
+++ b/packages/Category/tests/Unit/Observers/CategoryProfileObserverTest.php
@@ -0,0 +1,42 @@
+once()
+ ->with('category:1:profile');
+
+ $category = new Category();
+ $category->id = 1;
+
+ $profile = new CategoryProfile();
+ $profile->setRelation('category', $category);
+
+ (new CategoryProfileObserver())->updated($profile);
+ }
+
+ public function test_it_clears_profile_cache_on_delete(): void
+ {
+ Cache::shouldReceive('forget')
+ ->once()
+ ->with('category:1:profile');
+
+ $category = new Category();
+ $category->id = 1;
+
+ $profile = new CategoryProfile();
+ $profile->setRelation('category', $category);
+
+ (new CategoryProfileObserver())->deleted($profile);
+ }
+}
diff --git a/packages/Category/tests/Unit/Policies/CategoryPolicyTest.php b/packages/Category/tests/Unit/Policies/CategoryPolicyTest.php
new file mode 100644
index 0000000..5ef6745
--- /dev/null
+++ b/packages/Category/tests/Unit/Policies/CategoryPolicyTest.php
@@ -0,0 +1,156 @@
+policy = new CategoryPolicy();
+ }
+
+ public function test_view(): void
+ {
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('view_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('view_category')->andReturn(true);
+ $this->assertTrue($this->policy->view($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('view_any_category')->andReturn(true);
+ $this->assertTrue($this->policy->view($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('view_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('view_category')->andReturn(false);
+ $this->assertFalse($this->policy->view($user));
+ }
+
+ public function test_create(): void
+ {
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('create_category')->andReturn(true);
+ $this->assertTrue($this->policy->create($user));
+ }
+
+ public function test_update(): void
+ {
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('update_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('update_category')->andReturn(true);
+ $this->assertTrue($this->policy->update($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('update_any_category')->andReturn(true);
+ $this->assertTrue($this->policy->update($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('update_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('update_category')->andReturn(false);
+ $this->assertFalse($this->policy->update($user));
+ }
+
+ public function test_delete(): void
+ {
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('delete_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('delete_category')->andReturn(true);
+ $this->assertTrue($this->policy->delete($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('delete_any_category')->andReturn(true);
+ $this->assertTrue($this->policy->delete($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('delete_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('delete_category')->andReturn(false);
+ $this->assertFalse($this->policy->delete($user));
+ }
+
+ public function test_delete_any(): void
+ {
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('delete_any_category')->andReturn(true);
+ $this->assertTrue($this->policy->deleteAny($user));
+ }
+
+ public function test_force_delete(): void
+ {
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('force_delete_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('force_delete_category')->andReturn(true);
+ $this->assertTrue($this->policy->forceDelete($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('force_delete_any_category')->andReturn(true);
+ $this->assertTrue($this->policy->forceDelete($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('force_delete_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('force_delete_category')->andReturn(false);
+ $this->assertFalse($this->policy->forceDelete($user));
+ }
+
+ public function test_force_delete_any(): void
+ {
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('force_delete_any_category')->andReturn(true);
+ $this->assertTrue($this->policy->forceDeleteAny($user));
+ }
+
+ public function test_restore(): void
+ {
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('restore_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('restore_category')->andReturn(true);
+ $this->assertTrue($this->policy->restore($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('restore_any_category')->andReturn(true);
+ $this->assertTrue($this->policy->restore($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('restore_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('restore_category')->andReturn(false);
+ $this->assertFalse($this->policy->restore($user));
+ }
+
+ public function test_restore_any(): void
+ {
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('restore_any_category')->andReturn(true);
+ $this->assertTrue($this->policy->restoreAny($user));
+ }
+
+ public function test_replicate(): void
+ {
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('replicate_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('replicate_category')->andReturn(true);
+ $this->assertTrue($this->policy->replicate($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('replicate_any_category')->andReturn(true);
+ $this->assertTrue($this->policy->replicate($user));
+
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('replicate_any_category')->andReturn(false);
+ $user->shouldReceive('can')->with('replicate_category')->andReturn(false);
+ $this->assertFalse($this->policy->replicate($user));
+ }
+
+ public function test_reorder(): void
+ {
+ $user = Mockery::mock(User::class);
+ $user->shouldReceive('can')->with('reorder_category')->andReturn(true);
+ $this->assertTrue($this->policy->reorder($user));
+ }
+}
diff --git a/packages/Category/tests/Unit/Services/CategoryServiceTest.php b/packages/Category/tests/Unit/Services/CategoryServiceTest.php
new file mode 100644
index 0000000..5458413
--- /dev/null
+++ b/packages/Category/tests/Unit/Services/CategoryServiceTest.php
@@ -0,0 +1,118 @@
+service = app(CategoryService::class);
+ }
+
+ public function test_it_can_get_category_data_by_id(): void
+ {
+ $category = Category::factory()->create();
+
+ $data = $this->service->getData($category->id);
+
+ $this->assertEquals($category->name, $data->name);
+ $this->assertEquals($category->slug, $data->slug);
+ $this->assertTrue(Cache::has("category:{$category->id}"));
+ }
+
+ public function test_it_can_get_category_id_by_slug(): void
+ {
+ $category = Category::factory()->create();
+
+ $id = $this->service->getId($category->slug);
+
+ $this->assertEquals($category->id, $id);
+ $this->assertTrue(Cache::has("category:{$category->slug}:id"));
+ }
+
+ public function test_it_can_get_profile_data(): void
+ {
+ $category = Category::factory()->create();
+ $profile = CategoryProfile::factory()->create(['category_id' => $category->id]);
+
+ $data = $this->service->getProfileData($category->id);
+
+ $this->assertEquals($profile->description, $data->description);
+ $this->assertEquals($profile->color, $data->color);
+ $this->assertTrue(Cache::has("category:{$category->id}:profile"));
+ }
+
+ public function test_it_can_count_articles(): void
+ {
+ $category = Category::factory()->create();
+
+ $article = Article::factory()->create();
+ $article->categories()->attach($category);
+
+ $count = $this->service->getArticlesCount($category->id);
+
+ $this->assertEquals(1, $count);
+ $this->assertTrue(Cache::has("category:{$category->id}:articles"));
+ }
+
+ public function test_it_can_count_news(): void
+ {
+ $category = Category::factory()->create();
+
+ $news = News::factory()->create();
+ $news->categories()->attach($category);
+
+ $count = $this->service->getNewsCount($category->id);
+
+ $this->assertEquals(1, $count);
+ $this->assertTrue(Cache::has("category:{$category->id}:news"));
+ }
+
+ public function test_it_can_list_tags_by_category(): void
+ {
+ $category = Category::factory()->create();
+
+ $tagA = Tag::factory()->create();
+ $profileA = TagProfile::factory()->create(['tag_id' => $tagA->id]);
+ $profileA->categories()->attach($category);
+
+ $otherCategory = Category::factory()->create();
+ $tagB = Tag::factory()->create();
+ $profileB = TagProfile::factory()->create(['tag_id' => $tagB->id]);
+ $profileB->categories()->attach($otherCategory);
+
+ $tagC = Tag::factory()->create();
+ TagProfile::factory()->create(['tag_id' => $tagC->id]);
+
+ $tags = $this->service->listTags($category->id);
+
+ $this->assertTrue($tags->contains('slug', $tagA->slug));
+ $this->assertFalse($tags->contains('slug', $tagB->slug));
+ $this->assertFalse($tags->contains('slug', $tagC->slug));
+
+ $this->assertTrue(Cache::has("category:{$category->id}:tags"));
+ }
+
+ public function test_it_can_list_related_empty_when_no_profile(): void
+ {
+ $category = Category::factory()->create();
+
+ $related = $this->service->listTags($category->id);
+
+ $this->assertTrue($related->isEmpty());
+ }
+}
diff --git a/phpunit.xml b/phpunit.xml
index ec19c89..f02789f 100755
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -7,6 +7,7 @@
packages/Tag/tests
+ packages/Category/tests