156 lines
6.3 KiB
PHP
156 lines
6.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\CrossTenantComparePage;
|
|
use App\Jobs\Operations\CrossTenantPromotionExecutionJob;
|
|
use App\Models\AuditLog;
|
|
use App\Models\BackupSet;
|
|
use App\Models\OperationRun;
|
|
use App\Models\PolicyVersion;
|
|
use App\Models\RestoreRun;
|
|
use App\Services\Audit\WorkspaceAuditLogger;
|
|
use App\Services\Intune\RestoreService;
|
|
use App\Services\OperationRunService;
|
|
use App\Services\Operations\TargetScopeConcurrencyLimiter;
|
|
use App\Support\Audit\AuditActionId;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Livewire\Livewire;
|
|
use Tests\Feature\Concerns\BuildsPortfolioCompareFixtures;
|
|
|
|
uses(RefreshDatabase::class, BuildsPortfolioCompareFixtures::class);
|
|
|
|
it('audits queued promotion execution without creating restore-side writes before the worker starts', function (): void {
|
|
Queue::fake();
|
|
|
|
$fixture = $this->makeCrossTenantCompareFixture();
|
|
|
|
$this->createPortfolioCompareSubject(
|
|
tenant: $fixture['sourceTenant'],
|
|
displayName: 'Audit Queue Policy',
|
|
snapshot: ['settings' => [['key' => 'audit-queue', 'value' => 1]]],
|
|
);
|
|
|
|
$this->setAdminWorkspaceContext($fixture['user'], $fixture['workspace']);
|
|
|
|
$policyVersionCount = PolicyVersion::query()->count();
|
|
|
|
Livewire::withQueryParams([
|
|
'source_tenant_id' => (int) $fixture['sourceTenant']->getKey(),
|
|
'target_tenant_id' => (int) $fixture['targetTenant']->getKey(),
|
|
'policy_type' => ['deviceConfiguration'],
|
|
])
|
|
->actingAs($fixture['user'])
|
|
->test(CrossTenantComparePage::class)
|
|
->call('generatePromotionPreflight')
|
|
->mountAction('executePromotion')
|
|
->callMountedAction()
|
|
->assertHasNoActionErrors();
|
|
|
|
$run = OperationRun::query()->latest('id')->first();
|
|
|
|
$audit = AuditLog::query()
|
|
->where('workspace_id', (int) $fixture['workspace']->getKey())
|
|
->where('action', AuditActionId::CrossTenantPromotionExecutionQueued->value)
|
|
->latest('id')
|
|
->first();
|
|
|
|
expect($run)->not->toBeNull()
|
|
->and($audit)->not->toBeNull()
|
|
->and($audit?->status)->toBe('info')
|
|
->and($audit?->resource_type)->toBe('operation_run')
|
|
->and((int) ($audit?->operation_run_id ?? 0))->toBe((int) $run?->getKey())
|
|
->and(data_get($audit?->metadata, 'source_tenant_id'))->toBe((int) $fixture['sourceTenant']->getKey())
|
|
->and(data_get($audit?->metadata, 'target_tenant_id'))->toBe((int) $fixture['targetTenant']->getKey())
|
|
->and(data_get($audit?->metadata, 'ready_count'))->toBe(1)
|
|
->and(data_get($audit?->metadata, 'excluded_count'))->toBe(0)
|
|
->and(BackupSet::query()->count())->toBe(0)
|
|
->and(RestoreRun::query()->count())->toBe(0)
|
|
->and(PolicyVersion::query()->count())->toBe($policyVersionCount);
|
|
});
|
|
|
|
it('audits terminal promotion execution truth after the queued worker completes', function (): void {
|
|
Queue::fake();
|
|
|
|
$fixture = $this->makeCrossTenantCompareFixture();
|
|
|
|
$this->createPortfolioCompareSubject(
|
|
tenant: $fixture['sourceTenant'],
|
|
displayName: 'Audit Completion Policy',
|
|
snapshot: ['settings' => [['key' => 'audit-complete', 'value' => 1]]],
|
|
);
|
|
|
|
$this->setAdminWorkspaceContext($fixture['user'], $fixture['workspace']);
|
|
|
|
Livewire::withQueryParams([
|
|
'source_tenant_id' => (int) $fixture['sourceTenant']->getKey(),
|
|
'target_tenant_id' => (int) $fixture['targetTenant']->getKey(),
|
|
'policy_type' => ['deviceConfiguration'],
|
|
])
|
|
->actingAs($fixture['user'])
|
|
->test(CrossTenantComparePage::class)
|
|
->call('generatePromotionPreflight')
|
|
->mountAction('executePromotion')
|
|
->callMountedAction()
|
|
->assertHasNoActionErrors();
|
|
|
|
$run = OperationRun::query()->latest('id')->firstOrFail();
|
|
|
|
$restoreService = \Mockery::mock(RestoreService::class);
|
|
$restoreService->shouldReceive('execute')
|
|
->once()
|
|
->andReturnUsing(function ($tenant, $backupSet, array $selectedItemIds) {
|
|
return RestoreRun::factory()->create([
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'backup_set_id' => (int) $backupSet->getKey(),
|
|
'requested_items' => $selectedItemIds,
|
|
'results' => [
|
|
'items' => [[
|
|
'status' => 'applied',
|
|
'policy_identifier' => 'audit-complete-policy',
|
|
]],
|
|
],
|
|
'metadata' => [
|
|
'succeeded' => count($selectedItemIds),
|
|
'failed' => 0,
|
|
'partial' => 0,
|
|
'skipped' => 0,
|
|
],
|
|
]);
|
|
});
|
|
|
|
app()->instance(RestoreService::class, $restoreService);
|
|
|
|
$job = new CrossTenantPromotionExecutionJob($run);
|
|
$job->handle(
|
|
app(OperationRunService::class),
|
|
$restoreService,
|
|
app(TargetScopeConcurrencyLimiter::class),
|
|
app(WorkspaceAuditLogger::class),
|
|
);
|
|
|
|
$completedAudit = AuditLog::query()
|
|
->where('workspace_id', (int) $fixture['workspace']->getKey())
|
|
->where('action', AuditActionId::CrossTenantPromotionExecutionCompleted->value)
|
|
->latest('id')
|
|
->first();
|
|
|
|
$completedRun = $run->fresh();
|
|
$restoreRun = RestoreRun::query()->latest('id')->first();
|
|
|
|
expect($completedRun)->not->toBeNull()
|
|
->and($completedRun?->status)->toBe('completed')
|
|
->and($completedRun?->outcome)->toBe('succeeded')
|
|
->and($completedAudit)->not->toBeNull()
|
|
->and($completedAudit?->status)->toBe('success')
|
|
->and($completedAudit?->resource_type)->toBe('operation_run')
|
|
->and((int) ($completedAudit?->operation_run_id ?? 0))->toBe((int) $completedRun?->getKey())
|
|
->and(data_get($completedAudit?->metadata, 'source_tenant_id'))->toBe((int) $fixture['sourceTenant']->getKey())
|
|
->and(data_get($completedAudit?->metadata, 'target_tenant_id'))->toBe((int) $fixture['targetTenant']->getKey())
|
|
->and(data_get($completedAudit?->metadata, 'summary_counts.created'))->toBe(1)
|
|
->and(data_get($completedAudit?->metadata, 'summary_counts.succeeded'))->toBe(1)
|
|
->and(data_get($completedAudit?->metadata, 'restore_run_id'))->toBe((int) $restoreRun?->getKey())
|
|
->and(BackupSet::query()->count())->toBe(1)
|
|
->and(RestoreRun::query()->count())->toBe(1);
|
|
}); |