Merge remote-tracking branch 'origin/next' into mcp-server-instance-toggle

This commit is contained in:
Andras Bacsai committed 2026-04-30 11:30:45 +02:00
commit d057ce5172
8 files changed
+237 -18

No files matched your search

@@ -0,0 +1,107 @@
<?php
use App\Jobs\ApplicationDeploymentJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Environment;
use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\Team;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Bus;
uses(RefreshDatabase::class);
beforeEach(function () {
Bus::fake([ApplicationDeploymentJob::class]);
$this->team = Team::factory()->create();
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
$this->destination = StandaloneDocker::factory()->create([
'server_id' => $this->server->id,
'network' => 'test-network-'.fake()->unique()->word(),
]);
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
});
function makeApplication(int $environmentId, int $destinationId, ?string $gitCommitSha): Application
{
$attributes = [
'environment_id' => $environmentId,
'destination_id' => $destinationId,
'destination_type' => StandaloneDocker::class,
];
if ($gitCommitSha !== null) {
$attributes['git_commit_sha'] = $gitCommitSha;
}
return Application::factory()->create($attributes);
}
describe('queue_application_deployment commit resolution', function () {
test('uses application git_commit_sha when commit parameter omitted', function () {
$pinnedSha = 'abc123def456abc123def456abc123def456abc1';
$application = makeApplication($this->environment->id, $this->destination->id, $pinnedSha);
$result = queue_application_deployment(
application: $application,
deployment_uuid: 'test-deploy-uuid-1',
);
expect($result['status'])->toBe('queued');
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', 'test-deploy-uuid-1')->first();
expect($deployment)->not->toBeNull();
expect($deployment->commit)->toBe($pinnedSha);
});
test('falls back to HEAD when both commit parameter and git_commit_sha are unset', function () {
$application = makeApplication($this->environment->id, $this->destination->id, 'HEAD');
$result = queue_application_deployment(
application: $application,
deployment_uuid: 'test-deploy-uuid-2',
);
expect($result['status'])->toBe('queued');
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', 'test-deploy-uuid-2')->first();
expect($deployment->commit)->toBe('HEAD');
});
test('explicit commit parameter overrides application git_commit_sha', function () {
$pinnedSha = 'abc123def456abc123def456abc123def456abc1';
$webhookSha = '111222333444555666777888999000aaabbbccc1';
$application = makeApplication($this->environment->id, $this->destination->id, $pinnedSha);
$result = queue_application_deployment(
application: $application,
deployment_uuid: 'test-deploy-uuid-3',
commit: $webhookSha,
);
expect($result['status'])->toBe('queued');
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', 'test-deploy-uuid-3')->first();
expect($deployment->commit)->toBe($webhookSha);
});
test('treats empty string commit parameter as unset and uses git_commit_sha', function () {
$pinnedSha = 'abc123def456abc123def456abc123def456abc1';
$application = makeApplication($this->environment->id, $this->destination->id, $pinnedSha);
$result = queue_application_deployment(
application: $application,
deployment_uuid: 'test-deploy-uuid-4',
commit: '',
);
expect($result['status'])->toBe('queued');
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', 'test-deploy-uuid-4')->first();
expect($deployment->commit)->toBe($pinnedSha);
});
});
@@ -4,6 +4,7 @@ use App\Models\Server;
use App\Models\ServerSetting;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
uses(RefreshDatabase::class);
@@ -78,11 +79,73 @@ describe('ServerSetting::isValidSentinelToken', function () {
expect(ServerSetting::isValidSentinelToken(''))->toBeFalse();
});
it('returns false for null sentinel token', function () {
expect(ServerSetting::isValidSentinelToken(null))->toBeFalse();
});
it('rejects the reported PoC payload', function () {
expect(ServerSetting::isValidSentinelToken('abc" ; id >/tmp/coolify_poc_sentinel ; echo "'))->toBeFalse();
});
});
describe('ServerSetting::ensureValidSentinelToken', function () {
it('regenerates empty sentinel token via ensureValidSentinelToken', function () {
$settings = $this->server->settings;
DB::table('server_settings')->where('id', $settings->id)->update(['sentinel_token' => '']);
$settings->refresh();
$token = $settings->ensureValidSentinelToken();
expect($token)->not->toBeEmpty();
expect(ServerSetting::isValidSentinelToken($token))->toBeTrue();
expect($settings->fresh()->sentinel_token)->toBe($token);
});
it('regenerates token when stored value cannot be decrypted', function () {
$settings = $this->server->settings;
DB::table('server_settings')->where('id', $settings->id)->update(['sentinel_token' => 'not-encrypted-junk']);
$settings->refresh();
$token = $settings->ensureValidSentinelToken();
expect(ServerSetting::isValidSentinelToken($token))->toBeTrue();
expect($settings->fresh()->sentinel_token)->toBe($token);
});
it('returns existing valid token without regenerating', function () {
$settings = $this->server->settings;
$original = $settings->sentinel_token;
$token = $settings->ensureValidSentinelToken();
expect($token)->toBe($original);
});
it('throws RuntimeException only when regeneration also fails', function () {
$settings = $this->server->settings;
DB::table('server_settings')->where('id', $settings->id)->update(['sentinel_token' => '']);
$stub = new class extends ServerSetting
{
protected $table = 'server_settings';
public function generateSentinelToken(bool $save = true, bool $ignoreEvent = false): string
{
DB::table('server_settings')->where('id', $this->id)->update([
'sentinel_token' => encrypt('invalid token with spaces!'),
]);
return '';
}
};
$stub->setRawAttributes($settings->fresh()->getAttributes(), true);
$stub->exists = true;
expect(fn () => $stub->ensureValidSentinelToken())
->toThrow(RuntimeException::class, 'Sentinel token invalid after regeneration');
});
});
describe('generated sentinel tokens are valid', function () {
it('generates tokens that pass format validation', function () {
$settings = $this->server->settings;
@@ -92,4 +155,11 @@ describe('generated sentinel tokens are valid', function () {
expect($token)->not->toBeEmpty();
expect(ServerSetting::isValidSentinelToken($token))->toBeTrue();
});
it('returns the same value the cast reads back', function () {
$settings = $this->server->settings;
$returned = $settings->generateSentinelToken(save: true, ignoreEvent: true);
expect($settings->fresh()->sentinel_token)->toBe($returned);
});
});