mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 18:52:49 +00:00
- database import: add keep_owners (restore PostgreSQL owners and privileges) and sqlite_database (pick the target SQLite file) to the API, OpenAPI spec, and import form - oauth: require password confirmation and support team selection on OAuth login - remote process: send team broadcast events to the team of the server that ran the process instead of the causer's user id - servers API: handle the legacy build server setting - compose: support parent bind paths and volume driver options - dns: improve record hints and provider management - notifications: fix throttling and missing backup notifications - ui: rework the confirmation modal - add feature and unit tests for the new behavior
106 lines
4.6 KiB
PHP
106 lines
4.6 KiB
PHP
<?php
|
|
|
|
use App\Actions\CoolifyTask\RunRemoteProcess;
|
|
use App\Enums\ProcessStatus;
|
|
use App\Events\DatabaseStatusChanged;
|
|
use App\Events\ServiceStatusChanged;
|
|
use App\Jobs\CoolifyTask;
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\PrivateKey;
|
|
use App\Models\Server;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\Facades\Process;
|
|
use Spatie\Activitylog\Models\Activity;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
config()->set('cache.default', 'array');
|
|
config()->set('constants.ssh.mux_enabled', false);
|
|
InstanceSettings::forceCreate(['id' => 0]);
|
|
|
|
// The user id must differ from the team id, and another team must own the user id,
|
|
// so a team event that is sent to the user id would reach the wrong team.
|
|
User::factory()->count(2)->create();
|
|
$this->user = User::factory()->create();
|
|
$this->team = Team::factory()->create();
|
|
while (Team::query()->max('id') < $this->user->id) {
|
|
Team::factory()->create();
|
|
}
|
|
$this->otherTeam = Team::query()->findOrFail($this->user->id);
|
|
expect($this->user->id)->not->toBe($this->team->id);
|
|
|
|
$privateKey = PrivateKey::factory()->create(['team_id' => $this->team->id]);
|
|
$this->server = Server::factory()->create([
|
|
'team_id' => $this->team->id,
|
|
'private_key_id' => $privateKey->id,
|
|
'user' => 'root',
|
|
]);
|
|
|
|
$this->activity = activity()
|
|
->causedBy($this->user)
|
|
->withProperties([
|
|
'server_uuid' => $this->server->uuid,
|
|
'command' => 'echo test',
|
|
'type' => 'inline',
|
|
'status' => ProcessStatus::QUEUED->value,
|
|
'team_id' => $this->server->team_id,
|
|
])
|
|
->event('inline')
|
|
->log('[]');
|
|
});
|
|
|
|
it('broadcasts a team event of a finished remote process to the team of the server', function () {
|
|
Event::fake([ServiceStatusChanged::class]);
|
|
Process::fake(fn () => Process::result(output: 'ok'));
|
|
|
|
(new RunRemoteProcess(Activity::query()->findOrFail($this->activity->id), call_event_on_finish: 'ServiceStatusChanged'))();
|
|
|
|
Event::assertDispatched(ServiceStatusChanged::class, fn (ServiceStatusChanged $event) => $event->teamId === $this->team->id
|
|
&& $event->broadcastOn()[0]->name === "private-team.{$this->team->id}");
|
|
Event::assertNotDispatched(ServiceStatusChanged::class, fn (ServiceStatusChanged $event) => $event->teamId === $this->otherTeam->id);
|
|
});
|
|
|
|
it('broadcasts a team event of a failed coolify task to the team of the server', function () {
|
|
Event::fake([ServiceStatusChanged::class]);
|
|
|
|
(new CoolifyTask(Activity::query()->findOrFail($this->activity->id), ignore_errors: false, call_event_on_finish: 'ServiceStatusChanged', call_event_data: null))
|
|
->failed(new RuntimeException('failed'));
|
|
|
|
Event::assertDispatched(ServiceStatusChanged::class, fn (ServiceStatusChanged $event) => $event->teamId === $this->team->id);
|
|
Event::assertNotDispatched(ServiceStatusChanged::class, fn (ServiceStatusChanged $event) => $event->teamId === $this->otherTeam->id);
|
|
});
|
|
|
|
it('still sends a user event of a finished remote process to the user who started it', function () {
|
|
Event::fake([DatabaseStatusChanged::class]);
|
|
Process::fake(fn () => Process::result(output: 'ok'));
|
|
|
|
(new RunRemoteProcess(Activity::query()->findOrFail($this->activity->id), call_event_on_finish: 'DatabaseStatusChanged'))();
|
|
|
|
Event::assertDispatched(DatabaseStatusChanged::class, fn (DatabaseStatusChanged $event) => $event->userId === $this->user->id);
|
|
});
|
|
|
|
it('keeps the explicit event data of a remote process', function () {
|
|
Event::fake([ServiceStatusChanged::class]);
|
|
Process::fake(fn () => Process::result(output: 'ok'));
|
|
|
|
(new RunRemoteProcess(Activity::query()->findOrFail($this->activity->id), call_event_on_finish: 'ServiceStatusChanged', call_event_data: $this->otherTeam->id))();
|
|
|
|
Event::assertDispatched(ServiceStatusChanged::class, fn (ServiceStatusChanged $event) => $event->teamId === $this->otherTeam->id);
|
|
});
|
|
|
|
it('does not broadcast a team event when the activity has no team', function () {
|
|
Event::fake([ServiceStatusChanged::class]);
|
|
Process::fake(fn () => Process::result(output: 'ok'));
|
|
$activity = Activity::query()->findOrFail($this->activity->id);
|
|
$activity->properties = $activity->properties->except('team_id');
|
|
$activity->save();
|
|
|
|
(new RunRemoteProcess($activity, call_event_on_finish: 'ServiceStatusChanged'))();
|
|
|
|
Event::assertNotDispatched(ServiceStatusChanged::class, fn (ServiceStatusChanged $event) => $event->teamId === $this->otherTeam->id);
|
|
});
|