Fix: application rollback uses correct commit sha (#8576)
This commit is contained in:
commit
338fa1b45b
21 changed files with 842 additions and 596 deletions
|
|
@ -1087,19 +1087,24 @@ public function dirOnServer()
|
||||||
return application_configuration_dir()."/{$this->uuid}";
|
return application_configuration_dir()."/{$this->uuid}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
|
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false, ?string $commit = null)
|
||||||
{
|
{
|
||||||
$baseDir = $this->generateBaseDir($deployment_uuid);
|
$baseDir = $this->generateBaseDir($deployment_uuid);
|
||||||
$escapedBaseDir = escapeshellarg($baseDir);
|
$escapedBaseDir = escapeshellarg($baseDir);
|
||||||
$isShallowCloneEnabled = $this->settings?->is_git_shallow_clone_enabled ?? false;
|
$isShallowCloneEnabled = $this->settings?->is_git_shallow_clone_enabled ?? false;
|
||||||
|
|
||||||
if ($this->git_commit_sha !== 'HEAD') {
|
// Use the explicitly passed commit (e.g. from rollback), falling back to the application's git_commit_sha.
|
||||||
|
// Invalid refs will cause the git checkout/fetch command to fail on the remote server.
|
||||||
|
$commitToUse = $commit ?? $this->git_commit_sha;
|
||||||
|
|
||||||
|
if ($commitToUse !== 'HEAD') {
|
||||||
|
$escapedCommit = escapeshellarg($commitToUse);
|
||||||
// If shallow clone is enabled and we need a specific commit,
|
// If shallow clone is enabled and we need a specific commit,
|
||||||
// we need to fetch that specific commit with depth=1
|
// we need to fetch that specific commit with depth=1
|
||||||
if ($isShallowCloneEnabled) {
|
if ($isShallowCloneEnabled) {
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git fetch --depth=1 origin {$this->git_commit_sha} && git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
|
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git fetch --depth=1 origin {$escapedCommit} && git -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1";
|
||||||
} else {
|
} else {
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
|
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->settings->is_git_submodules_enabled) {
|
if ($this->settings->is_git_submodules_enabled) {
|
||||||
|
|
@ -1287,7 +1292,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
|
||||||
$escapedRepoUrl = escapeshellarg("{$this->source->html_url}/{$customRepository}");
|
$escapedRepoUrl = escapeshellarg("{$this->source->html_url}/{$customRepository}");
|
||||||
$git_clone_command = "{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}";
|
$git_clone_command = "{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}";
|
||||||
if (! $only_checkout) {
|
if (! $only_checkout) {
|
||||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
|
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true, commit: $commit);
|
||||||
}
|
}
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
||||||
|
|
@ -1308,7 +1313,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
|
||||||
$fullRepoUrl = $repoUrl;
|
$fullRepoUrl = $repoUrl;
|
||||||
}
|
}
|
||||||
if (! $only_checkout) {
|
if (! $only_checkout) {
|
||||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
|
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false, commit: $commit);
|
||||||
}
|
}
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
||||||
|
|
@ -1347,7 +1352,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
|
||||||
if ($only_checkout) {
|
if ($only_checkout) {
|
||||||
$git_clone_command = $git_clone_command_base;
|
$git_clone_command = $git_clone_command_base;
|
||||||
} else {
|
} else {
|
||||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base);
|
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit);
|
||||||
}
|
}
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
|
|
@ -1405,7 +1410,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
|
||||||
$fullRepoUrl = $customRepository;
|
$fullRepoUrl = $customRepository;
|
||||||
$escapedCustomRepository = escapeshellarg($customRepository);
|
$escapedCustomRepository = escapeshellarg($customRepository);
|
||||||
$git_clone_command = "{$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
|
$git_clone_command = "{$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
|
||||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
|
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true, commit: $commit);
|
||||||
|
|
||||||
if ($pull_request_id !== 0) {
|
if ($pull_request_id !== 0) {
|
||||||
if ($git_type === 'gitlab') {
|
if ($git_type === 'gitlab') {
|
||||||
|
|
@ -1961,7 +1966,6 @@ public function parseHealthcheckFromDockerfile($dockerfile, bool $isInit = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getLimits(): array
|
public function getLimits(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use App\Traits\ClearsGlobalSearchCache;
|
use App\Traits\ClearsGlobalSearchCache;
|
||||||
use App\Traits\HasSafeStringAttribute;
|
use App\Traits\HasSafeStringAttribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
#[OA\Schema(
|
#[OA\Schema(
|
||||||
|
|
@ -21,6 +22,7 @@
|
||||||
class Environment extends BaseModel
|
class Environment extends BaseModel
|
||||||
{
|
{
|
||||||
use ClearsGlobalSearchCache;
|
use ClearsGlobalSearchCache;
|
||||||
|
use HasFactory;
|
||||||
use HasSafeStringAttribute;
|
use HasSafeStringAttribute;
|
||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use App\Traits\ClearsGlobalSearchCache;
|
use App\Traits\ClearsGlobalSearchCache;
|
||||||
use App\Traits\HasSafeStringAttribute;
|
use App\Traits\HasSafeStringAttribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
|
@ -20,6 +21,7 @@
|
||||||
class Project extends BaseModel
|
class Project extends BaseModel
|
||||||
{
|
{
|
||||||
use ClearsGlobalSearchCache;
|
use ClearsGlobalSearchCache;
|
||||||
|
use HasFactory;
|
||||||
use HasSafeStringAttribute;
|
use HasSafeStringAttribute;
|
||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Traits\HasSafeStringAttribute;
|
use App\Traits\HasSafeStringAttribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
|
|
@ -25,6 +26,7 @@
|
||||||
)]
|
)]
|
||||||
class ScheduledTask extends BaseModel
|
class ScheduledTask extends BaseModel
|
||||||
{
|
{
|
||||||
|
use HasFactory;
|
||||||
use HasSafeStringAttribute;
|
use HasSafeStringAttribute;
|
||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@
|
||||||
|
|
||||||
use App\Jobs\ConnectProxyToNetworksJob;
|
use App\Jobs\ConnectProxyToNetworksJob;
|
||||||
use App\Traits\HasSafeStringAttribute;
|
use App\Traits\HasSafeStringAttribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
class StandaloneDocker extends BaseModel
|
class StandaloneDocker extends BaseModel
|
||||||
{
|
{
|
||||||
|
use HasFactory;
|
||||||
use HasSafeStringAttribute;
|
use HasSafeStringAttribute;
|
||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
"stevebauman/purify": "^6.3.1",
|
"stevebauman/purify": "^6.3.1",
|
||||||
"stripe/stripe-php": "^16.6.0",
|
"stripe/stripe-php": "^16.6.0",
|
||||||
"symfony/yaml": "^7.4.1",
|
"symfony/yaml": "^7.4.1",
|
||||||
"visus/cuid2": "^4.1.0",
|
"visus/cuid2": "^6.0.0",
|
||||||
"yosymfony/toml": "^1.0.4",
|
"yosymfony/toml": "^1.0.4",
|
||||||
"zircote/swagger-php": "^5.8.0"
|
"zircote/swagger-php": "^5.8.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
1195
composer.lock
generated
1195
composer.lock
generated
File diff suppressed because it is too large
Load diff
16
database/factories/EnvironmentFactory.php
Normal file
16
database/factories/EnvironmentFactory.php
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class EnvironmentFactory extends Factory
|
||||||
|
{
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => fake()->unique()->word(),
|
||||||
|
'project_id' => 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
16
database/factories/ProjectFactory.php
Normal file
16
database/factories/ProjectFactory.php
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class ProjectFactory extends Factory
|
||||||
|
{
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => fake()->unique()->company(),
|
||||||
|
'team_id' => 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
19
database/factories/ServiceFactory.php
Normal file
19
database/factories/ServiceFactory.php
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class ServiceFactory extends Factory
|
||||||
|
{
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => fake()->unique()->word(),
|
||||||
|
'destination_type' => \App\Models\StandaloneDocker::class,
|
||||||
|
'destination_id' => 1,
|
||||||
|
'environment_id' => 1,
|
||||||
|
'docker_compose_raw' => 'version: "3"',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
18
database/factories/StandaloneDockerFactory.php
Normal file
18
database/factories/StandaloneDockerFactory.php
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class StandaloneDockerFactory extends Factory
|
||||||
|
{
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'uuid' => fake()->uuid(),
|
||||||
|
'name' => fake()->unique()->word(),
|
||||||
|
'network' => 'coolify',
|
||||||
|
'server_id' => 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
88
tests/Feature/ApplicationRollbackTest.php
Normal file
88
tests/Feature/ApplicationRollbackTest.php
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationSetting;
|
||||||
|
|
||||||
|
describe('Application Rollback', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->application = new Application;
|
||||||
|
$this->application->forceFill([
|
||||||
|
'uuid' => 'test-app-uuid',
|
||||||
|
'git_commit_sha' => 'HEAD',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$settings = new ApplicationSetting;
|
||||||
|
$settings->is_git_shallow_clone_enabled = false;
|
||||||
|
$settings->is_git_submodules_enabled = false;
|
||||||
|
$settings->is_git_lfs_enabled = false;
|
||||||
|
$this->application->setRelation('settings', $settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setGitImportSettings uses passed commit instead of application git_commit_sha', function () {
|
||||||
|
$rollbackCommit = 'abc123def456abc123def456abc123def456abc1';
|
||||||
|
|
||||||
|
$result = $this->application->setGitImportSettings(
|
||||||
|
deployment_uuid: 'test-uuid',
|
||||||
|
git_clone_command: 'git clone',
|
||||||
|
public: true,
|
||||||
|
commit: $rollbackCommit
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($result)->toContain($rollbackCommit);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setGitImportSettings with shallow clone fetches specific commit', function () {
|
||||||
|
$this->application->settings->is_git_shallow_clone_enabled = true;
|
||||||
|
|
||||||
|
$rollbackCommit = 'abc123def456abc123def456abc123def456abc1';
|
||||||
|
|
||||||
|
$result = $this->application->setGitImportSettings(
|
||||||
|
deployment_uuid: 'test-uuid',
|
||||||
|
git_clone_command: 'git clone',
|
||||||
|
public: true,
|
||||||
|
commit: $rollbackCommit
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($result)
|
||||||
|
->toContain('git fetch --depth=1 origin')
|
||||||
|
->toContain($rollbackCommit);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setGitImportSettings falls back to git_commit_sha when no commit passed', function () {
|
||||||
|
$this->application->git_commit_sha = 'def789abc012def789abc012def789abc012def7';
|
||||||
|
|
||||||
|
$result = $this->application->setGitImportSettings(
|
||||||
|
deployment_uuid: 'test-uuid',
|
||||||
|
git_clone_command: 'git clone',
|
||||||
|
public: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($result)->toContain('def789abc012def789abc012def789abc012def7');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setGitImportSettings escapes shell metacharacters in commit parameter', function () {
|
||||||
|
$maliciousCommit = 'abc123; rm -rf /';
|
||||||
|
|
||||||
|
$result = $this->application->setGitImportSettings(
|
||||||
|
deployment_uuid: 'test-uuid',
|
||||||
|
git_clone_command: 'git clone',
|
||||||
|
public: true,
|
||||||
|
commit: $maliciousCommit
|
||||||
|
);
|
||||||
|
|
||||||
|
// escapeshellarg wraps the value in single quotes, neutralizing metacharacters
|
||||||
|
expect($result)
|
||||||
|
->toContain("checkout 'abc123; rm -rf /'")
|
||||||
|
->not->toContain('checkout abc123; rm -rf /');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setGitImportSettings does not append checkout when commit is HEAD', function () {
|
||||||
|
$result = $this->application->setGitImportSettings(
|
||||||
|
deployment_uuid: 'test-uuid',
|
||||||
|
git_clone_command: 'git clone',
|
||||||
|
public: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($result)->not->toContain('advice.detachedHead=false checkout');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Environment;
|
use App\Models\Environment;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
use App\Models\ScheduledTaskExecution;
|
use App\Models\ScheduledTaskExecution;
|
||||||
|
|
@ -15,6 +16,9 @@
|
||||||
uses(RefreshDatabase::class);
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
// ApiAllowed middleware requires InstanceSettings with id=0
|
||||||
|
InstanceSettings::create(['id' => 0, 'is_api_enabled' => true]);
|
||||||
|
|
||||||
$this->team = Team::factory()->create();
|
$this->team = Team::factory()->create();
|
||||||
$this->user = User::factory()->create();
|
$this->user = User::factory()->create();
|
||||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||||
|
|
@ -25,12 +29,14 @@
|
||||||
$this->bearerToken = $this->token->plainTextToken;
|
$this->bearerToken = $this->token->plainTextToken;
|
||||||
|
|
||||||
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||||
$this->destination = StandaloneDocker::factory()->create(['server_id' => $this->server->id]);
|
// Server::booted() auto-creates a StandaloneDocker, reuse it
|
||||||
|
$this->destination = StandaloneDocker::where('server_id', $this->server->id)->first();
|
||||||
|
// Project::booted() auto-creates a 'production' Environment, reuse it
|
||||||
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||||
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
$this->environment = $this->project->environments()->first();
|
||||||
});
|
});
|
||||||
|
|
||||||
function authHeaders($bearerToken): array
|
function scheduledTaskAuthHeaders($bearerToken): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'Authorization' => 'Bearer '.$bearerToken,
|
'Authorization' => 'Bearer '.$bearerToken,
|
||||||
|
|
@ -46,7 +52,7 @@ function authHeaders($bearerToken): array
|
||||||
'destination_type' => $this->destination->getMorphClass(),
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks");
|
->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks");
|
||||||
|
|
||||||
$response->assertStatus(200);
|
$response->assertStatus(200);
|
||||||
|
|
@ -66,7 +72,7 @@ function authHeaders($bearerToken): array
|
||||||
'name' => 'Test Task',
|
'name' => 'Test Task',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks");
|
->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks");
|
||||||
|
|
||||||
$response->assertStatus(200);
|
$response->assertStatus(200);
|
||||||
|
|
@ -75,7 +81,7 @@ function authHeaders($bearerToken): array
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns 404 for unknown application uuid', function () {
|
test('returns 404 for unknown application uuid', function () {
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->getJson('/api/v1/applications/nonexistent-uuid/scheduled-tasks');
|
->getJson('/api/v1/applications/nonexistent-uuid/scheduled-tasks');
|
||||||
|
|
||||||
$response->assertStatus(404);
|
$response->assertStatus(404);
|
||||||
|
|
@ -90,7 +96,7 @@ function authHeaders($bearerToken): array
|
||||||
'destination_type' => $this->destination->getMorphClass(),
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [
|
->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [
|
||||||
'name' => 'Backup',
|
'name' => 'Backup',
|
||||||
'command' => 'php artisan backup',
|
'command' => 'php artisan backup',
|
||||||
|
|
@ -116,7 +122,7 @@ function authHeaders($bearerToken): array
|
||||||
'destination_type' => $this->destination->getMorphClass(),
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [
|
->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [
|
||||||
'command' => 'echo test',
|
'command' => 'echo test',
|
||||||
'frequency' => '* * * * *',
|
'frequency' => '* * * * *',
|
||||||
|
|
@ -132,7 +138,7 @@ function authHeaders($bearerToken): array
|
||||||
'destination_type' => $this->destination->getMorphClass(),
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [
|
->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [
|
||||||
'name' => 'Test',
|
'name' => 'Test',
|
||||||
'command' => 'echo test',
|
'command' => 'echo test',
|
||||||
|
|
@ -150,7 +156,7 @@ function authHeaders($bearerToken): array
|
||||||
'destination_type' => $this->destination->getMorphClass(),
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [
|
->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [
|
||||||
'name' => 'Test',
|
'name' => 'Test',
|
||||||
'command' => 'echo test',
|
'command' => 'echo test',
|
||||||
|
|
@ -168,7 +174,7 @@ function authHeaders($bearerToken): array
|
||||||
'destination_type' => $this->destination->getMorphClass(),
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [
|
->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [
|
||||||
'name' => 'Test',
|
'name' => 'Test',
|
||||||
'command' => 'echo test',
|
'command' => 'echo test',
|
||||||
|
|
@ -199,7 +205,7 @@ function authHeaders($bearerToken): array
|
||||||
'name' => 'Old Name',
|
'name' => 'Old Name',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->patchJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/{$task->uuid}", [
|
->patchJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/{$task->uuid}", [
|
||||||
'name' => 'New Name',
|
'name' => 'New Name',
|
||||||
]);
|
]);
|
||||||
|
|
@ -215,7 +221,7 @@ function authHeaders($bearerToken): array
|
||||||
'destination_type' => $this->destination->getMorphClass(),
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->patchJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/nonexistent", [
|
->patchJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/nonexistent", [
|
||||||
'name' => 'Test',
|
'name' => 'Test',
|
||||||
]);
|
]);
|
||||||
|
|
@ -237,7 +243,7 @@ function authHeaders($bearerToken): array
|
||||||
'team_id' => $this->team->id,
|
'team_id' => $this->team->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->deleteJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/{$task->uuid}");
|
->deleteJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/{$task->uuid}");
|
||||||
|
|
||||||
$response->assertStatus(200);
|
$response->assertStatus(200);
|
||||||
|
|
@ -253,7 +259,7 @@ function authHeaders($bearerToken): array
|
||||||
'destination_type' => $this->destination->getMorphClass(),
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->deleteJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/nonexistent");
|
->deleteJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/nonexistent");
|
||||||
|
|
||||||
$response->assertStatus(404);
|
$response->assertStatus(404);
|
||||||
|
|
@ -279,7 +285,7 @@ function authHeaders($bearerToken): array
|
||||||
'message' => 'OK',
|
'message' => 'OK',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/{$task->uuid}/executions");
|
->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/{$task->uuid}/executions");
|
||||||
|
|
||||||
$response->assertStatus(200);
|
$response->assertStatus(200);
|
||||||
|
|
@ -294,7 +300,7 @@ function authHeaders($bearerToken): array
|
||||||
'destination_type' => $this->destination->getMorphClass(),
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/nonexistent/executions");
|
->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/nonexistent/executions");
|
||||||
|
|
||||||
$response->assertStatus(404);
|
$response->assertStatus(404);
|
||||||
|
|
@ -316,7 +322,7 @@ function authHeaders($bearerToken): array
|
||||||
'name' => 'Service Task',
|
'name' => 'Service Task',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->getJson("/api/v1/services/{$service->uuid}/scheduled-tasks");
|
->getJson("/api/v1/services/{$service->uuid}/scheduled-tasks");
|
||||||
|
|
||||||
$response->assertStatus(200);
|
$response->assertStatus(200);
|
||||||
|
|
@ -332,7 +338,7 @@ function authHeaders($bearerToken): array
|
||||||
'environment_id' => $this->environment->id,
|
'environment_id' => $this->environment->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->postJson("/api/v1/services/{$service->uuid}/scheduled-tasks", [
|
->postJson("/api/v1/services/{$service->uuid}/scheduled-tasks", [
|
||||||
'name' => 'Service Backup',
|
'name' => 'Service Backup',
|
||||||
'command' => 'pg_dump',
|
'command' => 'pg_dump',
|
||||||
|
|
@ -356,7 +362,7 @@ function authHeaders($bearerToken): array
|
||||||
'team_id' => $this->team->id,
|
'team_id' => $this->team->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $this->withHeaders(authHeaders($this->bearerToken))
|
$response = $this->withHeaders(scheduledTaskAuthHeaders($this->bearerToken))
|
||||||
->deleteJson("/api/v1/services/{$service->uuid}/scheduled-tasks/{$task->uuid}");
|
->deleteJson("/api/v1/services/{$service->uuid}/scheduled-tasks/{$task->uuid}");
|
||||||
|
|
||||||
$response->assertStatus(200);
|
$response->assertStatus(200);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use Mockery;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test to verify docker_compose_raw is properly synced to the Livewire component
|
* Unit test to verify docker_compose_raw is properly synced to the Livewire component
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Mockery;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
// Clean up Mockery after each test
|
// Clean up Mockery after each test
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use Mockery;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests to verify that containers without health checks are not
|
* Unit tests to verify that containers without health checks are not
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\ApplicationSetting;
|
use App\Models\ApplicationSetting;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Mockery;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
Mockery::close();
|
Mockery::close();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Notifications\Server\HetznerDeletionFailed;
|
use App\Notifications\Server\HetznerDeletionFailed;
|
||||||
use Mockery;
|
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
Mockery::close();
|
Mockery::close();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Queue;
|
use Illuminate\Support\Facades\Queue;
|
||||||
use Mockery;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
Queue::fake();
|
Queue::fake();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Mockery;
|
|
||||||
|
|
||||||
it('filters servers by proxy type using whereProxyType scope', function () {
|
it('filters servers by proxy type using whereProxyType scope', function () {
|
||||||
// Mock the Builder
|
// Mock the Builder
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use Mockery;
|
|
||||||
|
|
||||||
it('returns required port from service template', function () {
|
it('returns required port from service template', function () {
|
||||||
// Mock get_service_templates() function
|
// Mock get_service_templates() function
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue