Files
coolify/tests/Feature/LogDrain/CloudwatchLogDrainTest.php
T
22a8c09dd6 Next (#12202)
Co-authored-by: Aditya Tripathi <aditya@climactic.co>
Co-authored-by: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rosslh <ross@rosshill.ca>
Co-authored-by: Abdulfetah Suudi <suudiabdulfetah@gmail.com>
Co-authored-by: Dharmveer <51308779+dharmveer97@users.noreply.github.com>
Co-authored-by: Maxwell <136101+mxswd@users.noreply.github.com>
2026-10-19 10:07:23 +02:00

245 lines
10 KiB
PHP

<?php
use App\Actions\Server\StartLogDrain;
use App\Actions\Server\StopLogDrain;
use App\Livewire\Server\LogDrains;
use App\Models\PrivateKey;
use App\Models\Server;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Process;
use Livewire\Livewire;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->user = User::factory()->create();
$this->team = $this->user->teams()->first();
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
$this->actingAs($this->user);
session(['currentTeam' => $this->team]);
});
it('enables the CloudWatch drain and persists its options', function () {
StartLogDrain::mock()->shouldReceive('handle')->once()->andReturn('ok');
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('logDrainCloudwatchGroup', '/coolify/prod')
->set('logDrainCloudwatchRegion', 'us-east-1')
->set('logDrainCloudwatchStreamPrefix', 'docker/')
->call('toggleLogDrain', 'cloudwatch')
->assertHasNoErrors()
->assertSet('isLogDrainCloudwatchEnabled', true);
$settings = $this->server->settings->fresh();
expect($settings->is_logdrain_cloudwatch_enabled)->toBeTrue()
->and($settings->logdrain_cloudwatch_group)->toBe('/coolify/prod')
->and($settings->logdrain_cloudwatch_region)->toBe('us-east-1')
->and($settings->logdrain_cloudwatch_stream_prefix)->toBe('docker/');
});
it('requires a log group before enabling CloudWatch', function () {
StartLogDrain::mock()->shouldNotReceive('handle');
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('isLogDrainCloudwatchEnabled', true)
->call('instantSave')
->assertDispatched('error')
->assertSet('isLogDrainCloudwatchEnabled', false);
expect($this->server->settings->fresh()->is_logdrain_cloudwatch_enabled)->toBeFalse();
});
it('requires a region before enabling CloudWatch', function () {
StartLogDrain::mock()->shouldNotReceive('handle');
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('logDrainCloudwatchGroup', 'coolify')
->call('toggleLogDrain', 'cloudwatch')
->assertDispatched('error')
->assertSet('isLogDrainCloudwatchEnabled', false);
expect($this->server->settings->fresh()->is_logdrain_cloudwatch_enabled)->toBeFalse();
});
it('rejects stream prefixes that could break the compose file', function (string $prefix) {
StartLogDrain::mock()->shouldNotReceive('handle');
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('logDrainCloudwatchGroup', 'coolify')
->set('logDrainCloudwatchRegion', 'us-east-1')
->set('logDrainCloudwatchStreamPrefix', $prefix)
->set('isLogDrainCloudwatchEnabled', true)
->call('instantSave')
->assertDispatched('error')
->assertSet('isLogDrainCloudwatchEnabled', false);
expect($this->server->settings->fresh()->is_logdrain_cloudwatch_enabled)->toBeFalse();
})->with(['docker/${HOME}', 'docker:', '{{.ID}}', 'with space']);
it('enables the CloudWatch drain from the status dropdown', function () {
StartLogDrain::mock()->shouldReceive('handle')->once()->andReturn('ok');
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('logDrainCloudwatchGroup', 'coolify')
->set('logDrainCloudwatchRegion', 'us-east-1')
->set('logDrainCloudwatchStreamPrefix', 'docker/')
->set('isLogDrainCloudwatchEnabled', true)
->call('instantSave')
->assertDispatched('success');
$settings = $this->server->settings->fresh();
expect($settings->is_logdrain_cloudwatch_enabled)->toBeTrue()
->and($settings->logdrain_cloudwatch_stream_prefix)->toBe('docker/');
});
it('disables other drains when CloudWatch is enabled', function () {
StartLogDrain::mock()->shouldReceive('handle')->andReturn('ok');
$this->server->settings->update([
'is_logdrain_newrelic_enabled' => true,
'logdrain_newrelic_license_key' => 'abc123',
'logdrain_newrelic_base_uri' => 'https://log-api.newrelic.com',
]);
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('logDrainCloudwatchGroup', 'coolify')
->set('logDrainCloudwatchRegion', 'us-east-1')
->call('toggleLogDrain', 'cloudwatch')
->assertSet('isLogDrainNewRelicEnabled', false)
->assertSet('isLogDrainCloudwatchEnabled', true);
$settings = $this->server->settings->fresh();
expect($settings->is_logdrain_newrelic_enabled)->toBeFalsy()
->and($settings->is_logdrain_cloudwatch_enabled)->toBeTrue();
});
it('reverts the CloudWatch flag when starting the drain fails', function () {
StartLogDrain::mock()->shouldReceive('handle')->andThrow(new RuntimeException('runtime boom'));
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('logDrainCloudwatchGroup', 'coolify')
->set('logDrainCloudwatchRegion', 'us-east-1')
->call('toggleLogDrain', 'cloudwatch')
->assertSet('isLogDrainCloudwatchEnabled', false);
expect($this->server->settings->fresh()->is_logdrain_cloudwatch_enabled)->toBeFalse();
});
it('forbids team members from enabling CloudWatch', function () {
StartLogDrain::mock()->shouldNotReceive('handle');
$member = User::factory()->create();
$member->teams()->attach($this->team, ['role' => 'member']);
$this->actingAs($member);
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('logDrainCloudwatchGroup', 'coolify')
->set('logDrainCloudwatchRegion', 'us-east-1')
->call('toggleLogDrain', 'cloudwatch');
expect($this->server->settings->fresh()->is_logdrain_cloudwatch_enabled)->toBeFalse();
});
it('does not deploy the Fluent Bit container for CloudWatch', function () {
StopLogDrain::mock()->shouldReceive('handle')->once()->andReturn('removed');
$this->server->settings->update([
'is_logdrain_cloudwatch_enabled' => true,
'logdrain_cloudwatch_group' => 'coolify',
'logdrain_cloudwatch_region' => 'us-east-1',
]);
$result = StartLogDrain::run($this->server->fresh());
expect($result)->toContain('awslogs');
});
it('turns off the legacy Highlight drain when CloudWatch is enabled', function () {
StartLogDrain::mock()->shouldReceive('handle')->andReturn('ok');
$this->server->settings->update([
'is_logdrain_highlight_enabled' => true,
'logdrain_highlight_project_id' => 'legacy-project',
]);
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('logDrainCloudwatchGroup', 'coolify')
->set('logDrainCloudwatchRegion', 'us-east-1')
->set('isLogDrainCloudwatchEnabled', true)
->call('instantSave')
->assertDispatched('success');
$settings = $this->server->settings->fresh();
expect($settings->is_logdrain_highlight_enabled)->toBeFalsy()
->and($settings->is_logdrain_cloudwatch_enabled)->toBeTrue();
});
it('turns off the legacy Highlight drain when switching drains with toggleLogDrain', function () {
StartLogDrain::mock()->shouldReceive('handle')->andReturn('ok');
$this->server->settings->update(['is_logdrain_highlight_enabled' => true]);
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('logDrainCloudwatchGroup', 'coolify')
->set('logDrainCloudwatchRegion', 'us-east-1')
->call('toggleLogDrain', 'cloudwatch');
$settings = $this->server->settings->fresh();
expect($settings->is_logdrain_highlight_enabled)->toBeFalsy()
->and($settings->is_logdrain_cloudwatch_enabled)->toBeTrue();
});
it('keeps the legacy Highlight drain when saving without enabling another drain', function () {
StartLogDrain::mock()->shouldReceive('handle')->andReturn('ok');
$this->server->settings->update(['is_logdrain_highlight_enabled' => true]);
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('logDrainCloudwatchGroup', 'coolify')
->set('logDrainCloudwatchRegion', 'us-east-1')
->call('submit')
->assertDispatched('success');
expect($this->server->settings->fresh()->is_logdrain_highlight_enabled)->toBeTruthy();
});
it('rejects a save that would enable two log drains at once', function () {
StartLogDrain::mock()->shouldNotReceive('handle');
$this->server->settings->update([
'is_logdrain_newrelic_enabled' => true,
'logdrain_newrelic_license_key' => 'abc123',
'logdrain_newrelic_base_uri' => 'https://log-api.newrelic.com',
]);
// The UI disables the CloudWatch dropdown here; this simulates a crafted request.
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->set('logDrainCloudwatchGroup', 'coolify')
->set('logDrainCloudwatchRegion', 'us-east-1')
->set('isLogDrainCloudwatchEnabled', true)
->call('instantSave')
->assertDispatched('error')
->assertSet('isLogDrainCloudwatchEnabled', false)
->assertSet('isLogDrainNewRelicEnabled', true);
$settings = $this->server->settings->fresh();
expect($settings->is_logdrain_cloudwatch_enabled)->toBeFalse()
->and($settings->is_logdrain_newrelic_enabled)->toBeTruthy();
});
it('keeps the Fluent Bit drain running if CloudWatch is also enabled, matching the containers logging config', function () {
Process::fake();
StopLogDrain::mock()->shouldReceive('handle')->andReturn('removed');
$this->server->update(['private_key_id' => PrivateKey::factory()->create(['team_id' => $this->team->id])->id]);
$this->server->settings->update([
'is_logdrain_axiom_enabled' => true,
'logdrain_axiom_dataset_name' => 'ds',
'logdrain_axiom_api_key' => 'key',
'is_logdrain_cloudwatch_enabled' => true,
'logdrain_cloudwatch_group' => 'coolify',
'logdrain_cloudwatch_region' => 'us-east-1',
]);
$server = $this->server->fresh();
expect(generate_log_drain_configuration($server)['driver'])->toBe('fluentd');
StartLogDrain::run($server);
Process::assertRan(fn ($process) => str_contains($process->command, 'log-drains/docker-compose.yml up -d'));
});