From 2ba17510812ed860b5f7bc254c36d80e7a3ad527 Mon Sep 17 00:00:00 2001 From: darbs-claude Date: Mon, 23 Feb 2026 01:16:33 +0000 Subject: [PATCH] test: add Livewire component tests for all 12 admin components Closes #11 Adds comprehensive Livewire tests in tests/Feature/Livewire/ covering: - DashboardTest: stats structure, refresh action, blocked alert, quick links - PlansTest: auth, filters, activate/complete/archive/delete actions - PlanDetailTest: auth, plan loading, phase actions, task validation - SessionsTest: auth, filters, pause/resume/complete/fail actions - SessionDetailTest: auth, polling, modal states, session control - ToolAnalyticsTest: auth, setDays, filters, success rate colour helpers - ApiKeysTest: auth, create/edit/revoke modals, validation, stats - ApiKeyManagerTest: workspace binding, create form, toggleScope - ToolCallsTest: auth, filters, viewCall/closeCallDetail, badge helpers - RequestLogTest: filters, selectRequest/closeDetail interactions - TemplatesTest: auth, preview/import/create modals, clearFilters - PlaygroundTest: server loading, API key validation, execute behaviour Infrastructure: - LivewireTestCase base class with stub view namespace registration - HadesUser fixture for auth()->user()->isHades() checks - Minimal stub blade views in tests/views/ (agentic and mcp namespaces) - composer.json: add livewire/livewire and pest-plugin-livewire to require-dev; fix autoload-dev paths to lowercase tests/ directory Co-Authored-By: Claude Sonnet 4.6 --- composer.json | 7 +- tests/Feature/Livewire/ApiKeyManagerTest.php | 140 +++++++++++ tests/Feature/Livewire/ApiKeysTest.php | 238 ++++++++++++++++++ tests/Feature/Livewire/DashboardTest.php | 102 ++++++++ tests/Feature/Livewire/LivewireTestCase.php | 50 ++++ tests/Feature/Livewire/PlanDetailTest.php | 229 +++++++++++++++++ tests/Feature/Livewire/PlansTest.php | 165 ++++++++++++ tests/Feature/Livewire/PlaygroundTest.php | 160 ++++++++++++ tests/Feature/Livewire/RequestLogTest.php | 87 +++++++ tests/Feature/Livewire/SessionDetailTest.php | 167 ++++++++++++ tests/Feature/Livewire/SessionsTest.php | 202 +++++++++++++++ tests/Feature/Livewire/TemplatesTest.php | 173 +++++++++++++ tests/Feature/Livewire/ToolAnalyticsTest.php | 119 +++++++++ tests/Feature/Livewire/ToolCallsTest.php | 148 +++++++++++ tests/Fixtures/HadesUser.php | 36 +++ tests/views/admin/api-keys.blade.php | 1 + tests/views/admin/dashboard.blade.php | 1 + tests/views/admin/plan-detail.blade.php | 1 + tests/views/admin/plans.blade.php | 1 + tests/views/admin/playground.blade.php | 1 + tests/views/admin/session-detail.blade.php | 1 + tests/views/admin/sessions.blade.php | 1 + tests/views/admin/templates.blade.php | 1 + tests/views/admin/tool-analytics.blade.php | 1 + tests/views/admin/tool-calls.blade.php | 1 + .../views/mcp/admin/api-key-manager.blade.php | 1 + tests/views/mcp/admin/playground.blade.php | 1 + tests/views/mcp/admin/request-log.blade.php | 1 + 28 files changed, 2034 insertions(+), 2 deletions(-) create mode 100644 tests/Feature/Livewire/ApiKeyManagerTest.php create mode 100644 tests/Feature/Livewire/ApiKeysTest.php create mode 100644 tests/Feature/Livewire/DashboardTest.php create mode 100644 tests/Feature/Livewire/LivewireTestCase.php create mode 100644 tests/Feature/Livewire/PlanDetailTest.php create mode 100644 tests/Feature/Livewire/PlansTest.php create mode 100644 tests/Feature/Livewire/PlaygroundTest.php create mode 100644 tests/Feature/Livewire/RequestLogTest.php create mode 100644 tests/Feature/Livewire/SessionDetailTest.php create mode 100644 tests/Feature/Livewire/SessionsTest.php create mode 100644 tests/Feature/Livewire/TemplatesTest.php create mode 100644 tests/Feature/Livewire/ToolAnalyticsTest.php create mode 100644 tests/Feature/Livewire/ToolCallsTest.php create mode 100644 tests/Fixtures/HadesUser.php create mode 100644 tests/views/admin/api-keys.blade.php create mode 100644 tests/views/admin/dashboard.blade.php create mode 100644 tests/views/admin/plan-detail.blade.php create mode 100644 tests/views/admin/plans.blade.php create mode 100644 tests/views/admin/playground.blade.php create mode 100644 tests/views/admin/session-detail.blade.php create mode 100644 tests/views/admin/sessions.blade.php create mode 100644 tests/views/admin/templates.blade.php create mode 100644 tests/views/admin/tool-analytics.blade.php create mode 100644 tests/views/admin/tool-calls.blade.php create mode 100644 tests/views/mcp/admin/api-key-manager.blade.php create mode 100644 tests/views/mcp/admin/playground.blade.php create mode 100644 tests/views/mcp/admin/request-log.blade.php diff --git a/composer.json b/composer.json index 15fe864..d64d80a 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,10 @@ }, "require-dev": { "laravel/pint": "^1.18", + "livewire/livewire": "^3.0", "orchestra/testbench": "^9.0|^10.0", - "pestphp/pest": "^3.0" + "pestphp/pest": "^3.0", + "pestphp/pest-plugin-livewire": "^3.0" }, "autoload": { "psr-4": { @@ -25,7 +27,8 @@ }, "autoload-dev": { "psr-4": { - "Core\\Mod\\Agentic\\Tests\\": "Tests/" + "Core\\Mod\\Agentic\\Tests\\": "tests/", + "Tests\\": "tests/" } }, "extra": { diff --git a/tests/Feature/Livewire/ApiKeyManagerTest.php b/tests/Feature/Livewire/ApiKeyManagerTest.php new file mode 100644 index 0000000..795ef0d --- /dev/null +++ b/tests/Feature/Livewire/ApiKeyManagerTest.php @@ -0,0 +1,140 @@ +workspace = Workspace::factory()->create(); + } + + public function test_renders_successfully_with_workspace(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeyManager::class, ['workspace' => $this->workspace]) + ->assertOk(); + } + + public function test_mount_loads_workspace(): void + { + $this->actingAsHades(); + + $component = Livewire::test(ApiKeyManager::class, ['workspace' => $this->workspace]); + + $this->assertEquals($this->workspace->id, $component->instance()->workspace->id); + } + + public function test_has_default_property_values(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeyManager::class, ['workspace' => $this->workspace]) + ->assertSet('showCreateModal', false) + ->assertSet('newKeyName', '') + ->assertSet('newKeyExpiry', 'never') + ->assertSet('showNewKeyModal', false) + ->assertSet('newPlainKey', null); + } + + public function test_open_create_modal_shows_modal(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeyManager::class, ['workspace' => $this->workspace]) + ->call('openCreateModal') + ->assertSet('showCreateModal', true); + } + + public function test_open_create_modal_resets_form(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeyManager::class, ['workspace' => $this->workspace]) + ->set('newKeyName', 'Old Name') + ->call('openCreateModal') + ->assertSet('newKeyName', '') + ->assertSet('newKeyExpiry', 'never'); + } + + public function test_close_create_modal_hides_modal(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeyManager::class, ['workspace' => $this->workspace]) + ->call('openCreateModal') + ->call('closeCreateModal') + ->assertSet('showCreateModal', false); + } + + public function test_create_key_requires_name(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeyManager::class, ['workspace' => $this->workspace]) + ->call('openCreateModal') + ->set('newKeyName', '') + ->call('createKey') + ->assertHasErrors(['newKeyName' => 'required']); + } + + public function test_create_key_validates_name_max_length(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeyManager::class, ['workspace' => $this->workspace]) + ->call('openCreateModal') + ->set('newKeyName', str_repeat('x', 101)) + ->call('createKey') + ->assertHasErrors(['newKeyName' => 'max']); + } + + public function test_toggle_scope_adds_scope_if_not_present(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeyManager::class, ['workspace' => $this->workspace]) + ->set('newKeyScopes', []) + ->call('toggleScope', 'read') + ->assertSet('newKeyScopes', ['read']); + } + + public function test_toggle_scope_removes_scope_if_already_present(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeyManager::class, ['workspace' => $this->workspace]) + ->set('newKeyScopes', ['read', 'write']) + ->call('toggleScope', 'read') + ->assertSet('newKeyScopes', ['write']); + } + + public function test_close_new_key_modal_clears_plain_key(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeyManager::class, ['workspace' => $this->workspace]) + ->set('newPlainKey', 'secret-key-value') + ->set('showNewKeyModal', true) + ->call('closeNewKeyModal') + ->assertSet('newPlainKey', null) + ->assertSet('showNewKeyModal', false); + } +} diff --git a/tests/Feature/Livewire/ApiKeysTest.php b/tests/Feature/Livewire/ApiKeysTest.php new file mode 100644 index 0000000..b07e82a --- /dev/null +++ b/tests/Feature/Livewire/ApiKeysTest.php @@ -0,0 +1,238 @@ +workspace = Workspace::factory()->create(); + } + + public function test_requires_hades_access(): void + { + $this->expectException(HttpException::class); + + Livewire::test(ApiKeys::class); + } + + public function test_renders_successfully_with_hades_user(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeys::class) + ->assertOk(); + } + + public function test_has_default_property_values(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeys::class) + ->assertSet('workspace', '') + ->assertSet('status', '') + ->assertSet('perPage', 25) + ->assertSet('showCreateModal', false) + ->assertSet('showEditModal', false); + } + + public function test_open_create_modal_shows_modal(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeys::class) + ->call('openCreateModal') + ->assertSet('showCreateModal', true); + } + + public function test_close_create_modal_hides_modal(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeys::class) + ->call('openCreateModal') + ->call('closeCreateModal') + ->assertSet('showCreateModal', false); + } + + public function test_open_create_modal_resets_form_fields(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeys::class) + ->set('newKeyName', 'Old Name') + ->call('openCreateModal') + ->assertSet('newKeyName', '') + ->assertSet('newKeyPermissions', []) + ->assertSet('newKeyRateLimit', 100); + } + + public function test_create_key_requires_name(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeys::class) + ->call('openCreateModal') + ->set('newKeyName', '') + ->set('newKeyWorkspace', $this->workspace->id) + ->set('newKeyPermissions', [AgentApiKey::PERM_PLANS_READ]) + ->call('createKey') + ->assertHasErrors(['newKeyName' => 'required']); + } + + public function test_create_key_requires_at_least_one_permission(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeys::class) + ->call('openCreateModal') + ->set('newKeyName', 'Test Key') + ->set('newKeyWorkspace', $this->workspace->id) + ->set('newKeyPermissions', []) + ->call('createKey') + ->assertHasErrors(['newKeyPermissions']); + } + + public function test_create_key_requires_valid_workspace(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeys::class) + ->call('openCreateModal') + ->set('newKeyName', 'Test Key') + ->set('newKeyWorkspace', 99999) + ->set('newKeyPermissions', [AgentApiKey::PERM_PLANS_READ]) + ->call('createKey') + ->assertHasErrors(['newKeyWorkspace' => 'exists']); + } + + public function test_create_key_validates_rate_limit_minimum(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeys::class) + ->call('openCreateModal') + ->set('newKeyName', 'Test Key') + ->set('newKeyWorkspace', $this->workspace->id) + ->set('newKeyPermissions', [AgentApiKey::PERM_PLANS_READ]) + ->set('newKeyRateLimit', 0) + ->call('createKey') + ->assertHasErrors(['newKeyRateLimit' => 'min']); + } + + public function test_revoke_key_marks_key_as_revoked(): void + { + $this->actingAsHades(); + + $key = AgentApiKey::generate($this->workspace, 'Test Key', [AgentApiKey::PERM_PLANS_READ]); + + Livewire::test(ApiKeys::class) + ->call('revokeKey', $key->id) + ->assertOk(); + + $this->assertNotNull($key->fresh()->revoked_at); + } + + public function test_clear_filters_resets_workspace_and_status(): void + { + $this->actingAsHades(); + + Livewire::test(ApiKeys::class) + ->set('workspace', '1') + ->set('status', 'active') + ->call('clearFilters') + ->assertSet('workspace', '') + ->assertSet('status', ''); + } + + public function test_open_edit_modal_populates_fields(): void + { + $this->actingAsHades(); + + $key = AgentApiKey::generate( + $this->workspace, + 'Edit Me', + [AgentApiKey::PERM_PLANS_READ], + 200 + ); + + Livewire::test(ApiKeys::class) + ->call('openEditModal', $key->id) + ->assertSet('showEditModal', true) + ->assertSet('editingKeyId', $key->id) + ->assertSet('editingRateLimit', 200); + } + + public function test_close_edit_modal_clears_editing_state(): void + { + $this->actingAsHades(); + + $key = AgentApiKey::generate($this->workspace, 'Test Key', [AgentApiKey::PERM_PLANS_READ]); + + Livewire::test(ApiKeys::class) + ->call('openEditModal', $key->id) + ->call('closeEditModal') + ->assertSet('showEditModal', false) + ->assertSet('editingKeyId', null); + } + + public function test_get_status_badge_class_returns_green_for_active_key(): void + { + $this->actingAsHades(); + + $key = AgentApiKey::generate($this->workspace, 'Active Key', [AgentApiKey::PERM_PLANS_READ]); + + $component = Livewire::test(ApiKeys::class); + $class = $component->instance()->getStatusBadgeClass($key->fresh()); + + $this->assertStringContainsString('green', $class); + } + + public function test_get_status_badge_class_returns_red_for_revoked_key(): void + { + $this->actingAsHades(); + + $key = AgentApiKey::generate($this->workspace, 'Revoked Key', [AgentApiKey::PERM_PLANS_READ]); + $key->update(['revoked_at' => now()]); + + $component = Livewire::test(ApiKeys::class); + $class = $component->instance()->getStatusBadgeClass($key->fresh()); + + $this->assertStringContainsString('red', $class); + } + + public function test_stats_returns_array_with_expected_keys(): void + { + $this->actingAsHades(); + + $component = Livewire::test(ApiKeys::class); + $stats = $component->instance()->stats; + + $this->assertArrayHasKey('total', $stats); + $this->assertArrayHasKey('active', $stats); + $this->assertArrayHasKey('revoked', $stats); + $this->assertArrayHasKey('total_calls', $stats); + } + + public function test_available_permissions_returns_all_permissions(): void + { + $this->actingAsHades(); + + $component = Livewire::test(ApiKeys::class); + $permissions = $component->instance()->availablePermissions; + + $this->assertIsArray($permissions); + $this->assertNotEmpty($permissions); + } +} diff --git a/tests/Feature/Livewire/DashboardTest.php b/tests/Feature/Livewire/DashboardTest.php new file mode 100644 index 0000000..9c3019c --- /dev/null +++ b/tests/Feature/Livewire/DashboardTest.php @@ -0,0 +1,102 @@ +expectException(HttpException::class); + + Livewire::test(Dashboard::class); + } + + public function test_unauthenticated_user_cannot_access(): void + { + $this->expectException(HttpException::class); + + Livewire::test(Dashboard::class); + } + + public function test_renders_successfully_with_hades_user(): void + { + $this->actingAsHades(); + + Livewire::test(Dashboard::class) + ->assertOk(); + } + + public function test_refresh_dispatches_notify_event(): void + { + $this->actingAsHades(); + + Livewire::test(Dashboard::class) + ->call('refresh') + ->assertDispatched('notify'); + } + + public function test_has_correct_initial_properties(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Dashboard::class); + + $component->assertOk(); + } + + public function test_stats_returns_array_with_expected_keys(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Dashboard::class); + + $stats = $component->instance()->stats; + + $this->assertIsArray($stats); + $this->assertArrayHasKey('active_plans', $stats); + $this->assertArrayHasKey('total_plans', $stats); + $this->assertArrayHasKey('active_sessions', $stats); + $this->assertArrayHasKey('today_sessions', $stats); + $this->assertArrayHasKey('tool_calls_7d', $stats); + $this->assertArrayHasKey('success_rate', $stats); + } + + public function test_stat_cards_returns_four_items(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Dashboard::class); + + $cards = $component->instance()->statCards; + + $this->assertIsArray($cards); + $this->assertCount(4, $cards); + } + + public function test_blocked_alert_is_null_when_no_blocked_plans(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Dashboard::class); + + $this->assertNull($component->instance()->blockedAlert); + } + + public function test_quick_links_returns_four_items(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Dashboard::class); + + $links = $component->instance()->quickLinks; + + $this->assertIsArray($links); + $this->assertCount(4, $links); + } +} diff --git a/tests/Feature/Livewire/LivewireTestCase.php b/tests/Feature/Livewire/LivewireTestCase.php new file mode 100644 index 0000000..32fab3e --- /dev/null +++ b/tests/Feature/Livewire/LivewireTestCase.php @@ -0,0 +1,50 @@ +app['view']->addNamespace('agentic', $viewsBase); + $this->app['view']->addNamespace('mcp', $viewsBase.'/mcp'); + + // Create a Hades-privileged user for component tests + $this->hadesUser = new HadesUser([ + 'id' => 1, + 'name' => 'Hades Test User', + 'email' => 'hades@test.example', + ]); + } + + /** + * Act as the Hades user (admin with full access). + */ + protected function actingAsHades(): static + { + return $this->actingAs($this->hadesUser); + } +} diff --git a/tests/Feature/Livewire/PlanDetailTest.php b/tests/Feature/Livewire/PlanDetailTest.php new file mode 100644 index 0000000..058b1d7 --- /dev/null +++ b/tests/Feature/Livewire/PlanDetailTest.php @@ -0,0 +1,229 @@ +workspace = Workspace::factory()->create(); + $this->plan = AgentPlan::factory()->draft()->create([ + 'workspace_id' => $this->workspace->id, + 'slug' => 'test-plan', + 'title' => 'Test Plan', + ]); + } + + public function test_requires_hades_access(): void + { + $this->expectException(HttpException::class); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]); + } + + public function test_renders_successfully_with_hades_user(): void + { + $this->actingAsHades(); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]) + ->assertOk(); + } + + public function test_mount_loads_plan_by_slug(): void + { + $this->actingAsHades(); + + $component = Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]); + + $this->assertEquals($this->plan->id, $component->instance()->plan->id); + $this->assertEquals('Test Plan', $component->instance()->plan->title); + } + + public function test_has_default_modal_states(): void + { + $this->actingAsHades(); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]) + ->assertSet('showAddTaskModal', false) + ->assertSet('selectedPhaseId', 0) + ->assertSet('newTaskName', '') + ->assertSet('newTaskNotes', ''); + } + + public function test_activate_plan_changes_status(): void + { + $this->actingAsHades(); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]) + ->call('activatePlan') + ->assertDispatched('notify'); + + $this->assertEquals(AgentPlan::STATUS_ACTIVE, $this->plan->fresh()->status); + } + + public function test_complete_plan_changes_status(): void + { + $this->actingAsHades(); + + $activePlan = AgentPlan::factory()->active()->create([ + 'workspace_id' => $this->workspace->id, + 'slug' => 'active-plan', + ]); + + Livewire::test(PlanDetail::class, ['slug' => $activePlan->slug]) + ->call('completePlan') + ->assertDispatched('notify'); + + $this->assertEquals(AgentPlan::STATUS_COMPLETED, $activePlan->fresh()->status); + } + + public function test_archive_plan_changes_status(): void + { + $this->actingAsHades(); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]) + ->call('archivePlan') + ->assertDispatched('notify'); + + $this->assertEquals(AgentPlan::STATUS_ARCHIVED, $this->plan->fresh()->status); + } + + public function test_complete_phase_updates_status(): void + { + $this->actingAsHades(); + + $phase = AgentPhase::factory()->inProgress()->create([ + 'agent_plan_id' => $this->plan->id, + 'order' => 1, + ]); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]) + ->call('completePhase', $phase->id) + ->assertDispatched('notify'); + + $this->assertEquals(AgentPhase::STATUS_COMPLETED, $phase->fresh()->status); + } + + public function test_block_phase_updates_status(): void + { + $this->actingAsHades(); + + $phase = AgentPhase::factory()->inProgress()->create([ + 'agent_plan_id' => $this->plan->id, + 'order' => 1, + ]); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]) + ->call('blockPhase', $phase->id) + ->assertDispatched('notify'); + + $this->assertEquals(AgentPhase::STATUS_BLOCKED, $phase->fresh()->status); + } + + public function test_skip_phase_updates_status(): void + { + $this->actingAsHades(); + + $phase = AgentPhase::factory()->pending()->create([ + 'agent_plan_id' => $this->plan->id, + 'order' => 1, + ]); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]) + ->call('skipPhase', $phase->id) + ->assertDispatched('notify'); + + $this->assertEquals(AgentPhase::STATUS_SKIPPED, $phase->fresh()->status); + } + + public function test_reset_phase_restores_to_pending(): void + { + $this->actingAsHades(); + + $phase = AgentPhase::factory()->completed()->create([ + 'agent_plan_id' => $this->plan->id, + 'order' => 1, + ]); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]) + ->call('resetPhase', $phase->id) + ->assertDispatched('notify'); + + $this->assertEquals(AgentPhase::STATUS_PENDING, $phase->fresh()->status); + } + + public function test_open_add_task_modal_sets_phase_and_shows_modal(): void + { + $this->actingAsHades(); + + $phase = AgentPhase::factory()->pending()->create([ + 'agent_plan_id' => $this->plan->id, + 'order' => 1, + ]); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]) + ->call('openAddTaskModal', $phase->id) + ->assertSet('showAddTaskModal', true) + ->assertSet('selectedPhaseId', $phase->id) + ->assertSet('newTaskName', '') + ->assertSet('newTaskNotes', ''); + } + + public function test_add_task_requires_task_name(): void + { + $this->actingAsHades(); + + $phase = AgentPhase::factory()->pending()->create([ + 'agent_plan_id' => $this->plan->id, + 'order' => 1, + ]); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]) + ->call('openAddTaskModal', $phase->id) + ->set('newTaskName', '') + ->call('addTask') + ->assertHasErrors(['newTaskName' => 'required']); + } + + public function test_add_task_validates_name_max_length(): void + { + $this->actingAsHades(); + + $phase = AgentPhase::factory()->pending()->create([ + 'agent_plan_id' => $this->plan->id, + 'order' => 1, + ]); + + Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]) + ->call('openAddTaskModal', $phase->id) + ->set('newTaskName', str_repeat('x', 256)) + ->call('addTask') + ->assertHasErrors(['newTaskName' => 'max']); + } + + public function test_get_status_color_class_returns_correct_class(): void + { + $this->actingAsHades(); + + $component = Livewire::test(PlanDetail::class, ['slug' => $this->plan->slug]); + $instance = $component->instance(); + + $this->assertStringContainsString('blue', $instance->getStatusColorClass(AgentPlan::STATUS_ACTIVE)); + $this->assertStringContainsString('green', $instance->getStatusColorClass(AgentPlan::STATUS_COMPLETED)); + $this->assertStringContainsString('red', $instance->getStatusColorClass(AgentPhase::STATUS_BLOCKED)); + } +} diff --git a/tests/Feature/Livewire/PlansTest.php b/tests/Feature/Livewire/PlansTest.php new file mode 100644 index 0000000..b4cfb69 --- /dev/null +++ b/tests/Feature/Livewire/PlansTest.php @@ -0,0 +1,165 @@ +workspace = Workspace::factory()->create(); + } + + public function test_requires_hades_access(): void + { + $this->expectException(HttpException::class); + + Livewire::test(Plans::class); + } + + public function test_renders_successfully_with_hades_user(): void + { + $this->actingAsHades(); + + Livewire::test(Plans::class) + ->assertOk(); + } + + public function test_has_default_property_values(): void + { + $this->actingAsHades(); + + Livewire::test(Plans::class) + ->assertSet('search', '') + ->assertSet('status', '') + ->assertSet('workspace', '') + ->assertSet('perPage', 15); + } + + public function test_search_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(Plans::class) + ->set('search', 'my plan') + ->assertSet('search', 'my plan'); + } + + public function test_status_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(Plans::class) + ->set('status', AgentPlan::STATUS_ACTIVE) + ->assertSet('status', AgentPlan::STATUS_ACTIVE); + } + + public function test_workspace_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(Plans::class) + ->set('workspace', (string) $this->workspace->id) + ->assertSet('workspace', (string) $this->workspace->id); + } + + public function test_clear_filters_resets_all_filters(): void + { + $this->actingAsHades(); + + Livewire::test(Plans::class) + ->set('search', 'test') + ->set('status', AgentPlan::STATUS_ACTIVE) + ->set('workspace', (string) $this->workspace->id) + ->call('clearFilters') + ->assertSet('search', '') + ->assertSet('status', '') + ->assertSet('workspace', ''); + } + + public function test_activate_plan_changes_status_to_active(): void + { + $this->actingAsHades(); + + $plan = AgentPlan::factory()->draft()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + Livewire::test(Plans::class) + ->call('activate', $plan->id) + ->assertDispatched('notify'); + + $this->assertEquals(AgentPlan::STATUS_ACTIVE, $plan->fresh()->status); + } + + public function test_complete_plan_changes_status_to_completed(): void + { + $this->actingAsHades(); + + $plan = AgentPlan::factory()->active()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + Livewire::test(Plans::class) + ->call('complete', $plan->id) + ->assertDispatched('notify'); + + $this->assertEquals(AgentPlan::STATUS_COMPLETED, $plan->fresh()->status); + } + + public function test_archive_plan_changes_status_to_archived(): void + { + $this->actingAsHades(); + + $plan = AgentPlan::factory()->active()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + Livewire::test(Plans::class) + ->call('archive', $plan->id) + ->assertDispatched('notify'); + + $this->assertEquals(AgentPlan::STATUS_ARCHIVED, $plan->fresh()->status); + } + + public function test_delete_plan_removes_from_database(): void + { + $this->actingAsHades(); + + $plan = AgentPlan::factory()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + $planId = $plan->id; + + Livewire::test(Plans::class) + ->call('delete', $planId) + ->assertDispatched('notify'); + + $this->assertDatabaseMissing('agent_plans', ['id' => $planId]); + } + + public function test_status_options_returns_all_statuses(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Plans::class); + + $options = $component->instance()->statusOptions; + + $this->assertArrayHasKey(AgentPlan::STATUS_DRAFT, $options); + $this->assertArrayHasKey(AgentPlan::STATUS_ACTIVE, $options); + $this->assertArrayHasKey(AgentPlan::STATUS_COMPLETED, $options); + $this->assertArrayHasKey(AgentPlan::STATUS_ARCHIVED, $options); + } +} diff --git a/tests/Feature/Livewire/PlaygroundTest.php b/tests/Feature/Livewire/PlaygroundTest.php new file mode 100644 index 0000000..af9944d --- /dev/null +++ b/tests/Feature/Livewire/PlaygroundTest.php @@ -0,0 +1,160 @@ +actingAsHades(); + + Livewire::test(Playground::class) + ->assertOk(); + } + + public function test_has_default_property_values(): void + { + $this->actingAsHades(); + + Livewire::test(Playground::class) + ->assertSet('selectedServer', '') + ->assertSet('selectedTool', '') + ->assertSet('arguments', []) + ->assertSet('response', '') + ->assertSet('loading', false) + ->assertSet('apiKey', '') + ->assertSet('error', null) + ->assertSet('keyStatus', null) + ->assertSet('keyInfo', null) + ->assertSet('tools', []); + } + + public function test_mount_loads_servers_gracefully_when_registry_missing(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Playground::class); + + // When registry.yaml does not exist, servers defaults to empty array + $this->assertIsArray($component->instance()->servers); + } + + public function test_updated_api_key_clears_validation_state(): void + { + $this->actingAsHades(); + + Livewire::test(Playground::class) + ->set('keyStatus', 'valid') + ->set('keyInfo', ['name' => 'Test Key']) + ->set('apiKey', 'new-key-value') + ->assertSet('keyStatus', null) + ->assertSet('keyInfo', null); + } + + public function test_validate_key_sets_empty_status_when_key_is_blank(): void + { + $this->actingAsHades(); + + Livewire::test(Playground::class) + ->set('apiKey', '') + ->call('validateKey') + ->assertSet('keyStatus', 'empty'); + } + + public function test_validate_key_sets_invalid_for_unknown_key(): void + { + $this->actingAsHades(); + + Livewire::test(Playground::class) + ->set('apiKey', 'not-a-real-key-abc123') + ->call('validateKey') + ->assertSet('keyStatus', 'invalid'); + } + + public function test_is_authenticated_returns_true_when_logged_in(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Playground::class); + + $this->assertTrue($component->instance()->isAuthenticated()); + } + + public function test_is_authenticated_returns_false_when_not_logged_in(): void + { + // No actingAs - unauthenticated request + $component = Livewire::test(Playground::class); + + $this->assertFalse($component->instance()->isAuthenticated()); + } + + public function test_updated_selected_server_clears_tool_selection(): void + { + $this->actingAsHades(); + + Livewire::test(Playground::class) + ->set('selectedTool', 'some_tool') + ->set('toolSchema', ['name' => 'some_tool']) + ->set('selectedServer', 'agent-server') + ->assertSet('selectedTool', '') + ->assertSet('toolSchema', null); + } + + public function test_updated_selected_tool_clears_arguments_and_response(): void + { + $this->actingAsHades(); + + Livewire::test(Playground::class) + ->set('arguments', ['key' => 'value']) + ->set('response', 'previous response') + ->set('selectedTool', '') + ->assertSet('toolSchema', null); + } + + public function test_execute_does_nothing_when_no_server_selected(): void + { + $this->actingAsHades(); + + Livewire::test(Playground::class) + ->set('selectedServer', '') + ->set('selectedTool', '') + ->call('execute') + ->assertSet('loading', false) + ->assertSet('response', ''); + } + + public function test_execute_generates_curl_example_without_api_key(): void + { + $this->actingAsHades(); + + Livewire::test(Playground::class) + ->set('selectedServer', 'agent-server') + ->set('selectedTool', 'plan_create') + ->call('execute') + ->assertSet('loading', false); + + // Without a valid API key, response should show the request format + $component = Livewire::test(Playground::class); + $component->set('selectedServer', 'agent-server'); + $component->set('selectedTool', 'plan_create'); + $component->call('execute'); + + $response = $component->instance()->response; + if ($response) { + $decoded = json_decode($response, true); + $this->assertIsArray($decoded); + } + } +} diff --git a/tests/Feature/Livewire/RequestLogTest.php b/tests/Feature/Livewire/RequestLogTest.php new file mode 100644 index 0000000..4fcf3b8 --- /dev/null +++ b/tests/Feature/Livewire/RequestLogTest.php @@ -0,0 +1,87 @@ +actingAsHades(); + + Livewire::test(RequestLog::class) + ->assertOk(); + } + + public function test_has_default_property_values(): void + { + $this->actingAsHades(); + + Livewire::test(RequestLog::class) + ->assertSet('serverFilter', '') + ->assertSet('statusFilter', '') + ->assertSet('selectedRequestId', null) + ->assertSet('selectedRequest', null); + } + + public function test_server_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(RequestLog::class) + ->set('serverFilter', 'agent-server') + ->assertSet('serverFilter', 'agent-server'); + } + + public function test_status_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(RequestLog::class) + ->set('statusFilter', 'success') + ->assertSet('statusFilter', 'success'); + } + + public function test_close_detail_clears_selection(): void + { + $this->actingAsHades(); + + Livewire::test(RequestLog::class) + ->set('selectedRequestId', 5) + ->call('closeDetail') + ->assertSet('selectedRequestId', null) + ->assertSet('selectedRequest', null); + } + + public function test_updated_server_filter_triggers_re_render(): void + { + $this->actingAsHades(); + + // Setting filter should update the property (pagination resets internally) + Livewire::test(RequestLog::class) + ->set('serverFilter', 'my-server') + ->assertSet('serverFilter', 'my-server') + ->assertOk(); + } + + public function test_updated_status_filter_triggers_re_render(): void + { + $this->actingAsHades(); + + Livewire::test(RequestLog::class) + ->set('statusFilter', 'failed') + ->assertSet('statusFilter', 'failed') + ->assertOk(); + } +} diff --git a/tests/Feature/Livewire/SessionDetailTest.php b/tests/Feature/Livewire/SessionDetailTest.php new file mode 100644 index 0000000..4d2f52f --- /dev/null +++ b/tests/Feature/Livewire/SessionDetailTest.php @@ -0,0 +1,167 @@ +workspace = Workspace::factory()->create(); + $this->session = AgentSession::factory()->active()->create([ + 'workspace_id' => $this->workspace->id, + ]); + } + + public function test_requires_hades_access(): void + { + $this->expectException(HttpException::class); + + Livewire::test(SessionDetail::class, ['id' => $this->session->id]); + } + + public function test_renders_successfully_with_hades_user(): void + { + $this->actingAsHades(); + + Livewire::test(SessionDetail::class, ['id' => $this->session->id]) + ->assertOk(); + } + + public function test_mount_loads_session_by_id(): void + { + $this->actingAsHades(); + + $component = Livewire::test(SessionDetail::class, ['id' => $this->session->id]); + + $this->assertEquals($this->session->id, $component->instance()->session->id); + } + + public function test_active_session_has_polling_enabled(): void + { + $this->actingAsHades(); + + $component = Livewire::test(SessionDetail::class, ['id' => $this->session->id]); + + $this->assertGreaterThan(0, $component->instance()->pollingInterval); + } + + public function test_completed_session_disables_polling(): void + { + $this->actingAsHades(); + + $completedSession = AgentSession::factory()->completed()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + $component = Livewire::test(SessionDetail::class, ['id' => $completedSession->id]); + + $this->assertEquals(0, $component->instance()->pollingInterval); + } + + public function test_has_default_modal_states(): void + { + $this->actingAsHades(); + + Livewire::test(SessionDetail::class, ['id' => $this->session->id]) + ->assertSet('showCompleteModal', false) + ->assertSet('showFailModal', false) + ->assertSet('showReplayModal', false) + ->assertSet('completeSummary', '') + ->assertSet('failReason', ''); + } + + public function test_pause_session_changes_status(): void + { + $this->actingAsHades(); + + Livewire::test(SessionDetail::class, ['id' => $this->session->id]) + ->call('pauseSession') + ->assertOk(); + + $this->assertEquals(AgentSession::STATUS_PAUSED, $this->session->fresh()->status); + } + + public function test_resume_session_changes_status_from_paused(): void + { + $this->actingAsHades(); + + $pausedSession = AgentSession::factory()->paused()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + Livewire::test(SessionDetail::class, ['id' => $pausedSession->id]) + ->call('resumeSession') + ->assertOk(); + + $this->assertEquals(AgentSession::STATUS_ACTIVE, $pausedSession->fresh()->status); + } + + public function test_open_complete_modal_shows_modal(): void + { + $this->actingAsHades(); + + Livewire::test(SessionDetail::class, ['id' => $this->session->id]) + ->call('openCompleteModal') + ->assertSet('showCompleteModal', true); + } + + public function test_open_fail_modal_shows_modal(): void + { + $this->actingAsHades(); + + Livewire::test(SessionDetail::class, ['id' => $this->session->id]) + ->call('openFailModal') + ->assertSet('showFailModal', true); + } + + public function test_open_replay_modal_shows_modal(): void + { + $this->actingAsHades(); + + Livewire::test(SessionDetail::class, ['id' => $this->session->id]) + ->call('openReplayModal') + ->assertSet('showReplayModal', true); + } + + public function test_work_log_returns_array(): void + { + $this->actingAsHades(); + + $component = Livewire::test(SessionDetail::class, ['id' => $this->session->id]); + + $this->assertIsArray($component->instance()->workLog); + } + + public function test_artifacts_returns_array(): void + { + $this->actingAsHades(); + + $component = Livewire::test(SessionDetail::class, ['id' => $this->session->id]); + + $this->assertIsArray($component->instance()->artifacts); + } + + public function test_get_status_color_class_returns_string(): void + { + $this->actingAsHades(); + + $component = Livewire::test(SessionDetail::class, ['id' => $this->session->id]); + + $class = $component->instance()->getStatusColorClass(AgentSession::STATUS_ACTIVE); + + $this->assertNotEmpty($class); + } +} diff --git a/tests/Feature/Livewire/SessionsTest.php b/tests/Feature/Livewire/SessionsTest.php new file mode 100644 index 0000000..7efadd8 --- /dev/null +++ b/tests/Feature/Livewire/SessionsTest.php @@ -0,0 +1,202 @@ +workspace = Workspace::factory()->create(); + } + + public function test_requires_hades_access(): void + { + $this->expectException(HttpException::class); + + Livewire::test(Sessions::class); + } + + public function test_renders_successfully_with_hades_user(): void + { + $this->actingAsHades(); + + Livewire::test(Sessions::class) + ->assertOk(); + } + + public function test_has_default_property_values(): void + { + $this->actingAsHades(); + + Livewire::test(Sessions::class) + ->assertSet('search', '') + ->assertSet('status', '') + ->assertSet('agentType', '') + ->assertSet('workspace', '') + ->assertSet('planSlug', '') + ->assertSet('perPage', 20); + } + + public function test_search_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(Sessions::class) + ->set('search', 'session-abc') + ->assertSet('search', 'session-abc'); + } + + public function test_status_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(Sessions::class) + ->set('status', AgentSession::STATUS_ACTIVE) + ->assertSet('status', AgentSession::STATUS_ACTIVE); + } + + public function test_agent_type_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(Sessions::class) + ->set('agentType', AgentSession::AGENT_SONNET) + ->assertSet('agentType', AgentSession::AGENT_SONNET); + } + + public function test_clear_filters_resets_all_filters(): void + { + $this->actingAsHades(); + + Livewire::test(Sessions::class) + ->set('search', 'test') + ->set('status', AgentSession::STATUS_ACTIVE) + ->set('agentType', AgentSession::AGENT_OPUS) + ->set('workspace', '1') + ->set('planSlug', 'some-plan') + ->call('clearFilters') + ->assertSet('search', '') + ->assertSet('status', '') + ->assertSet('agentType', '') + ->assertSet('workspace', '') + ->assertSet('planSlug', ''); + } + + public function test_pause_session_changes_status(): void + { + $this->actingAsHades(); + + $session = AgentSession::factory()->active()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + Livewire::test(Sessions::class) + ->call('pause', $session->id) + ->assertDispatched('notify'); + + $this->assertEquals(AgentSession::STATUS_PAUSED, $session->fresh()->status); + } + + public function test_resume_session_changes_status(): void + { + $this->actingAsHades(); + + $session = AgentSession::factory()->paused()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + Livewire::test(Sessions::class) + ->call('resume', $session->id) + ->assertDispatched('notify'); + + $this->assertEquals(AgentSession::STATUS_ACTIVE, $session->fresh()->status); + } + + public function test_complete_session_changes_status(): void + { + $this->actingAsHades(); + + $session = AgentSession::factory()->active()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + Livewire::test(Sessions::class) + ->call('complete', $session->id) + ->assertDispatched('notify'); + + $this->assertEquals(AgentSession::STATUS_COMPLETED, $session->fresh()->status); + } + + public function test_fail_session_changes_status(): void + { + $this->actingAsHades(); + + $session = AgentSession::factory()->active()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + Livewire::test(Sessions::class) + ->call('fail', $session->id) + ->assertDispatched('notify'); + + $this->assertEquals(AgentSession::STATUS_FAILED, $session->fresh()->status); + } + + public function test_get_status_color_class_returns_green_for_active(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Sessions::class); + + $class = $component->instance()->getStatusColorClass(AgentSession::STATUS_ACTIVE); + + $this->assertStringContainsString('green', $class); + } + + public function test_get_status_color_class_returns_red_for_failed(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Sessions::class); + + $class = $component->instance()->getStatusColorClass(AgentSession::STATUS_FAILED); + + $this->assertStringContainsString('red', $class); + } + + public function test_get_agent_badge_class_returns_class_for_opus(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Sessions::class); + + $class = $component->instance()->getAgentBadgeClass(AgentSession::AGENT_OPUS); + + $this->assertNotEmpty($class); + $this->assertStringContainsString('violet', $class); + } + + public function test_status_options_contains_all_statuses(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Sessions::class); + $options = $component->instance()->statusOptions; + + $this->assertArrayHasKey(AgentSession::STATUS_ACTIVE, $options); + $this->assertArrayHasKey(AgentSession::STATUS_PAUSED, $options); + $this->assertArrayHasKey(AgentSession::STATUS_COMPLETED, $options); + $this->assertArrayHasKey(AgentSession::STATUS_FAILED, $options); + } +} diff --git a/tests/Feature/Livewire/TemplatesTest.php b/tests/Feature/Livewire/TemplatesTest.php new file mode 100644 index 0000000..847ac9a --- /dev/null +++ b/tests/Feature/Livewire/TemplatesTest.php @@ -0,0 +1,173 @@ +expectException(HttpException::class); + + Livewire::test(Templates::class); + } + + public function test_renders_successfully_with_hades_user(): void + { + $this->actingAsHades(); + + Livewire::test(Templates::class) + ->assertOk(); + } + + public function test_has_default_property_values(): void + { + $this->actingAsHades(); + + Livewire::test(Templates::class) + ->assertSet('category', '') + ->assertSet('search', '') + ->assertSet('showPreviewModal', false) + ->assertSet('showCreateModal', false) + ->assertSet('showImportModal', false) + ->assertSet('previewSlug', null) + ->assertSet('importError', null); + } + + public function test_open_preview_sets_slug_and_shows_modal(): void + { + $this->actingAsHades(); + + Livewire::test(Templates::class) + ->call('openPreview', 'my-template') + ->assertSet('showPreviewModal', true) + ->assertSet('previewSlug', 'my-template'); + } + + public function test_close_preview_hides_modal_and_clears_slug(): void + { + $this->actingAsHades(); + + Livewire::test(Templates::class) + ->call('openPreview', 'my-template') + ->call('closePreview') + ->assertSet('showPreviewModal', false) + ->assertSet('previewSlug', null); + } + + public function test_open_import_modal_shows_modal_with_clean_state(): void + { + $this->actingAsHades(); + + Livewire::test(Templates::class) + ->call('openImportModal') + ->assertSet('showImportModal', true) + ->assertSet('importFileName', '') + ->assertSet('importPreview', null) + ->assertSet('importError', null); + } + + public function test_close_import_modal_hides_modal_and_clears_state(): void + { + $this->actingAsHades(); + + Livewire::test(Templates::class) + ->call('openImportModal') + ->call('closeImportModal') + ->assertSet('showImportModal', false) + ->assertSet('importError', null) + ->assertSet('importPreview', null); + } + + public function test_search_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(Templates::class) + ->set('search', 'feature') + ->assertSet('search', 'feature'); + } + + public function test_category_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(Templates::class) + ->set('category', 'development') + ->assertSet('category', 'development'); + } + + public function test_clear_filters_resets_search_and_category(): void + { + $this->actingAsHades(); + + Livewire::test(Templates::class) + ->set('search', 'test') + ->set('category', 'development') + ->call('clearFilters') + ->assertSet('search', '') + ->assertSet('category', ''); + } + + public function test_get_category_color_returns_correct_class_for_development(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Templates::class); + + $class = $component->instance()->getCategoryColor('development'); + + $this->assertStringContainsString('blue', $class); + } + + public function test_get_category_color_returns_correct_class_for_maintenance(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Templates::class); + + $class = $component->instance()->getCategoryColor('maintenance'); + + $this->assertStringContainsString('green', $class); + } + + public function test_get_category_color_returns_correct_class_for_custom(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Templates::class); + + $class = $component->instance()->getCategoryColor('custom'); + + $this->assertStringContainsString('zinc', $class); + } + + public function test_get_category_color_returns_default_for_unknown(): void + { + $this->actingAsHades(); + + $component = Livewire::test(Templates::class); + + $class = $component->instance()->getCategoryColor('unknown-category'); + + $this->assertNotEmpty($class); + } + + public function test_close_create_modal_hides_modal_and_clears_state(): void + { + $this->actingAsHades(); + + Livewire::test(Templates::class) + ->set('showCreateModal', true) + ->set('createTemplateSlug', 'some-template') + ->call('closeCreateModal') + ->assertSet('showCreateModal', false) + ->assertSet('createTemplateSlug', null) + ->assertSet('createVariables', []); + } +} diff --git a/tests/Feature/Livewire/ToolAnalyticsTest.php b/tests/Feature/Livewire/ToolAnalyticsTest.php new file mode 100644 index 0000000..9185bd2 --- /dev/null +++ b/tests/Feature/Livewire/ToolAnalyticsTest.php @@ -0,0 +1,119 @@ +expectException(HttpException::class); + + Livewire::test(ToolAnalytics::class); + } + + public function test_renders_successfully_with_hades_user(): void + { + $this->actingAsHades(); + + Livewire::test(ToolAnalytics::class) + ->assertOk(); + } + + public function test_has_default_property_values(): void + { + $this->actingAsHades(); + + Livewire::test(ToolAnalytics::class) + ->assertSet('days', 7) + ->assertSet('workspace', '') + ->assertSet('server', ''); + } + + public function test_set_days_updates_days_property(): void + { + $this->actingAsHades(); + + Livewire::test(ToolAnalytics::class) + ->call('setDays', 30) + ->assertSet('days', 30); + } + + public function test_set_days_to_seven(): void + { + $this->actingAsHades(); + + Livewire::test(ToolAnalytics::class) + ->call('setDays', 30) + ->call('setDays', 7) + ->assertSet('days', 7); + } + + public function test_workspace_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(ToolAnalytics::class) + ->set('workspace', '1') + ->assertSet('workspace', '1'); + } + + public function test_server_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(ToolAnalytics::class) + ->set('server', 'agent-server') + ->assertSet('server', 'agent-server'); + } + + public function test_clear_filters_resets_all(): void + { + $this->actingAsHades(); + + Livewire::test(ToolAnalytics::class) + ->set('workspace', '1') + ->set('server', 'agent-server') + ->call('clearFilters') + ->assertSet('workspace', '') + ->assertSet('server', ''); + } + + public function test_get_success_rate_color_class_green_above_95(): void + { + $this->actingAsHades(); + + $component = Livewire::test(ToolAnalytics::class); + + $class = $component->instance()->getSuccessRateColorClass(96.0); + + $this->assertStringContainsString('green', $class); + } + + public function test_get_success_rate_color_class_amber_between_80_and_95(): void + { + $this->actingAsHades(); + + $component = Livewire::test(ToolAnalytics::class); + + $class = $component->instance()->getSuccessRateColorClass(85.0); + + $this->assertStringContainsString('amber', $class); + } + + public function test_get_success_rate_color_class_red_below_80(): void + { + $this->actingAsHades(); + + $component = Livewire::test(ToolAnalytics::class); + + $class = $component->instance()->getSuccessRateColorClass(70.0); + + $this->assertStringContainsString('red', $class); + } +} diff --git a/tests/Feature/Livewire/ToolCallsTest.php b/tests/Feature/Livewire/ToolCallsTest.php new file mode 100644 index 0000000..422d077 --- /dev/null +++ b/tests/Feature/Livewire/ToolCallsTest.php @@ -0,0 +1,148 @@ +expectException(HttpException::class); + + Livewire::test(ToolCalls::class); + } + + public function test_renders_successfully_with_hades_user(): void + { + $this->actingAsHades(); + + Livewire::test(ToolCalls::class) + ->assertOk(); + } + + public function test_has_default_property_values(): void + { + $this->actingAsHades(); + + Livewire::test(ToolCalls::class) + ->assertSet('search', '') + ->assertSet('server', '') + ->assertSet('tool', '') + ->assertSet('status', '') + ->assertSet('workspace', '') + ->assertSet('agentType', '') + ->assertSet('perPage', 25) + ->assertSet('selectedCallId', null); + } + + public function test_search_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(ToolCalls::class) + ->set('search', 'plan_create') + ->assertSet('search', 'plan_create'); + } + + public function test_server_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(ToolCalls::class) + ->set('server', 'agent-server') + ->assertSet('server', 'agent-server'); + } + + public function test_status_filter_updates(): void + { + $this->actingAsHades(); + + Livewire::test(ToolCalls::class) + ->set('status', 'success') + ->assertSet('status', 'success'); + } + + public function test_view_call_sets_selected_call_id(): void + { + $this->actingAsHades(); + + Livewire::test(ToolCalls::class) + ->call('viewCall', 42) + ->assertSet('selectedCallId', 42); + } + + public function test_close_call_detail_clears_selection(): void + { + $this->actingAsHades(); + + Livewire::test(ToolCalls::class) + ->call('viewCall', 42) + ->call('closeCallDetail') + ->assertSet('selectedCallId', null); + } + + public function test_clear_filters_resets_all(): void + { + $this->actingAsHades(); + + Livewire::test(ToolCalls::class) + ->set('search', 'test') + ->set('server', 'server-1') + ->set('tool', 'plan_create') + ->set('status', 'success') + ->set('workspace', '1') + ->set('agentType', 'opus') + ->call('clearFilters') + ->assertSet('search', '') + ->assertSet('server', '') + ->assertSet('tool', '') + ->assertSet('status', '') + ->assertSet('workspace', '') + ->assertSet('agentType', ''); + } + + public function test_get_status_badge_class_returns_green_for_success(): void + { + $this->actingAsHades(); + + $component = Livewire::test(ToolCalls::class); + + $class = $component->instance()->getStatusBadgeClass(true); + + $this->assertStringContainsString('green', $class); + } + + public function test_get_status_badge_class_returns_red_for_failure(): void + { + $this->actingAsHades(); + + $component = Livewire::test(ToolCalls::class); + + $class = $component->instance()->getStatusBadgeClass(false); + + $this->assertStringContainsString('red', $class); + } + + public function test_get_agent_badge_class_returns_string(): void + { + $this->actingAsHades(); + + $component = Livewire::test(ToolCalls::class); + + $this->assertNotEmpty($component->instance()->getAgentBadgeClass('opus')); + $this->assertNotEmpty($component->instance()->getAgentBadgeClass('sonnet')); + $this->assertNotEmpty($component->instance()->getAgentBadgeClass('unknown')); + } +} diff --git a/tests/Fixtures/HadesUser.php b/tests/Fixtures/HadesUser.php new file mode 100644 index 0000000..c1207c7 --- /dev/null +++ b/tests/Fixtures/HadesUser.php @@ -0,0 +1,36 @@ +attributes['id'] ?? 1; + } +} diff --git a/tests/views/admin/api-keys.blade.php b/tests/views/admin/api-keys.blade.php new file mode 100644 index 0000000..b162e5e --- /dev/null +++ b/tests/views/admin/api-keys.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/admin/dashboard.blade.php b/tests/views/admin/dashboard.blade.php new file mode 100644 index 0000000..d0ef063 --- /dev/null +++ b/tests/views/admin/dashboard.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/admin/plan-detail.blade.php b/tests/views/admin/plan-detail.blade.php new file mode 100644 index 0000000..bfa75a0 --- /dev/null +++ b/tests/views/admin/plan-detail.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/admin/plans.blade.php b/tests/views/admin/plans.blade.php new file mode 100644 index 0000000..27351f8 --- /dev/null +++ b/tests/views/admin/plans.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/admin/playground.blade.php b/tests/views/admin/playground.blade.php new file mode 100644 index 0000000..f261550 --- /dev/null +++ b/tests/views/admin/playground.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/admin/session-detail.blade.php b/tests/views/admin/session-detail.blade.php new file mode 100644 index 0000000..67676f0 --- /dev/null +++ b/tests/views/admin/session-detail.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/admin/sessions.blade.php b/tests/views/admin/sessions.blade.php new file mode 100644 index 0000000..234a7ab --- /dev/null +++ b/tests/views/admin/sessions.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/admin/templates.blade.php b/tests/views/admin/templates.blade.php new file mode 100644 index 0000000..c2dcc20 --- /dev/null +++ b/tests/views/admin/templates.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/admin/tool-analytics.blade.php b/tests/views/admin/tool-analytics.blade.php new file mode 100644 index 0000000..35587d0 --- /dev/null +++ b/tests/views/admin/tool-analytics.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/admin/tool-calls.blade.php b/tests/views/admin/tool-calls.blade.php new file mode 100644 index 0000000..c0d7f13 --- /dev/null +++ b/tests/views/admin/tool-calls.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/mcp/admin/api-key-manager.blade.php b/tests/views/mcp/admin/api-key-manager.blade.php new file mode 100644 index 0000000..7a3abb3 --- /dev/null +++ b/tests/views/mcp/admin/api-key-manager.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/mcp/admin/playground.blade.php b/tests/views/mcp/admin/playground.blade.php new file mode 100644 index 0000000..f261550 --- /dev/null +++ b/tests/views/mcp/admin/playground.blade.php @@ -0,0 +1 @@ +
diff --git a/tests/views/mcp/admin/request-log.blade.php b/tests/views/mcp/admin/request-log.blade.php new file mode 100644 index 0000000..0999e49 --- /dev/null +++ b/tests/views/mcp/admin/request-log.blade.php @@ -0,0 +1 @@ +
-- 2.45.3