TenantAtlas/apps/platform/tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php
ahmido eca19819d1 feat: add workspace baseline compare matrix (#221)
## Summary
- add a workspace-scoped baseline compare matrix page under baseline profiles
- derive matrix tenant summaries, subject rows, cell states, freshness, and trust from existing snapshots, compare runs, and findings
- add confirmation-gated `Compare assigned tenants` actions on the baseline detail and matrix surfaces without introducing a workspace umbrella run
- preserve matrix navigation context into tenant compare and finding drilldowns and add centralized matrix badge semantics
- include spec, plan, data model, contracts, quickstart, tasks, and focused feature/browser coverage for Spec 190

## Verification
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Badges/BaselineCompareMatrixBadgesTest.php tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php tests/Feature/Baselines/BaselineComparePerformanceGuardTest.php tests/Feature/Filament/BaselineCompareMatrixPageTest.php tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php tests/Feature/Rbac/BaselineCompareMatrixAuthorizationTest.php tests/Feature/Guards/ActionSurfaceContractTest.php tests/Feature/Guards/NoAdHocStatusBadgesTest.php tests/Feature/Guards/NoDiagnosticWarningBadgesTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- completed an integrated-browser smoke flow locally for matrix render, differ filter, finding drilldown round-trip, and `Compare assigned tenants` confirmation/action

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #221
2026-04-11 10:20:25 +00:00

198 lines
7.7 KiB
PHP

<?php
use App\Filament\Resources\BaselineProfileResource;
use App\Filament\Resources\BaselineProfileResource\Pages\ViewBaselineProfile;
use App\Jobs\CompareBaselineToTenantJob;
use App\Models\BaselineProfile;
use App\Models\BaselineSnapshot;
use App\Models\BaselineTenantAssignment;
use App\Models\OperationRun;
use App\Support\Baselines\BaselineCaptureMode;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Actions\Action;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;
it('does not start baseline compare for workspace members missing tenant.sync', function (): void {
Queue::fake();
config()->set('tenantpilot.baselines.full_content_capture.enabled', true);
[$user, $tenant] = createUserWithTenant(role: 'readonly');
$profile = BaselineProfile::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
'capture_mode' => BaselineCaptureMode::FullContent->value,
]);
$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(),
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
Livewire::actingAs($user)
->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
->assertActionVisible('compareNow')
->assertActionHasLabel('compareNow', 'Compare now (full content)')
->assertActionDisabled('compareNow')
->callAction('compareNow', data: ['target_tenant_id' => (int) $tenant->getKey()])
->assertStatus(200);
Queue::assertNotPushed(CompareBaselineToTenantJob::class);
});
it('starts baseline compare successfully for authorized workspace members', function (): void {
Queue::fake();
config()->set('tenantpilot.baselines.full_content_capture.enabled', true);
[$user, $tenant] = createUserWithTenant(role: 'owner');
$profile = BaselineProfile::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
'capture_mode' => BaselineCaptureMode::FullContent->value,
]);
$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(),
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
Livewire::actingAs($user)
->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
->assertActionVisible('compareNow')
->assertActionHasLabel('compareNow', 'Compare now (full content)')
->assertActionEnabled('compareNow')
->callAction('compareNow', data: ['target_tenant_id' => (int) $tenant->getKey()])
->assertStatus(200);
Queue::assertPushed(CompareBaselineToTenantJob::class);
$run = OperationRun::query()
->where('tenant_id', (int) $tenant->getKey())
->where('type', 'baseline_compare')
->latest('id')
->first();
expect($run)->not->toBeNull();
expect($run?->status)->toBe('queued');
});
it('does not start full-content baseline compare when rollout is disabled', function (): void {
Queue::fake();
config()->set('tenantpilot.baselines.full_content_capture.enabled', false);
[$user, $tenant] = createUserWithTenant(role: 'owner');
$profile = BaselineProfile::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
'capture_mode' => BaselineCaptureMode::FullContent->value,
]);
$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(),
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
Livewire::actingAs($user)
->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
->assertActionVisible('compareNow')
->assertActionHasLabel('compareNow', 'Compare now (full content)')
->assertActionEnabled('compareNow')
->callAction('compareNow', data: ['target_tenant_id' => (int) $tenant->getKey()])
->assertNotified('Cannot start comparison')
->assertStatus(200);
Queue::assertNotPushed(CompareBaselineToTenantJob::class);
expect(OperationRun::query()->where('type', 'baseline_compare')->count())->toBe(0);
});
it('shows open-compare-matrix and compare-assigned-tenants header actions with simulation-only copy', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$this->actingAs($user);
$profile = BaselineProfile::factory()->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(),
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
Livewire::actingAs($user)
->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
->assertActionExists('openCompareMatrix', fn (Action $action): bool => $action->getLabel() === 'Open compare matrix'
&& $action->getUrl() === BaselineProfileResource::compareMatrixUrl($profile))
->assertActionExists('compareAssignedTenants', fn (Action $action): bool => $action->getLabel() === 'Compare assigned tenants'
&& $action->isConfirmationRequired()
&& str_contains((string) $action->getModalDescription(), 'Simulation only.'));
});
it('keeps compare-assigned-tenants visible but disabled for readonly workspace members', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'readonly');
$profile = BaselineProfile::factory()->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(),
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
Livewire::actingAs($user)
->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
->assertActionVisible('openCompareMatrix')
->assertActionVisible('compareAssignedTenants')
->assertActionDisabled('compareAssignedTenants');
});