## Summary - implement Spec 198 monitoring page-state contracts across Operations, Audit Log, Finding Exceptions Queue, Evidence Overview, Baseline Compare Landing, and Baseline Compare Matrix - align selected-record and draft/apply behavior with query/session restoration semantics, including canonical navigation and tenant-filter normalization helpers - add Spec 198 feature and browser coverage, update closure/spec artifacts, and refresh affected regression tests that asserted pre-contract behavior ## Verification - focused Spec 198 feature pack passed through Sail - Spec 198 browser smoke passed through Sail - existing Spec 190 and Spec 194 browser smokes passed through Sail - targeted fallout tests were updated and rerun during full-suite triage ## Notes - Livewire v4 / Filament v5 compliant only; no legacy API reintroduction - no provider registration changes; Laravel 11+ provider registration remains in `bootstrap/providers.php` - no global-search behavior changed for any resource - destructive queue decision actions remain confirmation-gated and authorization-backed - no new Filament assets were added; existing deploy step for `php artisan filament:assets` remains unchanged Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #238
176 lines
6.7 KiB
PHP
176 lines
6.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\BaselineCompareLanding;
|
|
use App\Filament\Pages\Monitoring\FindingExceptionsQueue;
|
|
use App\Filament\Resources\BaselineProfileResource;
|
|
use App\Models\AuditLog;
|
|
use App\Models\BaselineProfile;
|
|
use App\Models\BaselineSnapshot;
|
|
use App\Models\BaselineTenantAssignment;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\Finding;
|
|
use App\Models\FindingException;
|
|
use App\Models\OperationRun;
|
|
use App\Models\Tenant;
|
|
use App\Support\Evidence\EvidenceCompletenessState;
|
|
use App\Support\Evidence\EvidenceSnapshotStatus;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
pest()->browser()->timeout(20_000);
|
|
|
|
it('smokes monitoring deeplinks for operations, audit log, finding exceptions queue, and evidence overview', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
|
|
$secondTenant = Tenant::factory()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'name' => 'Second Evidence Tenant',
|
|
]);
|
|
createUserWithTenant(tenant: $secondTenant, user: $user, role: 'owner', workspaceRole: 'manager');
|
|
|
|
$activeRun = OperationRun::factory()->create([
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'type' => 'inventory_sync',
|
|
'status' => OperationRunStatus::Running->value,
|
|
'outcome' => OperationRunOutcome::Pending->value,
|
|
'created_at' => now()->subMinute(),
|
|
'started_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$audit = AuditLog::query()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'actor_email' => 'owner@example.com',
|
|
'actor_name' => 'Owner',
|
|
'actor_type' => 'human',
|
|
'action' => 'workspace.selected',
|
|
'status' => 'success',
|
|
'resource_type' => 'workspace',
|
|
'resource_id' => (string) $tenant->workspace_id,
|
|
'target_label' => 'Workspace '.$tenant->workspace_id,
|
|
'summary' => 'Workspace selected for Workspace '.$tenant->workspace_id,
|
|
'recorded_at' => now(),
|
|
]);
|
|
|
|
$finding = Finding::factory()->for($tenant)->create();
|
|
|
|
$exception = 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(),
|
|
'status' => FindingException::STATUS_PENDING,
|
|
'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT,
|
|
'request_reason' => 'Spec198 browser queue smoke.',
|
|
'requested_at' => now()->subDay(),
|
|
'review_due_at' => now()->addDay(),
|
|
'evidence_summary' => ['reference_count' => 0],
|
|
]);
|
|
|
|
foreach ([$tenant, $secondTenant] as $snapshotTenant) {
|
|
EvidenceSnapshot::query()->create([
|
|
'tenant_id' => (int) $snapshotTenant->getKey(),
|
|
'workspace_id' => (int) $snapshotTenant->workspace_id,
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
|
'generated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
$this->actingAs($user)->withSession([
|
|
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
|
|
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
|
|
(string) $tenant->workspace_id => (int) $tenant->getKey(),
|
|
],
|
|
]);
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
|
|
|
visit(route('admin.operations.index', [
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'activeTab' => 'active',
|
|
]))
|
|
->waitForText('Monitoring landing')
|
|
->assertSee('Tenant prefilters and the selected operations tab remain shareable through the URL.')
|
|
->assertSee('Open run detail')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
visit(route('admin.monitoring.audit-log', ['event' => (int) $audit->getKey()]))
|
|
->waitForText('Summary-first audit history')
|
|
->assertSee('Close details')
|
|
->assertSee('Readable context')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
visit(FindingExceptionsQueue::getUrl(panel: 'admin', parameters: ['exception' => (int) $exception->getKey()]))
|
|
->waitForText('Focused review lane')
|
|
->assertSee('Approve exception')
|
|
->assertSee('Reject exception')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
visit(route('admin.evidence.overview', [
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'search' => $tenant->name,
|
|
]))
|
|
->waitForText('Tenant and search query seeds can reopen this overview in a specific monitoring slice.')
|
|
->assertSee($tenant->name)
|
|
->assertSee('Clear filters')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
});
|
|
|
|
it('smokes compare landing to compare matrix handoff with carried subject focus', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
$subjectKey = 'wifi-corp-profile';
|
|
|
|
$profile = BaselineProfile::factory()->active()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'name' => 'Spec198 Matrix Profile',
|
|
]);
|
|
|
|
$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(),
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
$tenant->makeCurrent();
|
|
|
|
$matrixUrl = BaselineProfileResource::compareMatrixUrl($profile).'?subject_key='.urlencode($subjectKey);
|
|
|
|
visit(BaselineCompareLanding::getUrl(
|
|
parameters: [
|
|
'baseline_profile_id' => (int) $profile->getKey(),
|
|
'subject_key' => $subjectKey,
|
|
],
|
|
panel: 'tenant',
|
|
tenant: $tenant,
|
|
))
|
|
->waitForText('Open compare matrix')
|
|
->assertSee('Launch the compare matrix with the currently known baseline profile and any carried subject focus from this tenant landing.');
|
|
|
|
visit($matrixUrl)
|
|
->waitForText('Focused subject')
|
|
->assertSee($subjectKey)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
});
|