Merge branch 'next' into decouple-storage-from-sentinel

This commit is contained in:
Andras Bacsai
2025-12-03 09:19:09 +01:00
committed by GitHub
11 changed files with 330 additions and 124 deletions

View File

@@ -214,3 +214,90 @@ it('sends immediate notifications when outdated traefik is detected', function (
expect($notification->servers)->toHaveCount(1);
expect($notification->servers->first()->outdatedInfo['type'])->toBe('patch_update');
});
it('notification generates correct server proxy URLs', function () {
$team = Team::factory()->create();
$server = Server::factory()->create([
'name' => 'Test Server',
'team_id' => $team->id,
'uuid' => 'test-uuid-123',
]);
$server->outdatedInfo = [
'current' => '3.5.0',
'latest' => '3.5.6',
'type' => 'patch_update',
];
$notification = new TraefikVersionOutdated(collect([$server]));
$mail = $notification->toMail($team);
// Verify the mail has the transformed servers with URLs
expect($mail->viewData['servers'])->toHaveCount(1);
expect($mail->viewData['servers'][0]['name'])->toBe('Test Server');
expect($mail->viewData['servers'][0]['uuid'])->toBe('test-uuid-123');
expect($mail->viewData['servers'][0]['url'])->toBe(base_url().'/server/test-uuid-123/proxy');
expect($mail->viewData['servers'][0]['outdatedInfo'])->toBeArray();
});
it('notification transforms multiple servers with URLs correctly', function () {
$team = Team::factory()->create();
$server1 = Server::factory()->create([
'name' => 'Server 1',
'team_id' => $team->id,
'uuid' => 'uuid-1',
]);
$server1->outdatedInfo = [
'current' => '3.5.0',
'latest' => '3.5.6',
'type' => 'patch_update',
];
$server2 = Server::factory()->create([
'name' => 'Server 2',
'team_id' => $team->id,
'uuid' => 'uuid-2',
]);
$server2->outdatedInfo = [
'current' => '3.4.0',
'latest' => '3.6.0',
'type' => 'minor_upgrade',
'upgrade_target' => 'v3.6',
];
$servers = collect([$server1, $server2]);
$notification = new TraefikVersionOutdated($servers);
$mail = $notification->toMail($team);
// Verify both servers have URLs
expect($mail->viewData['servers'])->toHaveCount(2);
expect($mail->viewData['servers'][0]['name'])->toBe('Server 1');
expect($mail->viewData['servers'][0]['url'])->toBe(base_url().'/server/uuid-1/proxy');
expect($mail->viewData['servers'][1]['name'])->toBe('Server 2');
expect($mail->viewData['servers'][1]['url'])->toBe(base_url().'/server/uuid-2/proxy');
});
it('notification uses base_url helper not config app.url', function () {
$team = Team::factory()->create();
$server = Server::factory()->create([
'name' => 'Test Server',
'team_id' => $team->id,
'uuid' => 'test-uuid',
]);
$server->outdatedInfo = [
'current' => '3.5.0',
'latest' => '3.5.6',
'type' => 'patch_update',
];
$notification = new TraefikVersionOutdated(collect([$server]));
$mail = $notification->toMail($team);
// Verify URL starts with base_url() not config('app.url')
$generatedUrl = $mail->viewData['servers'][0]['url'];
expect($generatedUrl)->toStartWith(base_url());
expect($generatedUrl)->not->toContain('localhost');
});

View File

@@ -126,6 +126,70 @@ describe('aggregateFromStrings', function () {
expect($result)->toBe('starting:unknown');
});
test('returns degraded:unhealthy for single degraded container', function () {
$statuses = collect(['degraded:unhealthy']);
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('degraded:unhealthy');
});
test('returns degraded:unhealthy when mixing degraded with running healthy', function () {
$statuses = collect(['degraded:unhealthy', 'running:healthy']);
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('degraded:unhealthy');
});
test('returns degraded:unhealthy when mixing running healthy with degraded', function () {
$statuses = collect(['running:healthy', 'degraded:unhealthy']);
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('degraded:unhealthy');
});
test('returns degraded:unhealthy for multiple degraded containers', function () {
$statuses = collect(['degraded:unhealthy', 'degraded:unhealthy']);
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('degraded:unhealthy');
});
test('degraded status overrides all other non-critical states', function () {
$statuses = collect(['degraded:unhealthy', 'running:healthy', 'starting', 'paused']);
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('degraded:unhealthy');
});
test('returns starting:unknown when mixing starting with running healthy (service not fully ready)', function () {
$statuses = collect(['starting:unknown', 'running:healthy']);
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('starting:unknown');
});
test('returns starting:unknown when mixing created with running healthy', function () {
$statuses = collect(['created', 'running:healthy']);
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('starting:unknown');
});
test('returns starting:unknown for multiple starting containers with some running', function () {
$statuses = collect(['starting:unknown', 'starting:unknown', 'running:healthy']);
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('starting:unknown');
});
test('handles parentheses format input (backward compatibility)', function () {
$statuses = collect(['running (healthy)', 'running (unhealthy)']);
@@ -166,8 +230,16 @@ describe('aggregateFromStrings', function () {
expect($result)->toBe('degraded:unhealthy');
});
test('prioritizes running over paused/starting/exited', function () {
$statuses = collect(['running:healthy', 'starting', 'paused']);
test('mixed running and starting returns starting', function () {
$statuses = collect(['running:healthy', 'starting']);
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('starting:unknown');
});
test('prioritizes running over paused/exited when no starting', function () {
$statuses = collect(['running:healthy', 'paused', 'exited']);
$result = $this->aggregator->aggregateFromStrings($statuses);
@@ -398,7 +470,23 @@ describe('aggregateFromContainers', function () {
});
describe('state priority enforcement', function () {
test('restarting has highest priority', function () {
test('degraded from sub-resources has highest priority', function () {
$statuses = collect([
'degraded:unhealthy',
'restarting',
'running:healthy',
'dead',
'paused',
'starting',
'exited',
]);
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('degraded:unhealthy');
});
test('restarting has second highest priority', function () {
$statuses = collect([
'restarting',
'running:healthy',
@@ -413,7 +501,7 @@ describe('state priority enforcement', function () {
expect($result)->toBe('degraded:unhealthy');
});
test('crash loop has second highest priority', function () {
test('crash loop has third highest priority', function () {
$statuses = collect([
'exited',
'running:healthy',
@@ -426,7 +514,7 @@ describe('state priority enforcement', function () {
expect($result)->toBe('degraded:unhealthy');
});
test('mixed state (running + exited) has third priority', function () {
test('mixed state (running + exited) has fourth priority', function () {
$statuses = collect([
'running:healthy',
'exited',
@@ -439,6 +527,18 @@ describe('state priority enforcement', function () {
expect($result)->toBe('degraded:unhealthy');
});
test('mixed state (running + starting) has fifth priority', function () {
$statuses = collect([
'running:healthy',
'starting',
'paused',
]);
$result = $this->aggregator->aggregateFromStrings($statuses);
expect($result)->toBe('starting:unknown');
});
test('running:unhealthy has priority over running:unknown', function () {
$statuses = collect([
'running:unknown',