actingAs($user); $backupSet = BackupSet::factory()->create([ 'tenant_id' => $tenant->id, 'name' => 'Test backup', 'status' => 'completed', 'metadata' => ['failures' => []], ]); $policyA = Policy::factory()->create([ 'tenant_id' => $tenant->id, 'ignored_at' => null, ]); $policyB = Policy::factory()->create([ 'tenant_id' => $tenant->id, 'ignored_at' => null, ]); $versionA = PolicyVersion::factory()->create([ 'tenant_id' => $tenant->id, 'policy_id' => $policyA->id, 'policy_type' => $policyA->policy_type, 'platform' => $policyA->platform, 'snapshot' => ['id' => $policyA->external_id], ]); $run = BulkOperationRun::factory()->create([ 'tenant_id' => $tenant->id, 'user_id' => $user->id, 'resource' => 'backup_set', 'action' => 'add_policies', 'status' => 'pending', 'total_items' => 2, 'item_ids' => [ 'backup_set_id' => $backupSet->id, 'policy_ids' => [$policyA->id, $policyB->id], 'options' => [ 'include_assignments' => true, 'include_scope_tags' => true, 'include_foundations' => false, ], ], 'failures' => [], ]); $this->mock(PolicyCaptureOrchestrator::class, function (MockInterface $mock) use ($policyA, $policyB, $tenant, $versionA) { $mock->shouldReceive('capture') ->twice() ->andReturnUsing(function ( Policy $policy, \App\Models\Tenant $tenantArg, bool $includeAssignments = false, bool $includeScopeTags = false, ?string $createdBy = null, array $metadata = [] ) use ($policyA, $policyB, $tenant, $versionA) { expect($tenantArg->id)->toBe($tenant->id); expect($includeAssignments)->toBeTrue(); expect($includeScopeTags)->toBeTrue(); expect($metadata['backup_set_id'] ?? null)->not->toBeNull(); if ($policy->is($policyA)) { return [ 'version' => $versionA, 'captured' => [ 'payload' => [ 'id' => $policyA->external_id, '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy', ], 'assignments' => [], 'scope_tags' => ['ids' => ['0'], 'names' => ['Default']], 'metadata' => [], ], ]; } expect($policy->is($policyB))->toBeTrue(); return [ 'failure' => [ 'policy_id' => $policyB->id, 'reason' => 'Forbidden', 'status' => 403, ], ]; }); }); $job = new AddPoliciesToBackupSetJob( bulkRunId: (int) $run->getKey(), backupSetId: (int) $backupSet->getKey(), includeAssignments: true, includeScopeTags: true, includeFoundations: false, ); $job->handle( bulkOperationService: app(BulkOperationService::class), captureOrchestrator: app(PolicyCaptureOrchestrator::class), foundationSnapshots: $this->mock(FoundationSnapshotService::class), snapshotValidator: app(SnapshotValidator::class), ); $run->refresh(); $backupSet->refresh(); expect($run->status)->toBe('completed_with_errors'); expect($run->total_items)->toBe(2); expect($run->processed_items)->toBe(2); expect($run->succeeded)->toBe(1); expect($run->failed)->toBe(1); expect($run->skipped)->toBe(0); expect(BackupItem::query() ->where('backup_set_id', $backupSet->id) ->where('policy_id', $policyA->id) ->exists())->toBeTrue(); $failureEntry = collect($run->failures ?? []) ->firstWhere('item_id', (string) $policyB->id); expect($failureEntry)->not->toBeNull(); expect($failureEntry['reason_code'] ?? null)->toBe('graph_forbidden'); expect($backupSet->status)->toBe('partial'); });