test: refresh api and job feature suites
This commit is contained in:
parent
347af07a79
commit
e82942b387
4 changed files with 240 additions and 240 deletions
|
|
@ -1,145 +1,167 @@
|
|||
<?php
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
// Create a team with owner
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
$this->user = User::factory()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
|
||||
// Create an API token for the user
|
||||
$this->token = $this->user->createToken('test-token', ['*'], $this->team->id);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->token = $this->user->createToken('test-token', ['*']);
|
||||
$this->bearerToken = $this->token->plainTextToken;
|
||||
|
||||
// Mock a database - we'll use Mockery to avoid needing actual database setup
|
||||
$this->database = \Mockery::mock(StandalonePostgresql::class);
|
||||
$this->database->shouldReceive('getAttribute')->with('id')->andReturn(1);
|
||||
$this->database->shouldReceive('getAttribute')->with('uuid')->andReturn('test-db-uuid');
|
||||
$this->database->shouldReceive('getAttribute')->with('postgres_db')->andReturn('testdb');
|
||||
$this->database->shouldReceive('type')->andReturn('standalone-postgresql');
|
||||
$this->database->shouldReceive('getMorphClass')->andReturn('App\Models\StandalonePostgresql');
|
||||
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
|
||||
StandaloneDocker::withoutEvents(function () {
|
||||
$this->destination = StandaloneDocker::firstOrCreate(
|
||||
['server_id' => $this->server->id, 'network' => 'coolify'],
|
||||
['uuid' => (string) Str::uuid(), 'name' => 'test-docker']
|
||||
);
|
||||
});
|
||||
|
||||
$this->project = Project::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Project',
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
$this->environment = $this->project->environments()->first();
|
||||
|
||||
$this->database = StandalonePostgresql::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test DB',
|
||||
'postgres_user' => 'postgres',
|
||||
'postgres_password' => 'password',
|
||||
'postgres_db' => 'testdb',
|
||||
'image' => 'postgres:15',
|
||||
'status' => 'running',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
]);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
\Mockery::close();
|
||||
});
|
||||
function backupHeaders(): array
|
||||
{
|
||||
return [
|
||||
'Authorization' => 'Bearer '.test()->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
}
|
||||
|
||||
describe('POST /api/v1/databases/{uuid}/backups', function () {
|
||||
test('creates backup configuration with minimal required fields', function () {
|
||||
// This is a unit-style test using mocks to avoid database dependency
|
||||
// For full integration testing, this should be run inside Docker
|
||||
test('creates backup configuration with valid frequency', function () {
|
||||
$response = $this->withHeaders(backupHeaders())
|
||||
->postJson("/api/v1/databases/{$this->database->uuid}/backups", [
|
||||
'frequency' => 'daily',
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/databases/test-db-uuid/backups', [
|
||||
'frequency' => 'daily',
|
||||
]);
|
||||
$response->assertStatus(201);
|
||||
$response->assertJsonStructure(['uuid', 'message']);
|
||||
$response->assertJson(['message' => 'Backup configuration created successfully.']);
|
||||
});
|
||||
|
||||
// Since we're mocking, this test verifies the endpoint exists and basic validation
|
||||
// Full integration tests should be run in Docker environment
|
||||
expect($response->status())->toBeIn([201, 404, 422]);
|
||||
test('creates backup with valid cron expression', function () {
|
||||
$response = $this->withHeaders(backupHeaders())
|
||||
->postJson("/api/v1/databases/{$this->database->uuid}/backups", [
|
||||
'frequency' => '0 2 * * *',
|
||||
]);
|
||||
|
||||
$response->assertStatus(201);
|
||||
$response->assertJsonStructure(['uuid', 'message']);
|
||||
});
|
||||
|
||||
test('accepts all predefined frequency values', function () {
|
||||
$frequencies = ['every_minute', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'];
|
||||
|
||||
foreach ($frequencies as $frequency) {
|
||||
$response = $this->withHeaders(backupHeaders())
|
||||
->postJson("/api/v1/databases/{$this->database->uuid}/backups", [
|
||||
'frequency' => $frequency,
|
||||
]);
|
||||
|
||||
$response->assertStatus(201, "Expected 201 for frequency '{$frequency}', got {$response->status()}");
|
||||
}
|
||||
});
|
||||
|
||||
test('validates frequency is required', function () {
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/databases/test-db-uuid/backups', [
|
||||
'enabled' => true,
|
||||
]);
|
||||
$response = $this->withHeaders(backupHeaders())
|
||||
->postJson("/api/v1/databases/{$this->database->uuid}/backups", [
|
||||
'enabled' => true,
|
||||
]);
|
||||
|
||||
$response->assertStatus(422);
|
||||
$response->assertJsonValidationErrors(['frequency']);
|
||||
});
|
||||
|
||||
test('validates s3_storage_uuid required when save_s3 is true', function () {
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/databases/test-db-uuid/backups', [
|
||||
'frequency' => 'daily',
|
||||
'save_s3' => true,
|
||||
]);
|
||||
test('rejects invalid frequency format', function () {
|
||||
$response = $this->withHeaders(backupHeaders())
|
||||
->postJson("/api/v1/databases/{$this->database->uuid}/backups", [
|
||||
'frequency' => 'invalid-frequency',
|
||||
]);
|
||||
|
||||
// Should fail validation because s3_storage_uuid is missing
|
||||
expect($response->status())->toBeIn([404, 422]);
|
||||
$response->assertStatus(422);
|
||||
});
|
||||
|
||||
test('rejects invalid frequency format', function () {
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/databases/test-db-uuid/backups', [
|
||||
'frequency' => 'invalid-frequency',
|
||||
]);
|
||||
test('validates s3_storage_uuid required when save_s3 is true', function () {
|
||||
$response = $this->withHeaders(backupHeaders())
|
||||
->postJson("/api/v1/databases/{$this->database->uuid}/backups", [
|
||||
'frequency' => 'daily',
|
||||
'save_s3' => true,
|
||||
]);
|
||||
|
||||
expect($response->status())->toBeIn([404, 422]);
|
||||
$response->assertStatus(422);
|
||||
});
|
||||
|
||||
test('validates retention fields are integers with minimum 0', function () {
|
||||
$response = $this->withHeaders(backupHeaders())
|
||||
->postJson("/api/v1/databases/{$this->database->uuid}/backups", [
|
||||
'frequency' => 'daily',
|
||||
'database_backup_retention_amount_locally' => -1,
|
||||
]);
|
||||
|
||||
$response->assertStatus(422);
|
||||
});
|
||||
|
||||
test('rejects extra fields not in allowed list', function () {
|
||||
$response = $this->withHeaders(backupHeaders())
|
||||
->postJson("/api/v1/databases/{$this->database->uuid}/backups", [
|
||||
'frequency' => 'daily',
|
||||
'invalid_field' => 'invalid_value',
|
||||
]);
|
||||
|
||||
$response->assertStatus(422);
|
||||
});
|
||||
|
||||
test('rejects request without authentication', function () {
|
||||
$response = $this->postJson('/api/v1/databases/test-db-uuid/backups', [
|
||||
$response = $this->postJson("/api/v1/databases/{$this->database->uuid}/backups", [
|
||||
'frequency' => 'daily',
|
||||
]);
|
||||
|
||||
$response->assertStatus(401);
|
||||
});
|
||||
|
||||
test('validates retention fields are integers with minimum 0', function () {
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/databases/test-db-uuid/backups', [
|
||||
'frequency' => 'daily',
|
||||
'database_backup_retention_amount_locally' => -1,
|
||||
]);
|
||||
|
||||
expect($response->status())->toBeIn([404, 422]);
|
||||
});
|
||||
|
||||
test('accepts valid cron expressions', function () {
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/databases/test-db-uuid/backups', [
|
||||
'frequency' => '0 2 * * *', // Daily at 2 AM
|
||||
]);
|
||||
|
||||
// Will fail with 404 because database doesn't exist, but validates the request format
|
||||
expect($response->status())->toBeIn([201, 404, 422]);
|
||||
});
|
||||
|
||||
test('accepts predefined frequency values', function () {
|
||||
$frequencies = ['every_minute', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'];
|
||||
|
||||
foreach ($frequencies as $frequency) {
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/databases/test-db-uuid/backups', [
|
||||
'frequency' => $frequency,
|
||||
test('returns 404 for non-existent database uuid', function () {
|
||||
$response = $this->withHeaders(backupHeaders())
|
||||
->postJson('/api/v1/databases/non-existent-uuid/backups', [
|
||||
'frequency' => 'daily',
|
||||
]);
|
||||
|
||||
// Will fail with 404 because database doesn't exist, but validates the request format
|
||||
expect($response->status())->toBeIn([201, 404, 422]);
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects extra fields not in allowed list', function () {
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/databases/test-db-uuid/backups', [
|
||||
'frequency' => 'daily',
|
||||
'invalid_field' => 'invalid_value',
|
||||
]);
|
||||
|
||||
expect($response->status())->toBeIn([404, 422]);
|
||||
$response->assertStatus(404);
|
||||
$response->assertJson(['message' => 'Database not found.']);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
|
|
@ -10,13 +11,17 @@
|
|||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
// Create a team with owner
|
||||
$this->team = Team::factory()->create();
|
||||
$this->user = User::factory()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
// Create an API token for the user
|
||||
$this->token = $this->user->createToken('test-token', ['*'], $this->team->id);
|
||||
$this->token = $this->user->createToken('test-token', ['*']);
|
||||
$this->bearerToken = $this->token->plainTextToken;
|
||||
|
||||
// Create a server for the team
|
||||
|
|
@ -76,7 +81,7 @@
|
|||
])->postJson("/api/v1/deployments/{$deployment->deployment_uuid}/cancel");
|
||||
|
||||
$response->assertStatus(400);
|
||||
$response->assertJsonFragment(['Deployment cannot be cancelled']);
|
||||
expect($response->json('message'))->toContain('Deployment cannot be cancelled');
|
||||
});
|
||||
|
||||
test('returns 400 when deployment is already failed', function () {
|
||||
|
|
@ -93,7 +98,7 @@
|
|||
])->postJson("/api/v1/deployments/{$deployment->deployment_uuid}/cancel");
|
||||
|
||||
$response->assertStatus(400);
|
||||
$response->assertJsonFragment(['Deployment cannot be cancelled']);
|
||||
expect($response->json('message'))->toContain('Deployment cannot be cancelled');
|
||||
});
|
||||
|
||||
test('returns 400 when deployment is already cancelled', function () {
|
||||
|
|
@ -110,10 +115,10 @@
|
|||
])->postJson("/api/v1/deployments/{$deployment->deployment_uuid}/cancel");
|
||||
|
||||
$response->assertStatus(400);
|
||||
$response->assertJsonFragment(['Deployment cannot be cancelled']);
|
||||
expect($response->json('message'))->toContain('Deployment cannot be cancelled');
|
||||
});
|
||||
|
||||
test('successfully cancels queued deployment', function () {
|
||||
test('cancels queued deployment and updates status in database', function () {
|
||||
$deployment = ApplicationDeploymentQueue::create([
|
||||
'deployment_uuid' => 'queued-deployment-uuid',
|
||||
'application_id' => 1,
|
||||
|
|
@ -121,20 +126,17 @@
|
|||
'status' => ApplicationDeploymentStatus::QUEUED->value,
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
$this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson("/api/v1/deployments/{$deployment->deployment_uuid}/cancel");
|
||||
|
||||
// Expect success (200) or 500 if server connection fails (which is expected in test environment)
|
||||
expect($response->status())->toBeIn([200, 500]);
|
||||
|
||||
// Verify deployment status was updated to cancelled
|
||||
// The controller updates status before SSH calls, so DB state is always correct
|
||||
$deployment->refresh();
|
||||
expect($deployment->status)->toBe(ApplicationDeploymentStatus::CANCELLED_BY_USER->value);
|
||||
});
|
||||
|
||||
test('successfully cancels in-progress deployment', function () {
|
||||
test('cancels in-progress deployment and updates status in database', function () {
|
||||
$deployment = ApplicationDeploymentQueue::create([
|
||||
'deployment_uuid' => 'in-progress-deployment-uuid',
|
||||
'application_id' => 1,
|
||||
|
|
@ -142,42 +144,13 @@
|
|||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
$this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson("/api/v1/deployments/{$deployment->deployment_uuid}/cancel");
|
||||
|
||||
// Expect success (200) or 500 if server connection fails (which is expected in test environment)
|
||||
expect($response->status())->toBeIn([200, 500]);
|
||||
|
||||
// Verify deployment status was updated to cancelled
|
||||
// The controller updates status before SSH calls, so DB state is always correct
|
||||
$deployment->refresh();
|
||||
expect($deployment->status)->toBe(ApplicationDeploymentStatus::CANCELLED_BY_USER->value);
|
||||
});
|
||||
|
||||
test('returns correct response structure on success', function () {
|
||||
$deployment = ApplicationDeploymentQueue::create([
|
||||
'deployment_uuid' => 'success-deployment-uuid',
|
||||
'application_id' => 1,
|
||||
'server_id' => $this->server->id,
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$this->bearerToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson("/api/v1/deployments/{$deployment->deployment_uuid}/cancel");
|
||||
|
||||
if ($response->status() === 200) {
|
||||
$response->assertJsonStructure([
|
||||
'message',
|
||||
'deployment_uuid',
|
||||
'status',
|
||||
]);
|
||||
$response->assertJson([
|
||||
'deployment_uuid' => $deployment->deployment_uuid,
|
||||
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,58 +1,68 @@
|
|||
<?php
|
||||
|
||||
it('tests login rate limiting with different IPs like the Python script', function () {
|
||||
// Create a test route that mimics login behavior
|
||||
// We'll directly test the rate limiter behavior
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
|
||||
$baseUrl = '/login';
|
||||
$email = 'grumpinout+admin@wearehackerone.com';
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
// First, get a CSRF token by visiting the login page
|
||||
$loginPageResponse = $this->get($baseUrl);
|
||||
$loginPageResponse->assertSuccessful();
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
RateLimiter::clear('login');
|
||||
|
||||
// Extract CSRF token using regex similar to Python script
|
||||
preg_match('/name="_token"\s+value="([^"]+)"/', $loginPageResponse->getContent(), $matches);
|
||||
$token = $matches[1] ?? null;
|
||||
|
||||
expect($token)->not->toBeNull('CSRF token should be found');
|
||||
|
||||
// Test 14 login attempts with different IPs (like the Python script does 1-14)
|
||||
$results = [];
|
||||
for ($i = 1; $i <= 14; $i++) {
|
||||
$spoofedIp = "198.51.100.{$i}";
|
||||
|
||||
$response = $this->withHeader('X-Forwarded-For', $spoofedIp)
|
||||
->post($baseUrl, [
|
||||
'_token' => $token,
|
||||
'email' => $email,
|
||||
'password' => "WrongPass{$i}!",
|
||||
]);
|
||||
|
||||
$statusCode = $response->getStatusCode();
|
||||
$rateLimitLimit = $response->headers->get('X-RateLimit-Limit');
|
||||
$rateLimitRemaining = $response->headers->get('X-RateLimit-Remaining');
|
||||
|
||||
$results[$i] = [
|
||||
'ip' => $spoofedIp,
|
||||
'status' => $statusCode,
|
||||
'rate_limit' => $rateLimitLimit,
|
||||
'rate_limit_remaining' => $rateLimitRemaining,
|
||||
];
|
||||
|
||||
// Print output similar to Python script
|
||||
echo 'Attempt '.str_pad($i, 2, '0', STR_PAD_LEFT).": status=$statusCode, RL=$rateLimitLimit/$rateLimitRemaining\n";
|
||||
|
||||
// Add a small delay like the Python script (0.2 seconds)
|
||||
usleep(200000);
|
||||
}
|
||||
|
||||
// Verify results
|
||||
expect($results)->toHaveCount(14);
|
||||
|
||||
// Check that we got responses for all attempts
|
||||
foreach ($results as $i => $result) {
|
||||
expect($result['status'])->toBeGreaterThanOrEqual(200);
|
||||
expect($result['ip'])->toBe("198.51.100.{$i}");
|
||||
}
|
||||
$this->user = User::factory()->create([
|
||||
'email' => 'test@example.com',
|
||||
'password' => bcrypt('password'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('login is rate limited after 5 failed attempts from same IP', function () {
|
||||
$email = 'test@example.com';
|
||||
|
||||
// First 5 attempts should be accepted (302 redirect back with error, not 429)
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$response = $this->post('/login', [
|
||||
'email' => $email,
|
||||
'password' => 'wrong-password',
|
||||
]);
|
||||
|
||||
expect($response->status())->toBe(302, "Attempt {$i} should redirect (302), got {$response->status()}");
|
||||
}
|
||||
|
||||
// 6th attempt from same IP should be throttled
|
||||
$response = $this->post('/login', [
|
||||
'email' => $email,
|
||||
'password' => 'wrong-password',
|
||||
]);
|
||||
|
||||
expect($response->status())->toBe(429, 'Expected 429 Too Many Requests after exceeding rate limit');
|
||||
});
|
||||
|
||||
test('rate limit is scoped per email and IP combination', function () {
|
||||
// Exhaust rate limit for first email
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$this->post('/login', [
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'wrong-password',
|
||||
]);
|
||||
}
|
||||
|
||||
// Different email from same IP should still work (different composite key)
|
||||
$response = $this->post('/login', [
|
||||
'email' => 'other@example.com',
|
||||
'password' => 'wrong-password',
|
||||
]);
|
||||
|
||||
expect($response->status())->toBe(302, 'Different email should not be rate limited');
|
||||
});
|
||||
|
||||
test('successful login is still possible within rate limit', function () {
|
||||
$response = $this->post('/login', [
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
expect($response->status())->not->toBe(429);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,70 +1,65 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Jobs\CoolifyTask;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('can dispatch CoolifyTask successfully', function () {
|
||||
// Skip if no servers available
|
||||
$server = Server::where('ip', '!=', '1.2.3.4')->first();
|
||||
beforeEach(function () {
|
||||
$team = Team::factory()->create();
|
||||
Server::factory()->create(['team_id' => $team->id]);
|
||||
|
||||
if (! $server) {
|
||||
$this->markTestSkipped('No servers available for testing');
|
||||
}
|
||||
|
||||
Queue::fake();
|
||||
|
||||
// Create an activity for the task
|
||||
$activity = activity()
|
||||
$this->activity = activity()
|
||||
->withProperties([
|
||||
'server_uuid' => $server->uuid,
|
||||
'server_uuid' => Server::first()->uuid,
|
||||
'command' => 'echo "test"',
|
||||
'type' => 'inline',
|
||||
'status' => ProcessStatus::QUEUED->value,
|
||||
])
|
||||
->event('inline')
|
||||
->log('[]');
|
||||
|
||||
// Dispatch the job
|
||||
CoolifyTask::dispatch(
|
||||
activity: $activity,
|
||||
$this->job = new CoolifyTask(
|
||||
activity: $this->activity,
|
||||
ignore_errors: false,
|
||||
call_event_on_finish: null,
|
||||
call_event_data: null
|
||||
call_event_data: null,
|
||||
);
|
||||
|
||||
// Assert job was dispatched
|
||||
Queue::assertPushed(CoolifyTask::class);
|
||||
});
|
||||
|
||||
it('has correct retry configuration on CoolifyTask', function () {
|
||||
$server = Server::where('ip', '!=', '1.2.3.4')->first();
|
||||
|
||||
if (! $server) {
|
||||
$this->markTestSkipped('No servers available for testing');
|
||||
}
|
||||
|
||||
$activity = activity()
|
||||
->withProperties([
|
||||
'server_uuid' => $server->uuid,
|
||||
'command' => 'echo "test"',
|
||||
'type' => 'inline',
|
||||
])
|
||||
->event('inline')
|
||||
->log('[]');
|
||||
|
||||
$job = new CoolifyTask(
|
||||
activity: $activity,
|
||||
ignore_errors: false,
|
||||
call_event_on_finish: null,
|
||||
call_event_data: null
|
||||
);
|
||||
|
||||
// Assert retry configuration
|
||||
expect($job->tries)->toBe(3);
|
||||
expect($job->maxExceptions)->toBe(1);
|
||||
expect($job->timeout)->toBe(600);
|
||||
expect($job->backoff())->toBe([30, 90, 180]);
|
||||
test('has correct retry configuration', function () {
|
||||
expect($this->job->tries)->toBe(3)
|
||||
->and($this->job->maxExceptions)->toBe(1)
|
||||
->and($this->job->timeout)->toBe(600)
|
||||
->and($this->job->backoff())->toBe([30, 90, 180]);
|
||||
});
|
||||
|
||||
test('is queued on the high priority queue', function () {
|
||||
expect($this->job->queue)->toBe('high');
|
||||
});
|
||||
|
||||
test('marks activity as error on permanent failure', function () {
|
||||
$exception = new \RuntimeException('SSH connection failed');
|
||||
|
||||
$this->job->failed($exception);
|
||||
|
||||
$this->activity->refresh();
|
||||
$properties = $this->activity->properties;
|
||||
|
||||
expect($properties['status'])->toBe(ProcessStatus::ERROR->value)
|
||||
->and($properties['error'])->toBe('SSH connection failed')
|
||||
->and($properties)->toHaveKey('failed_at');
|
||||
});
|
||||
|
||||
test('marks activity as error with default message when exception is null', function () {
|
||||
$this->job->failed(null);
|
||||
|
||||
$this->activity->refresh();
|
||||
$properties = $this->activity->properties;
|
||||
|
||||
expect($properties['status'])->toBe(ProcessStatus::ERROR->value)
|
||||
->and($properties['error'])->toBe('Job permanently failed');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue