create([ 'tenant_id' => $tenantContext->id, 'user_id' => $user->id, 'type' => 'tenant.sync', 'status' => 'queued', 'summary_counts' => [], 'context' => [ 'target_scope' => [ 'entra_tenant_id' => (string) ($tenantContext->tenant_id ?? $tenantContext->external_id), ], ], ]); Bus::fake(); $job = new BulkTenantSyncJob( tenantId: (int) $tenantContext->getKey(), userId: (int) $user->getKey(), tenantIds: [3, 2, 1], operationRun: $run, context: [], ); $job->handle(app(OperationRunService::class)); $run = $run->fresh(); expect($run)->not->toBeNull(); expect($run?->status)->toBe('running'); expect($run?->summary_counts['total'] ?? null)->toBe(3); Bus::assertDispatched(TenantSyncWorkerJob::class, 3); })->group('ops-ux'); it('syncs eligible tenants and updates summary counts', function (): void { [$user, $tenantContext] = createUserWithTenant(role: 'owner'); $eligible = Tenant::factory()->create([ 'status' => 'active', 'deleted_at' => null, ]); $inactive = Tenant::factory()->create([ 'status' => 'inactive', 'deleted_at' => null, ]); $unauthorized = Tenant::factory()->create([ 'status' => 'active', 'deleted_at' => null, ]); $user->tenants()->syncWithoutDetaching([ $eligible->getKey() => ['role' => 'owner'], $inactive->getKey() => ['role' => 'owner'], ]); mock(PolicySyncService::class) ->shouldReceive('syncPolicies') ->andReturn([]); $lock = new class implements Lock { public function get($callback = null): bool { return true; } public function block($seconds, $callback = null): bool { return true; } public function release(): bool { return true; } public function owner(): string { return 'test'; } public function forceRelease(): void { // no-op } }; Cache::partialMock() ->shouldReceive('lock') ->andReturn($lock); $run = OperationRun::factory()->create([ 'tenant_id' => $tenantContext->id, 'user_id' => $user->id, 'type' => 'tenant.sync', 'status' => 'running', 'summary_counts' => [ 'total' => 4, 'processed' => 0, 'failed' => 0, ], 'context' => [ 'target_scope' => [ 'entra_tenant_id' => (string) ($tenantContext->tenant_id ?? $tenantContext->external_id), ], ], ]); (new TenantSyncWorkerJob( tenantId: (int) $eligible->getKey(), userId: (int) $user->getKey(), operationRun: $run, ))->handle(app(OperationRunService::class), app(TargetScopeConcurrencyLimiter::class), app(PolicySyncService::class)); (new TenantSyncWorkerJob( tenantId: (int) $inactive->getKey(), userId: (int) $user->getKey(), operationRun: $run, ))->handle(app(OperationRunService::class), app(TargetScopeConcurrencyLimiter::class), app(PolicySyncService::class)); (new TenantSyncWorkerJob( tenantId: (int) $unauthorized->getKey(), userId: (int) $user->getKey(), operationRun: $run, ))->handle(app(OperationRunService::class), app(TargetScopeConcurrencyLimiter::class), app(PolicySyncService::class)); (new TenantSyncWorkerJob( tenantId: 999999, userId: (int) $user->getKey(), operationRun: $run, ))->handle(app(OperationRunService::class), app(TargetScopeConcurrencyLimiter::class), app(PolicySyncService::class)); $run = $run->fresh(); expect($run)->not->toBeNull(); expect($run?->status)->toBe('completed'); expect($run?->outcome)->toBe('partially_succeeded'); expect($run?->summary_counts['processed'] ?? null)->toBe(4); expect($run?->summary_counts['succeeded'] ?? null)->toBe(1); expect($run?->summary_counts['skipped'] ?? null)->toBe(2); expect($run?->summary_counts['failed'] ?? null)->toBe(1); })->group('ops-ux');