fix(destination): promote networks atomically

Wrap destination promotion in a transaction so the main destination swap and additional network updates stay consistent. Add coverage for promoting an owned team network while preserving the previous main destination as an additional network.
This commit is contained in:
Andras Bacsai
2026-05-26 14:50:29 +02:00
parent f44ace3965
commit 8e033c5bc3
2 changed files with 27 additions and 9 deletions
+12 -9
View File
@@ -8,6 +8,7 @@ use App\Events\ApplicationStatusChanged;
use App\Models\Server;
use App\Models\StandaloneDocker;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -115,15 +116,17 @@ class Destination extends Component
$network = StandaloneDocker::ownedByCurrentTeam()->where('server_id', $server->id)->findOrFail($network_id);
$this->authorize('update', $this->resource);
$main_destination = $this->resource->destination;
$this->resource->update([
'destination_id' => $network->id,
'destination_type' => StandaloneDocker::class,
]);
$this->resource->additional_networks()->detach($network->id, ['server_id' => $server->id]);
$this->resource->additional_networks()->attach($main_destination->id, ['server_id' => $main_destination->server->id]);
$this->refreshServers();
$this->resource->refresh();
DB::transaction(function () use ($network, $server) {
$main_destination = $this->resource->destination;
$this->resource->update([
'destination_id' => $network->id,
'destination_type' => StandaloneDocker::class,
]);
$this->resource->additional_networks()->detach($network->id, ['server_id' => $server->id]);
$this->resource->additional_networks()->attach($main_destination->id, ['server_id' => $main_destination->server->id]);
$this->refreshServers();
$this->resource->refresh();
});
} catch (\Exception $e) {
return handleError($e, $this);
}
@@ -143,4 +143,19 @@ describe('Destination::promote GHSA-j395-3pqh-9r5g', function () {
expect($this->applicationA->fresh()->destination_id)->toBe($originalDestinationId);
});
test('can promote own team network and preserve previous main as additional network', function () {
$this->applicationA->additional_networks()->attach($this->destinationA2->id, ['server_id' => $this->serverA2->id]);
Livewire::test(Destination::class, ['resource' => $this->applicationA])
->call('promote', $this->destinationA2->id, $this->serverA2->id);
$application = $this->applicationA->fresh();
$additional = $application->additional_networks;
expect($application->destination_id)->toBe($this->destinationA2->id);
expect($additional)->toHaveCount(1);
expect($additional->first()->id)->toBe($this->destinationA->id);
expect($additional->first()->pivot->server_id)->toBe($this->serverA->id);
});
});