create(); $connection = ProviderConnection::factory()->create([ 'tenant_id' => $tenant->getKey(), 'provider' => 'microsoft', 'entra_tenant_id' => 'entra-tenant-id', ]); $dispatched = 0; $gate = app(ProviderOperationStartGate::class); $result = $gate->start( tenant: $tenant, connection: $connection, operationType: 'provider.connection.check', dispatcher: function (OperationRun $run) use (&$dispatched): void { $dispatched++; expect($run->type)->toBe('provider.connection.check'); }, ); expect($dispatched)->toBe(1); expect($result->status)->toBe('started'); expect($result->dispatched)->toBeTrue(); $run = $result->run->fresh(); expect($run)->not->toBeNull(); expect($run->type)->toBe('provider.connection.check'); expect($run->status)->toBe('queued'); expect($run->context)->toMatchArray([ 'provider' => 'microsoft', 'module' => 'health_check', 'provider_connection_id' => (int) $connection->getKey(), 'target_scope' => [ 'entra_tenant_id' => 'entra-tenant-id', ], ]); }); it('dedupes when the same operation is already active for the scope', function (): void { $tenant = Tenant::factory()->create(); $connection = ProviderConnection::factory()->create([ 'tenant_id' => $tenant->getKey(), ]); $existing = OperationRun::factory()->create([ 'tenant_id' => $tenant->getKey(), 'type' => 'provider.connection.check', 'status' => 'running', 'context' => [ 'provider_connection_id' => (int) $connection->getKey(), ], ]); $dispatched = 0; $gate = app(ProviderOperationStartGate::class); $result = $gate->start( tenant: $tenant, connection: $connection, operationType: 'provider.connection.check', dispatcher: function () use (&$dispatched): void { $dispatched++; }, ); expect($dispatched)->toBe(0); expect($result->status)->toBe('deduped'); expect($result->run->getKey())->toBe($existing->getKey()); expect(OperationRun::query()->where('tenant_id', $tenant->getKey())->count())->toBe(1); }); it('blocks when a different operation is already active for the scope', function (): void { $tenant = Tenant::factory()->create(); $connection = ProviderConnection::factory()->create([ 'tenant_id' => $tenant->getKey(), ]); $blocking = OperationRun::factory()->create([ 'tenant_id' => $tenant->getKey(), 'type' => 'inventory.sync', 'status' => 'queued', 'context' => [ 'provider_connection_id' => (int) $connection->getKey(), ], ]); $dispatched = 0; $gate = app(ProviderOperationStartGate::class); $result = $gate->start( tenant: $tenant, connection: $connection, operationType: 'provider.connection.check', dispatcher: function () use (&$dispatched): void { $dispatched++; }, ); expect($dispatched)->toBe(0); expect($result->status)->toBe('scope_busy'); expect($result->run->getKey())->toBe($blocking->getKey()); expect(OperationRun::query()->where('tenant_id', $tenant->getKey())->count())->toBe(1); });