TenantAtlas/apps/platform/tests/Feature/PortfolioCompare/CrossEnvironmentCompareLaunchContextTest.php
Ahmed Darrazi 8cd125e398
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m45s
feat: implement environment CTA explicit filter contract
2026-05-16 13:49:05 +02:00

268 lines
14 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\CrossEnvironmentComparePage;
use App\Filament\Resources\ManagedEnvironmentResource;
use App\Jobs\Operations\CrossEnvironmentPromotionExecutionJob;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Services\Auth\CapabilityResolver;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Support\Auth\Capabilities;
use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\RestoreSafety\RestoreResultAttention;
use App\Support\Tenants\TenantRecoveryTriagePresentation;
use Filament\Actions\Action;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;
use Tests\Feature\Concerns\BuildsPortfolioCompareFixtures;
use Tests\Feature\Concerns\BuildsPortfolioTriageFixtures;
uses(RefreshDatabase::class, BuildsPortfolioTriageFixtures::class, BuildsPortfolioCompareFixtures::class);
function crossEnvironmentCompareLaunchQuery(string $url): array
{
parse_str((string) parse_url($url, PHP_URL_QUERY), $query);
return $query;
}
it('launches cross-environment compare from the tenant registry with target prefill and return context', function (): void {
[$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
$targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');
$backupSet = $this->seedPortfolioBackupConcern($targetEnvironment, TenantBackupHealthAssessment::POSTURE_STALE);
$this->seedPortfolioRecoveryConcern($targetEnvironment, RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP, $backupSet);
$triageState = $this->portfolioReturnFilters(
[TenantBackupHealthAssessment::POSTURE_STALE],
[TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED],
[],
TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST,
);
$expectedUrl = ManagedEnvironmentResource::crossEnvironmentCompareOpenUrl($targetEnvironment, $triageState);
$this->portfolioTriageRegistryList($user, $anchorTenant, $triageState)
->assertTableActionVisible('compareEnvironments', $targetEnvironment)
->assertTableActionHasUrl('compareEnvironments', $expectedUrl, $targetEnvironment);
$query = crossEnvironmentCompareLaunchQuery($expectedUrl);
$backUrl = urldecode((string) data_get($query, 'nav.back_url'));
expect($query)->toMatchArray([
'target_environment_id' => (string) $targetEnvironment->getKey(),
])
->and(data_get($query, 'nav.source_surface'))->toBe('tenant_registry')
->and(data_get($query, 'nav.environment_id'))->toBe((string) $targetEnvironment->getKey())
->and(data_get($query, 'nav.back_label'))->toBe(__('localization.shell.back_to_environment_registry'))
->and($backUrl)->toContain('backup_posture[0]='.TenantBackupHealthAssessment::POSTURE_STALE)
->and($backUrl)->toContain('recovery_evidence[0]='.TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED)
->and($backUrl)->toContain('triage_sort='.TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST);
Livewire::withQueryParams($query)
->actingAs($user)
->test(CrossEnvironmentComparePage::class)
->assertSet('sourceEnvironmentId', null)
->assertSet('targetEnvironmentId', (string) $targetEnvironment->getKey())
->assertActionVisible('return_to_origin')
->assertActionExists('return_to_origin', fn (Action $action): bool => $action->getLabel() === __('localization.shell.back_to_environment_registry')
&& $action->getUrl() === ManagedEnvironmentResource::getUrl(panel: 'admin', parameters: $triageState));
});
it('launches cross-environment compare from an exact-two bulk selection with both tenants prefilled', function (): void {
[$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
$targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');
$anchorBackupSet = $this->seedPortfolioBackupConcern($anchorTenant, TenantBackupHealthAssessment::POSTURE_STALE);
$this->seedPortfolioRecoveryConcern($anchorTenant, RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP, $anchorBackupSet);
$backupSet = $this->seedPortfolioBackupConcern($targetEnvironment, TenantBackupHealthAssessment::POSTURE_STALE);
$this->seedPortfolioRecoveryConcern($targetEnvironment, RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP, $backupSet);
$triageState = $this->portfolioReturnFilters(
[TenantBackupHealthAssessment::POSTURE_STALE],
[TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED],
[],
TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST,
);
$expectedUrl = ManagedEnvironmentResource::crossEnvironmentCompareOpenUrlForSelection(
targetEnvironment: $targetEnvironment,
triageState: $triageState,
sourceEnvironment: $anchorTenant,
);
$this->portfolioTriageRegistryList($user, $anchorTenant, $triageState)
->selectTableRecords([$anchorTenant, $targetEnvironment])
->assertTableBulkActionVisible('compareSelected')
->callTableBulkAction('compareSelected', [$anchorTenant, $targetEnvironment])
->assertRedirect($expectedUrl);
$query = crossEnvironmentCompareLaunchQuery($expectedUrl);
$backUrl = urldecode((string) data_get($query, 'nav.back_url'));
expect($query)->toMatchArray([
'source_environment_id' => (string) $anchorTenant->getKey(),
'target_environment_id' => (string) $targetEnvironment->getKey(),
])
->and(data_get($query, 'nav.source_surface'))->toBe('tenant_registry')
->and(data_get($query, 'nav.back_label'))->toBe(__('localization.shell.back_to_environment_registry'))
->and(data_get($query, 'nav.managed_environment_id'))->toBeNull()
->and($backUrl)->toContain('backup_posture[0]='.TenantBackupHealthAssessment::POSTURE_STALE)
->and($backUrl)->toContain('recovery_evidence[0]='.TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED)
->and($backUrl)->toContain('triage_sort='.TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST);
Livewire::withQueryParams($query)
->actingAs($user)
->test(CrossEnvironmentComparePage::class)
->assertSet('sourceEnvironmentId', (string) $anchorTenant->getKey())
->assertSet('targetEnvironmentId', (string) $targetEnvironment->getKey())
->assertActionVisible('return_to_origin');
});
it('keeps launch context after queueing promotion from an exact-two registry launch', function (): void {
Queue::fake();
[$user, $anchorTenant] = $this->makePortfolioTriageActor(
tenantName: 'Anchor ManagedEnvironment',
workspaceRole: 'owner',
);
$targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');
createMinimalUserWithTenant(
tenant: $targetEnvironment,
user: $user,
role: 'owner',
workspaceRole: 'owner',
);
$this->createPortfolioCompareSubject(
tenant: $anchorTenant,
displayName: 'Queued Launch Context Policy',
snapshot: ['settings' => [['key' => 'launch-context', 'value' => 1]]],
);
$triageState = $this->portfolioReturnFilters(
[TenantBackupHealthAssessment::POSTURE_STALE],
[TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED],
[],
TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST,
);
$expectedUrl = ManagedEnvironmentResource::crossEnvironmentCompareOpenUrlForSelection(
targetEnvironment: $targetEnvironment,
triageState: $triageState,
sourceEnvironment: $anchorTenant,
);
$expectedBackUrl = ManagedEnvironmentResource::getUrl(panel: 'admin', parameters: $triageState);
$query = crossEnvironmentCompareLaunchQuery($expectedUrl);
$query['policy_type'] = ['deviceConfiguration'];
$this->usePortfolioTriageWorkspace($user, $anchorTenant);
$component = Livewire::withQueryParams($query)
->actingAs($user)
->test(CrossEnvironmentComparePage::class)
->assertSet('sourceEnvironmentId', (string) $anchorTenant->getKey())
->assertSet('targetEnvironmentId', (string) $targetEnvironment->getKey())
->assertSet('selectedPolicyTypes', ['deviceConfiguration'])
->assertActionVisible('return_to_origin')
->assertActionExists('return_to_origin', fn (Action $action): bool => $action->getLabel() === __('localization.shell.back_to_environment_registry')
&& $action->getUrl() === $expectedBackUrl);
$page = $component->instance();
$page->generatePromotionPreflight();
$page->executePromotion();
$run = OperationRun::query()->latest('id')->first();
$navigationContext = CanonicalNavigationContext::fromPayload($page->navigationContextPayload);
expect($run)
->not->toBeNull()
->and($run?->type)->toBe('promotion.execute')
->and(data_get($run?->context, 'selection.sourceEnvironmentId'))->toBe((int) $anchorTenant->getKey())
->and(data_get($run?->context, 'selection.targetEnvironmentId'))->toBe((int) $targetEnvironment->getKey())
->and(data_get($run?->context, 'selection.policyTypes'))->toBe(['deviceConfiguration'])
->and($page->sourceEnvironmentId)->toBe((string) $anchorTenant->getKey())
->and($page->targetEnvironmentId)->toBe((string) $targetEnvironment->getKey())
->and($page->selectedPolicyTypes)->toBe(['deviceConfiguration'])
->and($page->navigationContextPayload)->toBe($query['nav'])
->and($navigationContext?->backLinkLabel)->toBe(__('localization.shell.back_to_environment_registry'))
->and($navigationContext?->backLinkUrl)->toBe($expectedBackUrl);
Queue::assertPushed(CrossEnvironmentPromotionExecutionJob::class, function (CrossEnvironmentPromotionExecutionJob $job) use ($run): bool {
return $job->getOperationRun()?->is($run);
});
});
it('rejects the bulk compare action until exactly two active environments are selected', function (): void {
[$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
$targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');
$thirdTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Third ManagedEnvironment');
$this->portfolioTriageRegistryList($user, $anchorTenant)
->selectTableRecords([$anchorTenant])
->assertTableBulkActionVisible('compareSelected')
->callTableBulkAction('compareSelected', [$anchorTenant])
->assertNotified('Select exactly two environments to compare.');
$this->portfolioTriageRegistryList($user, $anchorTenant)
->selectTableRecords([$anchorTenant, $targetEnvironment, $thirdTenant])
->assertTableBulkActionVisible('compareSelected')
->callTableBulkAction('compareSelected', [$anchorTenant, $targetEnvironment, $thirdTenant])
->assertNotified('Select exactly two environments to compare.');
});
it('rejects the bulk compare action when a selected environment is not active', function (): void {
[$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
$onboardingTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Onboarding ManagedEnvironment');
$onboardingTenant->forceFill([
'status' => ManagedEnvironment::STATUS_ONBOARDING,
])->save();
$this->portfolioTriageRegistryList($user, $anchorTenant)
->selectTableRecords([$anchorTenant, $onboardingTenant])
->assertTableBulkActionVisible('compareSelected')
->callTableBulkAction('compareSelected', [$anchorTenant, $onboardingTenant])
->assertNotified('Only active environments can be compared.');
});
it('hides the compare launch action when workspace baseline view capability is missing', function (): void {
[$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
$targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');
$resolver = \Mockery::mock(WorkspaceCapabilityResolver::class);
$resolver->shouldReceive('isMember')->andReturnTrue();
$resolver->shouldReceive('can')->andReturnFalse();
app()->instance(WorkspaceCapabilityResolver::class, $resolver);
$this->portfolioTriageRegistryList($user, $anchorTenant)
->assertTableActionHidden('compareEnvironments', $targetEnvironment);
});
it('hides the compare launch action when the actor lacks tenant view on the launched tenant', function (): void {
[$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
$targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');
$resolver = \Mockery::mock(CapabilityResolver::class);
$resolver->shouldReceive('primeMemberships')->andReturnNull();
$resolver->shouldReceive('isMember')->andReturnTrue();
$resolver->shouldReceive('can')->andReturnUsing(function (mixed $actor, mixed $tenant, string $capability) use ($targetEnvironment): bool {
if ($tenant instanceof ManagedEnvironment
&& (int) $tenant->getKey() === (int) $targetEnvironment->getKey()
&& $capability === Capabilities::TENANT_VIEW) {
return false;
}
return true;
});
app()->instance(CapabilityResolver::class, $resolver);
$this->portfolioTriageRegistryList($user, $anchorTenant)
->assertTableActionHidden('compareEnvironments', $targetEnvironment);
});