TenantAtlas/apps/platform/tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php
ahmido 74210bac2e feat: add baseline compare operator modes (#224)
## Summary
- add adaptive baseline compare presentation modes with `auto`, `dense`, and `compact` route handling on the existing matrix page
- compress support surfaces with staged filters, grouped legends, last-updated and passive refresh cues, compact single-tenant results, and dense multi-tenant scan rendering
- extend the matrix builder plus Pest and browser smoke coverage for visible-set-only compact and dense workflows

## Filament / Laravel notes
- Livewire v4 compliance preserved; no legacy Livewire v3 patterns introduced
- provider registration is unchanged; no `bootstrap/providers.php` changes were needed for this feature
- no globally searchable resources were changed by this branch
- no destructive actions were added; the existing compare action remains simulation-only and non-destructive
- asset strategy is unchanged; no new Filament assets were introduced

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/BaselineCompareMatrixPageTest.php tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php tests/Feature/Guards/ActionSurfaceContractTest.php tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php`
- `80` tests passed with `673` assertions
- integrated browser smoke run on `http://localhost/admin/baseline-profiles/20/compare-matrix`

## Scope
- Spec 191 implementation
- spec contract updates in `spec.md`, `tasks.md`, and the logical OpenAPI contract

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #224
2026-04-11 15:48:22 +00:00

150 lines
5.1 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Resources\BaselineProfileResource;
use App\Models\Finding;
use App\Models\User;
use App\Models\WorkspaceMembership;
use App\Support\Workspaces\WorkspaceContext;
use Tests\Feature\Concerns\BuildsBaselineCompareMatrixFixtures;
uses(BuildsBaselineCompareMatrixFixtures::class);
pest()->browser()->timeout(15_000);
it('smokes dense multi-tenant scanning and finding drilldown continuity', function (): void {
$fixture = $this->makeBaselineCompareMatrixFixture();
$run = $this->makeBaselineCompareMatrixRun(
$fixture['visibleTenant'],
$fixture['profile'],
$fixture['snapshot'],
);
$this->makeBaselineCompareMatrixRun(
$fixture['visibleTenantTwo'],
$fixture['profile'],
$fixture['snapshot'],
);
$this->makeBaselineCompareMatrixFinding(
$fixture['visibleTenant'],
$fixture['profile'],
$run,
'wifi-corp-profile',
['severity' => Finding::SEVERITY_CRITICAL],
);
$this->actingAs($fixture['user'])->withSession([
WorkspaceContext::SESSION_KEY => (int) $fixture['workspace']->getKey(),
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
(string) $fixture['workspace']->getKey() => (int) $fixture['visibleTenant']->getKey(),
],
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $fixture['workspace']->getKey());
$page = visit(BaselineProfileResource::compareMatrixUrl($fixture['profile']));
$page
->assertNoJavaScriptErrors()
->waitForText('Requested: Auto mode. Resolved: Dense mode.')
->assertSee('Dense multi-tenant scan')
->assertSee('Grouped legend')
->assertSee('Open finding')
->assertSee('More follow-up')
->click('Open finding')
->waitForText('Back to compare matrix')
->assertNoJavaScriptErrors()
->assertSee('Back to compare matrix');
});
it('smokes the compact single-tenant path when only one visible tenant remains', function (): void {
$fixture = $this->makeBaselineCompareMatrixFixture();
$run = $this->makeBaselineCompareMatrixRun(
$fixture['visibleTenant'],
$fixture['profile'],
$fixture['snapshot'],
);
$this->makeBaselineCompareMatrixRun(
$fixture['visibleTenantTwo'],
$fixture['profile'],
$fixture['snapshot'],
);
$this->makeBaselineCompareMatrixFinding(
$fixture['visibleTenant'],
$fixture['profile'],
$run,
'wifi-corp-profile',
['severity' => Finding::SEVERITY_HIGH],
);
$viewer = User::factory()->create();
WorkspaceMembership::factory()->create([
'workspace_id' => (int) $fixture['workspace']->getKey(),
'user_id' => (int) $viewer->getKey(),
'role' => 'owner',
]);
$viewer->tenants()->syncWithoutDetaching([
(int) $fixture['visibleTenant']->getKey() => ['role' => 'owner'],
]);
$this->actingAs($viewer)->withSession([
WorkspaceContext::SESSION_KEY => (int) $fixture['workspace']->getKey(),
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
(string) $fixture['workspace']->getKey() => (int) $fixture['visibleTenant']->getKey(),
],
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $fixture['workspace']->getKey());
visit(BaselineProfileResource::compareMatrixUrl($fixture['profile']))
->assertNoJavaScriptErrors()
->waitForText('Requested: Auto mode. Resolved: Compact mode.')
->assertSee('Compact compare results')
->assertSee('Open finding');
});
it('smokes filtered zero-results reset flow and passive refresh cues without losing the matrix route', function (): void {
$fixture = $this->makeBaselineCompareMatrixFixture();
$this->makeBaselineCompareMatrixRun(
$fixture['visibleTenant'],
$fixture['profile'],
$fixture['snapshot'],
attributes: [
'status' => \App\Support\OperationRunStatus::Queued->value,
'outcome' => \App\Support\OperationRunOutcome::Pending->value,
'completed_at' => null,
'started_at' => now(),
],
);
$this->makeBaselineCompareMatrixRun(
$fixture['visibleTenantTwo'],
$fixture['profile'],
$fixture['snapshot'],
);
$this->actingAs($fixture['user'])->withSession([
WorkspaceContext::SESSION_KEY => (int) $fixture['workspace']->getKey(),
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
(string) $fixture['workspace']->getKey() => (int) $fixture['visibleTenant']->getKey(),
],
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $fixture['workspace']->getKey());
visit(BaselineProfileResource::compareMatrixUrl($fixture['profile']).'?mode=dense&state[]=missing')
->assertNoJavaScriptErrors()
->waitForText('No rows match the current filters')
->assertSee('Passive auto-refresh every 5 seconds')
->click('Reset filters')
->waitForText('Dense multi-tenant scan')
->assertSee('Requested: Dense mode. Resolved: Dense mode.')
->assertNoJavaScriptErrors();
});