active()->create([ 'workspace_id' => (int) $tenant->workspace_id, ]); $snapshot = BaselineSnapshot::factory()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'baseline_profile_id' => (int) $profile->getKey(), ]); $profile->update(['active_snapshot_id' => (int) $snapshot->getKey()]); BaselineTenantAssignment::factory()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'baseline_profile_id' => (int) $profile->getKey(), ]); return [$user, $tenant, $profile, $snapshot]; } function makeBackupHealthScheduleForNeedsAttention(\App\Models\Tenant $tenant, array $attributes = []): BackupSchedule { return BackupSchedule::query()->create(array_merge([ 'tenant_id' => (int) $tenant->getKey(), 'name' => 'Needs Attention backup schedule', 'is_enabled' => true, 'timezone' => 'UTC', 'frequency' => 'daily', 'time_of_day' => '01:00:00', 'days_of_week' => null, 'policy_types' => ['deviceConfiguration'], 'include_foundations' => true, 'retention_keep_last' => 30, 'next_run_at' => now()->addHour(), ], $attributes)); } afterEach(function (): void { CarbonImmutable::setTestNow(); }); it('shows a cautionary baseline posture in needs-attention when compare trust is limited', function (): void { [$user, $tenant, $profile, $snapshot] = createNeedsAttentionTenant(); $this->actingAs($user); OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'type' => OperationRunType::BaselineCompare->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::PartiallySucceeded->value, 'completed_at' => now(), 'context' => [ 'baseline_profile_id' => (int) $profile->getKey(), 'baseline_snapshot_id' => (int) $snapshot->getKey(), 'baseline_compare' => [ 'reason_code' => BaselineCompareReasonCode::EvidenceCaptureIncomplete->value, 'coverage' => [ 'effective_types' => ['deviceConfiguration'], 'covered_types' => ['deviceConfiguration'], 'uncovered_types' => [], 'proof' => true, ], 'evidence_gaps' => [ 'count' => 2, 'by_reason' => [ 'policy_record_missing' => 2, ], ], ], ], ]); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); $component = Livewire::test(NeedsAttention::class) ->assertSee('Needs Attention') ->assertSee('Baseline compare posture') ->assertSee('The last compare finished, but normal result output was suppressed.') ->assertSee('Open Baseline Compare') ->assertDontSee('Current governance and findings signals look trustworthy.'); expect($component->html())->toContain('href='); }); it('keeps needs-attention healthy only for trustworthy compare results', function (): void { [$user, $tenant, $profile, $snapshot] = createNeedsAttentionTenant(); $this->actingAs($user); $healthyBackup = BackupSet::factory()->for($tenant)->create([ 'name' => 'Healthy compare backup', 'item_count' => 1, 'completed_at' => now()->subMinutes(20), ]); BackupItem::factory()->for($tenant)->for($healthyBackup)->create([ 'payload' => ['id' => 'healthy-policy'], 'metadata' => [], 'assignments' => [], ]); OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'type' => OperationRunType::BaselineCompare->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, 'completed_at' => now()->subHour(), 'context' => [ 'baseline_profile_id' => (int) $profile->getKey(), 'baseline_snapshot_id' => (int) $snapshot->getKey(), 'baseline_compare' => [ 'reason_code' => BaselineCompareReasonCode::NoDriftDetected->value, 'coverage' => [ 'effective_types' => ['deviceConfiguration'], 'covered_types' => ['deviceConfiguration'], 'uncovered_types' => [], 'proof' => true, ], ], ], ]); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); $component = Livewire::test(NeedsAttention::class) ->assertSee('Current governance and findings signals look trustworthy.') ->assertSee('Baseline compare looks trustworthy') ->assertSee('No confirmed drift in the latest baseline compare.') ->assertDontSee('Baseline compare posture'); expect($component->html())->not->toContain('href='); }); it('surfaces stale compare posture instead of a healthy fallback', function (): void { [$user, $tenant, $profile, $snapshot] = createNeedsAttentionTenant(); $this->actingAs($user); OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'type' => OperationRunType::BaselineCompare->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, 'completed_at' => now()->subDays(10), 'context' => [ 'baseline_profile_id' => (int) $profile->getKey(), 'baseline_snapshot_id' => (int) $snapshot->getKey(), 'baseline_compare' => [ 'reason_code' => BaselineCompareReasonCode::NoDriftDetected->value, 'coverage' => [ 'effective_types' => ['deviceConfiguration'], 'covered_types' => ['deviceConfiguration'], 'uncovered_types' => [], 'proof' => true, ], ], ], ]); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); Livewire::test(NeedsAttention::class) ->assertSee('Baseline compare posture') ->assertSee('The latest baseline compare result is stale.') ->assertSee('Open Baseline Compare') ->assertDontSee('Current governance and findings signals look trustworthy.'); }); it('surfaces compare unavailability instead of a healthy fallback when no result exists yet', function (): void { [$user, $tenant] = createNeedsAttentionTenant(); $this->actingAs($user); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); Livewire::test(NeedsAttention::class) ->assertSee('Baseline compare posture') ->assertSee('A current baseline compare result is not available yet.') ->assertSee('Open Baseline Compare') ->assertDontSee('Current governance and findings signals look trustworthy.'); }); it('surfaces overdue and lapsed-governance findings even when there are no new findings', function (): void { [$user, $tenant] = createNeedsAttentionTenant(); $this->actingAs($user); Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_TRIAGED, 'due_at' => now()->subDay(), ]); Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_RISK_ACCEPTED, ]); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); Livewire::test(NeedsAttention::class) ->assertSee('Overdue findings') ->assertSee('Lapsed accepted-risk governance') ->assertSee('Open findings') ->assertDontSee('Current governance and findings signals look trustworthy.'); }); it('surfaces expiring governance from the shared aggregate with the matching findings action', function (): void { [$user, $tenant, $profile, $snapshot] = createNeedsAttentionTenant(); $this->actingAs($user); OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'type' => OperationRunType::BaselineCompare->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, 'completed_at' => now(), 'context' => [ 'baseline_profile_id' => (int) $profile->getKey(), 'baseline_snapshot_id' => (int) $snapshot->getKey(), 'baseline_compare' => [ 'reason_code' => BaselineCompareReasonCode::NoDriftDetected->value, 'coverage' => [ 'effective_types' => ['deviceConfiguration'], 'covered_types' => ['deviceConfiguration'], 'uncovered_types' => [], 'proof' => true, ], ], ], ]); $finding = Finding::factory()->riskAccepted()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), ]); FindingException::query()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'finding_id' => (int) $finding->getKey(), 'requested_by_user_id' => (int) $user->getKey(), 'owner_user_id' => (int) $user->getKey(), 'approved_by_user_id' => (int) $user->getKey(), 'status' => FindingException::STATUS_EXPIRING, 'current_validity_state' => FindingException::VALIDITY_EXPIRING, 'request_reason' => 'Expiring governance coverage', 'approval_reason' => 'Approved for coverage', 'requested_at' => now()->subDays(2), 'approved_at' => now()->subDay(), 'effective_from' => now()->subDay(), 'expires_at' => now()->addDays(2), 'review_due_at' => now()->addDay(), 'evidence_summary' => ['reference_count' => 0], ]); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); $component = Livewire::test(NeedsAttention::class) ->assertSee('Expiring accepted-risk governance') ->assertSee('Open findings') ->assertDontSee('Current governance and findings signals look trustworthy.'); expect($component->html())->toContain('href='); }); it('keeps findings attention visible but non-clickable when the member lacks findings access', function (): void { [$user, $tenant] = createNeedsAttentionTenant(); $this->actingAs($user); Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_TRIAGED, 'due_at' => now()->subDay(), ]); Gate::define(Capabilities::TENANT_FINDINGS_VIEW, fn (): bool => false); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); $component = Livewire::test(NeedsAttention::class) ->assertSee('Overdue findings') ->assertSee('Open findings') ->assertSee(UiTooltips::INSUFFICIENT_PERMISSION); expect($component->html()) ->not->toContain(FindingResource::getUrl('index', ['tab' => 'overdue'], panel: 'tenant', tenant: $tenant)) ->toContain('Open Baseline Compare'); }); it('separates stale active attention from terminal follow-up on tenant operations attention', function (): void { [$user, $tenant] = createNeedsAttentionTenant(); $this->actingAs($user); OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'type' => 'inventory_sync', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, 'created_at' => now()->subHour(), ]); OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'type' => 'policy.sync', 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Failed->value, ]); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); Livewire::test(NeedsAttention::class) ->assertSee('Active operations look stale') ->assertSee('Terminal operations need follow-up') ->assertSee('Open stale operations') ->assertSee('Open terminal follow-up') ->assertDontSee('Current governance and findings signals look trustworthy.'); }); it('surfaces a no-backup attention item with a backup-sets destination', function (): void { [$user, $tenant] = createNeedsAttentionTenant(); $this->actingAs($user); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); $component = Livewire::test(NeedsAttention::class) ->assertSee('No usable backup basis') ->assertSee('Create or finish a backup set before relying on restore input.') ->assertSee('Open backup sets') ->assertDontSee('Backups are recent and healthy'); expect($component->html())->toContain(BackupSetResource::getUrl('index', [ 'backup_health_reason' => TenantBackupHealthAssessment::REASON_NO_BACKUP_BASIS, ], panel: 'tenant', tenant: $tenant)); }); it('surfaces stale latest-backup attention with the matching latest-backup drill-through', function (): void { CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 7, 12, 0, 0, 'UTC')); [$user, $tenant] = createNeedsAttentionTenant(); $this->actingAs($user); $staleBackup = BackupSet::factory()->for($tenant)->create([ 'name' => 'Stale backup', 'item_count' => 1, 'completed_at' => now()->subDays(2), ]); BackupItem::factory()->for($tenant)->for($staleBackup)->create([ 'payload' => ['id' => 'policy-stale'], 'metadata' => [], 'assignments' => [], ]); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); $staleComponent = Livewire::test(NeedsAttention::class) ->assertSee('Latest backup is stale') ->assertSee('Open latest backup') ->assertDontSee('Backups are recent and healthy'); expect($staleComponent->html())->toContain(BackupSetResource::getUrl('view', [ 'record' => (int) $staleBackup->getKey(), 'backup_health_reason' => TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE, ], panel: 'tenant', tenant: $tenant)); }); it('surfaces degraded latest-backup attention with the matching latest-backup drill-through', function (): void { CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 7, 12, 0, 0, 'UTC')); [$user, $tenant] = createNeedsAttentionTenant(); $this->actingAs($user); $degradedBackup = BackupSet::factory()->for($tenant)->create([ 'name' => 'Degraded backup', 'item_count' => 1, 'completed_at' => now()->subMinutes(45), ]); BackupItem::factory()->for($tenant)->for($degradedBackup)->create([ 'payload' => [], 'metadata' => [ 'source' => 'metadata_only', 'assignments_fetch_failed' => true, ], 'assignments' => [], ]); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); $degradedComponent = Livewire::test(NeedsAttention::class) ->assertSee('Latest backup is degraded') ->assertSee('Open latest backup') ->assertDontSee('Backups are recent and healthy'); expect($degradedComponent->html())->toContain(BackupSetResource::getUrl('view', [ 'record' => (int) $degradedBackup->getKey(), 'backup_health_reason' => TenantBackupHealthAssessment::REASON_LATEST_BACKUP_DEGRADED, ], panel: 'tenant', tenant: $tenant)); }); it('surfaces schedule follow-up instead of a healthy backup check when automation needs review', function (): void { CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 7, 12, 0, 0, 'UTC')); [$user, $tenant] = createNeedsAttentionTenant(); $this->actingAs($user); $healthyBackup = BackupSet::factory()->for($tenant)->create([ 'name' => 'Healthy backup', 'item_count' => 1, 'completed_at' => now()->subMinutes(20), ]); BackupItem::factory()->for($tenant)->for($healthyBackup)->create([ 'payload' => ['id' => 'healthy-policy'], 'metadata' => [], 'assignments' => [], ]); makeBackupHealthScheduleForNeedsAttention($tenant, [ 'name' => 'Overdue schedule', 'last_run_at' => null, 'last_run_status' => null, 'next_run_at' => now()->subHours(2), ]); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); $component = Livewire::test(NeedsAttention::class) ->assertSee('Backup schedules need follow-up') ->assertSee('not produced a successful run') ->assertSee('Open backup schedules') ->assertDontSee('Backups are recent and healthy'); expect($component->html())->toContain(BackupScheduleResource::getUrl('index', [ 'backup_health_reason' => TenantBackupHealthAssessment::REASON_SCHEDULE_FOLLOW_UP, ], panel: 'tenant', tenant: $tenant)); }); it('adds the healthy backup check only when the latest backup basis genuinely earns it', function (): void { CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 7, 12, 0, 0, 'UTC')); [$user, $tenant, $profile, $snapshot] = createNeedsAttentionTenant(); $this->actingAs($user); $healthyBackup = BackupSet::factory()->for($tenant)->create([ 'name' => 'Healthy backup', 'item_count' => 1, 'completed_at' => now()->subMinutes(10), ]); BackupItem::factory()->for($tenant)->for($healthyBackup)->create([ 'payload' => ['id' => 'healthy-policy'], 'metadata' => [], 'assignments' => [], ]); OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'type' => OperationRunType::BaselineCompare->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, 'completed_at' => now()->subHour(), 'context' => [ 'baseline_profile_id' => (int) $profile->getKey(), 'baseline_snapshot_id' => (int) $snapshot->getKey(), 'baseline_compare' => [ 'reason_code' => BaselineCompareReasonCode::NoDriftDetected->value, 'coverage' => [ 'effective_types' => ['deviceConfiguration'], 'covered_types' => ['deviceConfiguration'], 'uncovered_types' => [], 'proof' => true, ], ], ], ]); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); Livewire::test(NeedsAttention::class) ->assertSee('Backups are recent and healthy') ->assertSee('Baseline compare looks trustworthy') ->assertDontSee('Backup schedules need follow-up') ->assertDontSee('No usable backup basis'); }); it('keeps backup-health attention visible but non-clickable when the member lacks backup view access', function (): void { CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 7, 12, 0, 0, 'UTC')); [$user, $tenant] = createNeedsAttentionTenant(); $this->actingAs($user); $backupSet = BackupSet::factory()->for($tenant)->create([ 'name' => 'Stale hidden backup', 'item_count' => 1, 'completed_at' => now()->subDays(2), ]); BackupItem::factory()->for($tenant)->for($backupSet)->create([ 'payload' => ['id' => 'policy-stale'], 'metadata' => [], 'assignments' => [], ]); Gate::define(Capabilities::TENANT_VIEW, fn (): bool => false); Filament::setCurrentPanel(Filament::getPanel('tenant')); Filament::setTenant($tenant, true); $component = Livewire::test(NeedsAttention::class) ->assertSee('Latest backup is stale') ->assertSee('Open latest backup') ->assertSee(UiTooltips::INSUFFICIENT_PERMISSION); expect($component->html())->not->toContain(BackupSetResource::getUrl('view', [ 'record' => (int) $backupSet->getKey(), 'backup_health_reason' => TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE, ], panel: 'tenant', tenant: $tenant)); });