set('tenantpilot.hardening.intune_write_gate.enabled', true); config()->set('tenantpilot.hardening.intune_write_gate.freshness_threshold_hours', 24); }); test('restore assignments job marks run failed when rbac is stale', function () { $tenant = Tenant::factory()->create([ 'rbac_status' => 'ok', 'rbac_last_checked_at' => now()->subHours(48), ]); $backupSet = BackupSet::create([ 'tenant_id' => $tenant->id, 'name' => 'Backup', 'status' => 'completed', 'item_count' => 0, ]); $restoreRun = RestoreRun::create([ 'tenant_id' => $tenant->id, 'backup_set_id' => $backupSet->id, 'requested_by' => 'actor@example.com', 'is_dry_run' => false, 'status' => RestoreRunStatus::Running->value, 'requested_items' => null, 'preview' => [], 'results' => null, 'metadata' => [], ]); $this->mock(AssignmentRestoreService::class, function (MockInterface $mock) { $mock->shouldNotReceive('restore'); }); $operationRun = app(OperationRunService::class)->ensureRun( tenant: $tenant, type: 'assignments.restore', inputs: [ 'restore_run_id' => $restoreRun->id, 'policy_type' => 'deviceConfiguration', 'policy_id' => 'test-policy-id', ], ); $assignments = [ ['target' => ['@odata.type' => '#microsoft.graph.allLicensedUsersAssignmentTarget']], ]; $job = new RestoreAssignmentsJob( restoreRunId: $restoreRun->id, tenantId: (int) $tenant->getKey(), policyType: 'deviceConfiguration', policyId: 'test-policy-id', assignments: $assignments, groupMapping: [], foundationMapping: [], actorEmail: 'actor@example.com', actorName: 'Actor', operationRun: $operationRun, ); $job->handle( app(AssignmentRestoreService::class), app(OperationRunService::class), ); $operationRun->refresh(); expect($operationRun->outcome)->toBe(OperationRunOutcome::Failed->value) ->and($operationRun->status)->toBe(OperationRunStatus::Completed->value); $failures = is_array($operationRun->failure_summary) ? $operationRun->failure_summary : []; $reasonCodes = array_column($failures, 'reason_code'); expect($reasonCodes)->toContain('intune_rbac.stale'); }); test('restore assignments job marks run failed when rbac is not configured', function () { $tenant = Tenant::factory()->create([ 'rbac_status' => null, 'rbac_last_checked_at' => null, ]); $backupSet = BackupSet::create([ 'tenant_id' => $tenant->id, 'name' => 'Backup', 'status' => 'completed', 'item_count' => 0, ]); $restoreRun = RestoreRun::create([ 'tenant_id' => $tenant->id, 'backup_set_id' => $backupSet->id, 'requested_by' => 'actor@example.com', 'is_dry_run' => false, 'status' => RestoreRunStatus::Running->value, 'requested_items' => null, 'preview' => [], 'results' => null, 'metadata' => [], ]); $this->mock(AssignmentRestoreService::class, function (MockInterface $mock) { $mock->shouldNotReceive('restore'); }); $operationRun = app(OperationRunService::class)->ensureRun( tenant: $tenant, type: 'assignments.restore', inputs: [ 'restore_run_id' => $restoreRun->id, 'policy_type' => 'deviceConfiguration', 'policy_id' => 'another-policy-id', ], ); $assignments = [ ['target' => ['@odata.type' => '#microsoft.graph.allLicensedUsersAssignmentTarget']], ]; $job = new RestoreAssignmentsJob( restoreRunId: $restoreRun->id, tenantId: (int) $tenant->getKey(), policyType: 'deviceConfiguration', policyId: 'another-policy-id', assignments: $assignments, groupMapping: [], foundationMapping: [], actorEmail: 'actor@example.com', actorName: 'Actor', operationRun: $operationRun, ); $job->handle( app(AssignmentRestoreService::class), app(OperationRunService::class), ); $operationRun->refresh(); $failures = is_array($operationRun->failure_summary) ? $operationRun->failure_summary : []; $reasonCodes = array_column($failures, 'reason_code'); expect($reasonCodes)->toContain('intune_rbac.not_configured'); });