test: consolidate webhook repository URL tests and cover compose location save

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2026-10-16 22:41:15 +02:00
co-authored by Claude Opus 5.5
parent 3aa6ff1fdd
commit 35d4888c1a
3 changed files with 60 additions and 128 deletions
+1
View File
@@ -17,6 +17,7 @@
- Never run `php artisan test` inside a dev container (`./scripts/dev exec` or `docker exec coolify-dev-*`). The container sets `DB_CONNECTION=pgsql` as a real environment variable, which wins over `phpunit.xml`, so `RefreshDatabase` runs `migrate:fresh` on the dev database and wipes it. Run tests on the host only.
- The helper image runs `docker compose` for deployments and for the Coolify upgrade itself. Before you bump its Docker CLI or Compose, test them against the oldest supported Docker Engine (for example a `docker:<version>-dind` container); newer clients can reject data from older daemons.
- Do not symlink `vendor` into another worktree. Composer resolves the base path from the real `vendor` path, so the tests load the app classes of the other checkout. Run `composer install --ignore-platform-req=ext-zip` in the worktree.
- To assert that the unsaved-changes bar shows, check `el.classList.contains('is-dirty')`. Playwright treats the hidden bar (`opacity-0`) as visible, and its class attribute always contains the text `is-dirty` from Tailwind variants.
- Call `visit()` directly in each `tests/v4/Browser` test body; Pest does not mark a test that only uses helper-wrapped `visit()` as a browser test, so it fails with `sendText() on null`.
## Verify the complete user flow
+22 -128
View File
@@ -917,139 +917,33 @@ describe('Manual Webhook Repository Matching', function () {
],
]);
test('github matches ssh git repository URL exactly', function () {
$app = createApplicationWithWebhook(overrides: [
'git_repository' => 'git@github.com:test-org/test-repo.git',
]);
$secret = $app->manual_webhook_secret_github;
test('signed pushes deploy applications whose repository URL names the pushed repository', function (string $provider, string $gitRepository) {
Queue::fake();
$application = makeWebhookApplicationServerFunctional(createApplicationWithWebhook(overrides: [
'git_repository' => $gitRepository,
]));
$payload = json_encode([
'ref' => 'refs/heads/main',
'repository' => ['full_name' => 'test-org/test-repo'],
'after' => 'abc1234',
'commits' => [],
]);
sendManualWebhookPush($this, $provider, $application)->assertOk();
$response = $this->call('POST', '/webhooks/source/github/events/manual', [], [], [], [
'HTTP_X-GitHub-Event' => 'push',
'HTTP_X-Hub-Signature-256' => 'sha256='.hash_hmac('sha256', $payload, $secret),
'CONTENT_TYPE' => 'application/json',
], $payload);
expect(ApplicationDeploymentQueue::query()->where('application_id', $application->id)->exists())->toBeTrue();
})->with(['github', 'gitlab', 'bitbucket', 'gitea'])->with([
'scp-style' => 'git@git.example.com:test-org/test-repo.git',
'scp-style with custom port' => 'git@10.0.0.80:2222/test-org/test-repo.git',
'scp-style with custom user and port' => 'custom-user@git.example.com:2222/test-org/test-repo.git',
'ssh scheme with custom port' => 'ssh://git@10.0.0.80:2222/test-org/test-repo.git',
'https with different case' => 'https://github.com/Test-Org/Test-Repo.git',
]);
$response->assertOk();
expect($response->getContent())->not->toContain('No applications found');
});
test('signed pushes do not deploy applications for another repository', function (string $provider) {
Queue::fake();
$application = makeWebhookApplicationServerFunctional(createApplicationWithWebhook(overrides: [
'git_repository' => 'git@10.0.0.80:2222/other-org/test-repo.git',
]));
test('github matches an ssh repository URL with a non-git username', function () {
$app = createApplicationWithWebhook(overrides: [
'git_repository' => 'custom-user@git.example.com:test-org/test-repo.git',
]);
$secret = $app->manual_webhook_secret_github;
sendManualWebhookPush($this, $provider, $application)->assertOk();
$payload = json_encode([
'ref' => 'refs/heads/main',
'repository' => ['full_name' => 'test-org/test-repo'],
'after' => 'abc1234',
'commits' => [],
]);
$response = $this->call('POST', '/webhooks/source/github/events/manual', [], [], [], [
'HTTP_X-GitHub-Event' => 'push',
'HTTP_X-Hub-Signature-256' => 'sha256='.hash_hmac('sha256', $payload, $secret),
'CONTENT_TYPE' => 'application/json',
], $payload);
$response->assertOk();
expect($response->getContent())->not->toContain('No applications found');
});
test('github matches an ssh repository URL with a non-git username and custom port', function () {
$app = createApplicationWithWebhook(overrides: [
'git_repository' => 'custom-user@git.example.com:2222/test-org/test-repo.git',
]);
$secret = $app->manual_webhook_secret_github;
$payload = json_encode([
'ref' => 'refs/heads/main',
'repository' => ['full_name' => 'test-org/test-repo'],
'after' => 'abc1234',
'commits' => [],
]);
$response = $this->call('POST', '/webhooks/source/github/events/manual', [], [], [], [
'HTTP_X-GitHub-Event' => 'push',
'HTTP_X-Hub-Signature-256' => 'sha256='.hash_hmac('sha256', $payload, $secret),
'CONTENT_TYPE' => 'application/json',
], $payload);
$response->assertOk();
expect($response->getContent())->not->toContain('No applications found');
});
test('gitlab matches scp-style ssh repository URL with custom port', function () {
$app = createApplicationWithWebhook(overrides: [
'git_repository' => 'git@gitlab.example.com:2222/services/xyz.git',
'git_branch' => 'master',
]);
$secret = $app->manual_webhook_secret_gitlab;
$response = $this->postJson('/webhooks/source/gitlab/events/manual', [
'object_kind' => 'push',
'ref' => 'refs/heads/master',
'project' => ['path_with_namespace' => 'services/xyz'],
'after' => 'abc1234',
'commits' => [],
], [
'X-Gitlab-Token' => $secret,
]);
$response->assertOk();
expect($response->getContent())->not->toContain('No applications found');
});
test('gitlab matches scp-style ssh repository URL without port', function () {
$app = createApplicationWithWebhook(overrides: [
'git_repository' => 'git@gitlab.example.com:services/xyz.git',
'git_branch' => 'master',
]);
$secret = $app->manual_webhook_secret_gitlab;
$response = $this->postJson('/webhooks/source/gitlab/events/manual', [
'object_kind' => 'push',
'ref' => 'refs/heads/master',
'project' => ['path_with_namespace' => 'services/xyz'],
'after' => 'abc1234',
'commits' => [],
], [
'X-Gitlab-Token' => $secret,
]);
$response->assertOk();
expect($response->getContent())->not->toContain('No applications found');
});
test('github matches repository case-insensitively', function () {
$app = createApplicationWithWebhook(overrides: [
'git_repository' => 'https://github.com/Test-Org/Test-Repo.git',
]);
$secret = $app->manual_webhook_secret_github;
$payload = json_encode([
'ref' => 'refs/heads/main',
'repository' => ['full_name' => 'test-org/test-repo'],
'after' => 'abc1234',
'commits' => [],
]);
$response = $this->call('POST', '/webhooks/source/github/events/manual', [], [], [], [
'HTTP_X-GitHub-Event' => 'push',
'HTTP_X-Hub-Signature-256' => 'sha256='.hash_hmac('sha256', $payload, $secret),
'CONTENT_TYPE' => 'application/json',
], $payload);
$response->assertOk();
expect($response->getContent())->not->toContain('No applications found');
});
expect(ApplicationDeploymentQueue::query()->where('application_id', $application->id)->exists())->toBeFalse();
})->with(['github', 'gitlab', 'bitbucket', 'gitea']);
});
describe('Webhook Secret Auto-Generation', function () {
@@ -5,6 +5,7 @@ use App\Models\InstanceSettings;
use App\Models\LocalPersistentVolume;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Process;
use Visus\Cuid2\Cuid2;
uses(RefreshDatabase::class);
@@ -135,6 +136,42 @@ it('saves application name description and ports from the general form', functio
->screenshot(filename: 'application-general-reloaded');
});
it('shows the unsaved bar for a Compose location edit and saves it with the name', function () {
Process::fake(fn ($process) => match (true) {
str_contains($process->command, 'git --version') => Process::result('git version 2.43.0'),
str_contains($process->command, 'head -c') => Process::result("services:\n web:\n image: nginx\n"),
default => Process::result(''),
});
$this->application->update([
'build_pack' => 'dockercompose',
'docker_compose_location' => '/docker-compose.yaml',
'docker_compose_raw' => "services:\n web:\n image: nginx\n",
]);
loginAndSkipBoarding();
$page = visit(applicationConfigurationUrl(
$this->stack['project'],
$this->stack['environment'],
$this->application
));
// The Compose location input is bound through @entangle, not wire:model.
$page->assertSee('Docker compose location')
->type('input[placeholder="/docker-compose.yaml"]', '/compose.prod.yaml')
->assertScript(<<<'JS'
() => [...document.querySelectorAll('.is-dirty')].some((bar) => bar.textContent.includes('Save changes'))
JS)
->type('name', 'Renamed Compose App')
->click('button:visible:has-text("Save changes")')
->wait(2)
->screenshot(filename: 'application-compose-location-saved');
expect($this->application->refresh()->only(['name', 'docker_compose_location']))->toBe([
'name' => 'Renamed Compose App',
'docker_compose_location' => '/compose.prod.yaml',
]);
});
it('saves custom docker run options from the UI', function () {
loginAndSkipBoarding();