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 committed 2026-05-26 14:50:29 +02:00
1 parent f44ace3965
commit 8e033c5bc3
2 files changed
+27 -9

No files matched your search

+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);
});
});