## Summary - normalize provider-neutral target-scope and identity contracts across provider connection resolution, operation-start gating, verification reporting, and boundary configuration - align provider connection resource, onboarding, tenant summaries, and operation follow-up on the same shared scope contract while keeping Microsoft-specific profile details in provider-owned metadata - add Spec 281 artifacts and focused feature/browser coverage for the new provider-scope contract - move the tenant dashboard context-chip rail into Filament header widgets so the metadata row renders directly under the page subtitle ## Validation - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Providers/ProviderConnectionTargetScopeNeutralityTest.php tests/Feature/Providers/ProviderIdentityResolutionNeutralityTest.php tests/Feature/Providers/ProviderOperationStartGateTargetScopeContextTest.php tests/Feature/Filament/ProviderConnectionResourceScopeSummaryTest.php tests/Feature/Onboarding/ManagedTenantOnboardingProviderConnectionScopeTest.php tests/Feature/Guards/ProviderConnectionMicrosoftScopeLeakGuardTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec281ProviderConnectionScopeSmokeTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Dashboard/TenantDashboardProductizationSummaryTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Dashboard/TenantDashboardProductizationSmokeTest.php` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` ## Notes - Filament remains on v5 with Livewire v4-compatible surfaces only. - Provider registration location is unchanged; Laravel 11+ providers stay in `apps/platform/bootstrap/providers.php`. - `ProviderConnectionResource` remains non-globally-searchable and still exposes View/Edit pages. - No new asset registration was added; deploy-time `filament:assets` expectations are unchanged. - No new destructive action path was introduced; existing server authorization and confirmation handling remain in place where applicable. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #339
427 lines
20 KiB
PHP
427 lines
20 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\TenantDashboard;
|
|
use App\Models\Finding;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ProviderConnection;
|
|
use App\Services\Intune\TenantRequiredPermissionsViewModelBuilder;
|
|
use App\Support\OperationCatalog;
|
|
use App\Support\OperationRunLinks;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\TenantDashboard\TenantDashboardSummaryBuilder;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
use function Pest\Laravel\mock;
|
|
|
|
function mockTenantDashboardSummaryPermissions(array $overview = []): void
|
|
{
|
|
mock(TenantRequiredPermissionsViewModelBuilder::class, function ($mock) use ($overview): void {
|
|
$mock->shouldReceive('build')->andReturn([
|
|
'overview' => array_replace_recursive([
|
|
'overall' => 'ready',
|
|
'counts' => [
|
|
'missing_application' => 0,
|
|
'missing_delegated' => 0,
|
|
],
|
|
'freshness' => [
|
|
'is_stale' => false,
|
|
'last_refreshed_at' => now()->toIso8601String(),
|
|
],
|
|
], $overview),
|
|
]);
|
|
});
|
|
}
|
|
|
|
it('renders the decision-first tenant overview with the capped first-screen structure', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
mockTenantDashboardSummaryPermissions();
|
|
|
|
[$profile, $snapshot] = seedActiveBaselineForTenant($tenant);
|
|
|
|
seedBaselineCompareRun($tenant, $profile, $snapshot, workspaceOverviewCompareCoverage());
|
|
|
|
$backupSet = workspaceOverviewSeedHealthyBackup($tenant);
|
|
|
|
workspaceOverviewSeedRestoreHistory($tenant, $backupSet);
|
|
|
|
Finding::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'finding_type' => Finding::FINDING_TYPE_DRIFT,
|
|
'severity' => Finding::SEVERITY_HIGH,
|
|
'status' => Finding::STATUS_NEW,
|
|
]);
|
|
|
|
OperationRun::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'type' => 'inventory_sync',
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Failed->value,
|
|
'completed_at' => now()->subHour(),
|
|
]);
|
|
|
|
ProviderConnection::factory()->platform()->consentGranted()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'is_default' => true,
|
|
]);
|
|
|
|
$this->actingAs($user);
|
|
setTenantPanelContext($tenant);
|
|
|
|
$response = $this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
|
|
->assertSuccessful()
|
|
->assertSee($tenant->name)
|
|
->assertSee('Recommended next actions')
|
|
->assertSee('Governance status')
|
|
->assertSee('Operations needing attention')
|
|
->assertSee('Current review')
|
|
->assertSee('Risk exceptions')
|
|
->assertSee('Provider Health')
|
|
->assertSee('Customer-safe output')
|
|
->assertSee('Operations requiring attention')
|
|
->assertSee('Review operation')
|
|
->assertSee('Open operations hub')
|
|
->assertDontSee('Recent operations');
|
|
|
|
$content = $response->getContent();
|
|
$contextChipsPosition = strpos($content, 'data-testid="tenant-dashboard-context-chips"');
|
|
$firstKpiPosition = strpos($content, 'data-testid="tenant-dashboard-kpi"');
|
|
$governanceStatusCount = substr_count($content, 'data-testid="tenant-dashboard-governance-status"');
|
|
$secondaryListRowCount = substr_count($content, 'data-overview-row-style="secondary-list-row"');
|
|
|
|
expect(substr_count($content, 'data-testid="tenant-dashboard-kpi"'))->toBe(4)
|
|
->and($content)->toContain('data-testid="tenant-dashboard-posture-pill"')
|
|
->and($content)->toContain('data-testid="tenant-dashboard-context-chips"')
|
|
->and($content)->toContain('class="flex w-full flex-col items-start gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-start md:flex-nowrap"')
|
|
->and($content)->toContain('data-testid="tenant-dashboard-context-chip-workspace"')
|
|
->and($content)->toContain('data-testid="tenant-dashboard-context-chip-workspace" class="inline-flex min-w-0 w-full max-w-full items-center')
|
|
->and($content)->toContain('sm:w-auto sm:max-w-[20rem] lg:max-w-[24rem]')
|
|
->and($content)->toContain('Workspace: '.$tenant->workspace->name)
|
|
->and($content)->toContain('data-testid="tenant-dashboard-context-chip-provider"')
|
|
->and($content)->toContain('data-testid="tenant-dashboard-context-chip-provider-microsoft-logo"')
|
|
->and($content)->toContain('data-provider-key="microsoft"')
|
|
->and($content)->toContain('Microsoft tenant')
|
|
->and($content)->toContain('data-testid="tenant-dashboard-context-chip-latest-activity" class="inline-flex items-center gap-2 whitespace-nowrap')
|
|
->and($content)->toContain('data-testid="tenant-dashboard-context-chip-latest-activity-icon"')
|
|
->and($content)->toContain('Latest activity:')
|
|
->and($contextChipsPosition)->not->toBeFalse()
|
|
->and($firstKpiPosition)->not->toBeFalse()
|
|
->and($contextChipsPosition)->toBeLessThan($firstKpiPosition)
|
|
->and($secondaryListRowCount)->toBe($governanceStatusCount)
|
|
->and($content)->toContain('hover:shadow-md')
|
|
->and($content)->toContain('hover:ring-1')
|
|
->and(substr_count($content, 'data-kpi-has-icon="true"'))->toBe(4)
|
|
->and(substr_count($content, 'data-kpi-has-chart="true"'))->toBe(2)
|
|
->and(substr_count($content, 'data-testid="tenant-dashboard-recommended-action"'))->toBeLessThanOrEqual(3)
|
|
->and(substr_count($content, 'tenant-dashboard-recommended-actions'))->toBeGreaterThanOrEqual(1)
|
|
->and(substr_count($content, 'data-testid="tenant-dashboard-governance-status-icon"'))->toBe(substr_count($content, 'data-testid="tenant-dashboard-governance-status"'))
|
|
->and(substr_count($content, 'data-testid="tenant-dashboard-operations-attention-item-icon"'))->toBeGreaterThanOrEqual(1)
|
|
->and(substr_count($content, 'data-testid="tenant-dashboard-readiness-card"'))->toBe(4)
|
|
->and($content)->toContain('data-readiness-key="provider_health"')
|
|
->and($content)->not->toContain('Open customer workspace')
|
|
->and($content)->not->toContain('fixed bottom-4 right-4 z-[999999] w-96 space-y-2')
|
|
->and($content)->toContain('High severity findings')
|
|
->and($content)->not->toContain('section_recent_operations');
|
|
});
|
|
|
|
it('adds repo-real icon metadata and only supported sparkline series to tenant dashboard kpis', function (): void {
|
|
Carbon::setTestNow('2026-05-03 12:00:00');
|
|
|
|
try {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
mockTenantDashboardSummaryPermissions([
|
|
'counts' => [
|
|
'missing_application' => 2,
|
|
'missing_delegated' => 1,
|
|
],
|
|
]);
|
|
|
|
[$profile, $snapshot] = seedActiveBaselineForTenant($tenant);
|
|
|
|
seedBaselineCompareRun($tenant, $profile, $snapshot, workspaceOverviewCompareCoverage());
|
|
|
|
$backupSet = workspaceOverviewSeedHealthyBackup($tenant);
|
|
|
|
workspaceOverviewSeedRestoreHistory($tenant, $backupSet);
|
|
|
|
foreach ([6, 6, 4, 1] as $daysAgo) {
|
|
Finding::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'finding_type' => Finding::FINDING_TYPE_DRIFT,
|
|
'severity' => $daysAgo === 4 ? Finding::SEVERITY_CRITICAL : Finding::SEVERITY_HIGH,
|
|
'status' => Finding::STATUS_NEW,
|
|
'first_seen_at' => now()->subDays($daysAgo),
|
|
'last_seen_at' => now()->subDays($daysAgo),
|
|
]);
|
|
}
|
|
|
|
Finding::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'finding_type' => Finding::FINDING_TYPE_DRIFT,
|
|
'severity' => Finding::SEVERITY_MEDIUM,
|
|
'status' => Finding::STATUS_NEW,
|
|
'first_seen_at' => now()->subDays(2),
|
|
'last_seen_at' => now()->subDays(2),
|
|
]);
|
|
|
|
Finding::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'finding_type' => Finding::FINDING_TYPE_DRIFT,
|
|
'severity' => Finding::SEVERITY_MEDIUM,
|
|
'status' => Finding::STATUS_NEW,
|
|
'first_seen_at' => now()->subDays(2),
|
|
'last_seen_at' => now()->subDays(2),
|
|
'due_at' => now()->subDay(),
|
|
]);
|
|
|
|
foreach ([5, 2, 2] as $daysAgo) {
|
|
OperationRun::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'type' => 'inventory_sync',
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Failed->value,
|
|
'created_at' => now()->subDays($daysAgo)->subHours(3),
|
|
'completed_at' => now()->subDays($daysAgo),
|
|
]);
|
|
}
|
|
|
|
$kpis = collect(app(TenantDashboardSummaryBuilder::class)
|
|
->build($tenant, $user)
|
|
->toArray()['kpis'])
|
|
->keyBy('key');
|
|
|
|
expect($kpis->keys()->all())->toBe([
|
|
'high_severity_findings',
|
|
'overdue_findings',
|
|
'missing_permissions',
|
|
'active_operations',
|
|
])
|
|
->and($kpis['active_operations']['label'])->toBe('Operations needing attention')
|
|
->and($kpis->pluck('icon')->filter()->count())->toBe(4)
|
|
->and($kpis['high_severity_findings']['icon'])->toBe('heroicon-m-arrow-trending-up')
|
|
->and($kpis['high_severity_findings']['description'])->toBe('4 active · 4 new in 7d')
|
|
->and($kpis['high_severity_findings']['chart'])->toBe([2, 0, 1, 0, 0, 1, 0])
|
|
->and($kpis['overdue_findings']['icon'])->toBe('heroicon-m-arrow-trending-up')
|
|
->and($kpis['overdue_findings']['description'])->toBe('1 overdue now')
|
|
->and($kpis['missing_permissions']['icon'])->toBe('heroicon-m-arrow-trending-up')
|
|
->and($kpis['missing_permissions']['description'])->toBe('2 app · 1 delegated missing')
|
|
->and($kpis['active_operations']['icon'])->toBe('heroicon-m-arrow-trending-up')
|
|
->and($kpis['active_operations']['description'])->toBe('3 operations require attention')
|
|
->and($kpis['active_operations']['chart'])->toBe([0, 1, 0, 0, 2, 0, 0])
|
|
->and($kpis['overdue_findings']['chart'])->toBeNull()
|
|
->and($kpis['missing_permissions']['chart'])->toBeNull();
|
|
} finally {
|
|
Carbon::setTestNow();
|
|
}
|
|
});
|
|
|
|
it('adds semantic icon metadata to governance status rows and curated operations attention items', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
mockTenantDashboardSummaryPermissions();
|
|
|
|
OperationRun::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'type' => 'inventory_sync',
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Failed->value,
|
|
'created_at' => now()->subMinutes(3),
|
|
'completed_at' => now()->subMinutes(3),
|
|
]);
|
|
|
|
OperationRun::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'type' => 'tenant.review_pack.generate',
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Failed->value,
|
|
'created_at' => now()->subMinutes(2),
|
|
'completed_at' => now()->subMinutes(2),
|
|
]);
|
|
|
|
OperationRun::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'type' => OperationCatalog::TYPE_PERMISSION_POSTURE_CHECK,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Blocked->value,
|
|
'created_at' => now()->subMinute(),
|
|
'completed_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$summary = app(TenantDashboardSummaryBuilder::class)
|
|
->build($tenant, $user)
|
|
->toArray();
|
|
|
|
$governanceStatus = collect($summary['governanceStatus'])->keyBy('key');
|
|
$attentionOperations = collect($summary['activeOperationSummary']['items'] ?? [])->keyBy('type');
|
|
|
|
expect($governanceStatus['baseline_compare']['icon'] ?? null)->toBe('heroicon-m-arrows-right-left')
|
|
->and($governanceStatus['evidence_coverage']['icon'] ?? null)->toBe('heroicon-m-document-check')
|
|
->and($governanceStatus['review_freshness']['icon'] ?? null)->toBe('heroicon-m-clipboard-document-check')
|
|
->and($governanceStatus['provider_permissions']['icon'] ?? null)->toBe('heroicon-m-key')
|
|
->and($governanceStatus['backup_posture']['icon'] ?? null)->toBe('heroicon-m-archive-box')
|
|
->and($attentionOperations['Inventory sync']['icon'] ?? null)->toBe('heroicon-m-arrow-path')
|
|
->and($attentionOperations['Review pack generation']['icon'] ?? null)->toBe('heroicon-m-document-arrow-down')
|
|
->and($attentionOperations['Permission posture check']['icon'] ?? null)->toBe('heroicon-m-key');
|
|
});
|
|
|
|
it('shows calm honest fallbacks when no urgent tenant follow-up is visible', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
mockTenantDashboardSummaryPermissions();
|
|
|
|
[$profile, $snapshot] = seedActiveBaselineForTenant($tenant);
|
|
|
|
seedBaselineCompareRun($tenant, $profile, $snapshot, workspaceOverviewCompareCoverage());
|
|
|
|
$backupSet = workspaceOverviewSeedHealthyBackup($tenant);
|
|
|
|
workspaceOverviewSeedRestoreHistory($tenant, $backupSet);
|
|
|
|
$this->actingAs($user);
|
|
setTenantPanelContext($tenant);
|
|
|
|
$response = $this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
|
|
->assertSuccessful()
|
|
->assertSee('No immediate action is waiting.')
|
|
->assertDontSee('Recent operations')
|
|
->assertDontSee('Operations requiring attention');
|
|
|
|
$content = $response->getContent();
|
|
|
|
expect(substr_count($content, 'data-testid="tenant-dashboard-recommended-actions-empty"'))->toBe(1)
|
|
->and($content)->not->toContain('data-testid="tenant-dashboard-operations-attention-summary"')
|
|
->and($content)->not->toContain('data-testid="tenant-dashboard-recent-operations-empty"');
|
|
});
|
|
|
|
it('builds a curated operations requiring attention summary and excludes healthy active runs', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
mockTenantDashboardSummaryPermissions();
|
|
|
|
$healthyRunningRun = OperationRun::factory()->create([
|
|
'managed_environment_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(),
|
|
]);
|
|
|
|
$followUpRun = OperationRun::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'type' => 'inventory_sync',
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Failed->value,
|
|
'created_at' => now()->subMinutes(6),
|
|
'started_at' => now()->subMinutes(5),
|
|
'completed_at' => now()->subMinutes(4),
|
|
]);
|
|
|
|
$blockedRun = OperationRun::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'type' => OperationCatalog::TYPE_PERMISSION_POSTURE_CHECK,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Blocked->value,
|
|
'created_at' => now()->subMinutes(5),
|
|
'started_at' => now()->subMinutes(4),
|
|
'completed_at' => now()->subMinutes(3),
|
|
]);
|
|
|
|
$summary = app(TenantDashboardSummaryBuilder::class)
|
|
->build($tenant, $user)
|
|
->toArray();
|
|
|
|
$activeOperationSummary = $summary['activeOperationSummary'] ?? null;
|
|
$items = collect($activeOperationSummary['items'] ?? []);
|
|
|
|
expect($activeOperationSummary)
|
|
->not->toBeNull()
|
|
->and($activeOperationSummary['title'] ?? null)->toBe('Operations requiring attention')
|
|
->and($activeOperationSummary['count'] ?? null)->toBe(2)
|
|
->and($activeOperationSummary['secondaryActionLabel'] ?? null)->toBe('Open operations hub')
|
|
->and($activeOperationSummary['secondaryActionUrl'] ?? null)->toBe(OperationRunLinks::index(
|
|
$tenant,
|
|
activeTab: OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP,
|
|
problemClass: OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP,
|
|
))
|
|
->and($items)->toHaveCount(2)
|
|
->and($items->pluck('id')->all())->toBe([
|
|
(int) $blockedRun->getKey(),
|
|
(int) $followUpRun->getKey(),
|
|
])
|
|
->and($items->pluck('primaryActionLabel')->unique()->all())->toBe(['Review operation'])
|
|
->and($items->pluck('primaryActionUrl')->all())->toBe([
|
|
OperationRunLinks::view($blockedRun, $tenant),
|
|
OperationRunLinks::view($followUpRun, $tenant),
|
|
])
|
|
->and($items->pluck('attentionLabel')->unique()->all())->toBe(['Follow-up required'])
|
|
->and($items->pluck('timingLabel')->filter()->isNotEmpty())->toBeTrue()
|
|
->and($items->pluck('outcomeSentence')->filter()->isNotEmpty())->toBeTrue()
|
|
->and($items->pluck('reason')->filter()->isNotEmpty())->toBeTrue()
|
|
->and($items->pluck('impact')->filter()->isNotEmpty())->toBeTrue()
|
|
->and($items->pluck('id')->contains((int) $healthyRunningRun->getKey()))->toBeFalse();
|
|
|
|
$this->actingAs($user);
|
|
setTenantPanelContext($tenant);
|
|
|
|
$this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
|
|
->assertSuccessful()
|
|
->assertSee('data-testid="tenant-dashboard-operations-attention-summary"', false)
|
|
->assertSee('Review operation')
|
|
->assertSee('Open operations hub')
|
|
->assertSee('Completed '.$followUpRun->completed_at?->diffForHumans())
|
|
->assertSee('Inventory sync')
|
|
->assertDontSee('Operation #'.$followUpRun->getKey())
|
|
->assertDontSee('Recent operations');
|
|
});
|
|
|
|
it('omits the compact active operations summary when no qualifying visible run exists', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
mockTenantDashboardSummaryPermissions();
|
|
|
|
OperationRun::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'type' => 'inventory_sync',
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Succeeded->value,
|
|
'created_at' => now()->subMinutes(3),
|
|
'started_at' => now()->subMinutes(2),
|
|
'completed_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$summary = app(TenantDashboardSummaryBuilder::class)
|
|
->build($tenant, $user)
|
|
->toArray();
|
|
|
|
expect($summary['activeOperationSummary'] ?? null)->toBeNull();
|
|
|
|
$this->actingAs($user);
|
|
setTenantPanelContext($tenant);
|
|
|
|
$this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
|
|
->assertSuccessful()
|
|
->assertDontSee('data-testid="tenant-dashboard-operations-attention-summary"', false)
|
|
->assertDontSee('Review operation')
|
|
->assertDontSee('Open operations hub')
|
|
->assertDontSee('Recent operations');
|
|
});
|