From 582bdc3739a7a166af149abcec1c7d66dc8068a1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:35:54 +0100 Subject: [PATCH] test: Add comprehensive preview deployment port and path tests Add missing edge case test for root path (/) and expand test coverage for preview FQDN generation. Tests verify that ports and paths are correctly preserved in preview URLs while excluding root paths. Fixes #2184 --- app/Models/ApplicationPreview.php | 14 +-- tests/Unit/PreviewDeploymentPortTest.php | 126 +++++++++++++++++++++++ 2 files changed, 134 insertions(+), 6 deletions(-) diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php index 04ce6274a..7373fdb16 100644 --- a/app/Models/ApplicationPreview.php +++ b/app/Models/ApplicationPreview.php @@ -77,21 +77,21 @@ public function generate_preview_fqdn() if ($this->application->fqdn) { if (str($this->application->fqdn)->contains(',')) { $url = Url::fromString(str($this->application->fqdn)->explode(',')[0]); - $preview_fqdn = getFqdnWithoutPort(str($this->application->fqdn)->explode(',')[0]); } else { $url = Url::fromString($this->application->fqdn); - if ($this->fqdn) { - $preview_fqdn = getFqdnWithoutPort($this->fqdn); - } } $template = $this->application->preview_url_template; $host = $url->getHost(); $schema = $url->getScheme(); + $portInt = $url->getPort(); + $port = $portInt !== null ? ':'.$portInt : ''; + $urlPath = $url->getPath(); + $path = ($urlPath !== '' && $urlPath !== '/') ? $urlPath : ''; $random = new Cuid2; $preview_fqdn = str_replace('{{random}}', $random, $template); $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); $preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn); - $preview_fqdn = "$schema://$preview_fqdn"; + $preview_fqdn = "$schema://$preview_fqdn{$port}{$path}"; $this->fqdn = $preview_fqdn; $this->save(); } @@ -147,11 +147,13 @@ public function generate_preview_fqdn_compose() $schema = $url->getScheme(); $portInt = $url->getPort(); $port = $portInt !== null ? ':'.$portInt : ''; + $urlPath = $url->getPath(); + $path = ($urlPath !== '' && $urlPath !== '/') ? $urlPath : ''; $random = new Cuid2; $preview_fqdn = str_replace('{{random}}', $random, $template); $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); $preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn); - $preview_fqdn = "$schema://$preview_fqdn{$port}"; + $preview_fqdn = "$schema://$preview_fqdn{$port}{$path}"; $preview_domains[] = $preview_fqdn; } diff --git a/tests/Unit/PreviewDeploymentPortTest.php b/tests/Unit/PreviewDeploymentPortTest.php index 0dae6789a..beaac86cc 100644 --- a/tests/Unit/PreviewDeploymentPortTest.php +++ b/tests/Unit/PreviewDeploymentPortTest.php @@ -133,3 +133,129 @@ expect($port)->toBe($case['expected']); } }); + +// Tests for path preservation in preview URLs +// @see https://github.com/coollabsio/coolify/issues/2184#issuecomment-3638971221 + +it('generates preview FQDN with port and path preserved', function () { + $domain = 'https://api.example.com:3000/api/v1'; + $url = Url::fromString($domain); + $template = '{{pr_id}}.{{domain}}'; + $pullRequestId = 42; + + $host = $url->getHost(); + $schema = $url->getScheme(); + $portInt = $url->getPort(); + $port = $portInt !== null ? ':'.$portInt : ''; + $urlPath = $url->getPath(); + $path = ($urlPath !== '' && $urlPath !== '/') ? $urlPath : ''; + + $preview_fqdn = str_replace('{{random}}', 'abc123', $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn{$port}{$path}"; + + expect($preview_fqdn)->toBe('https://42.api.example.com:3000/api/v1'); +}); + +it('generates preview FQDN with path only (no port)', function () { + $domain = 'https://api.example.com/api'; + $url = Url::fromString($domain); + $template = '{{pr_id}}.{{domain}}'; + $pullRequestId = 99; + + $host = $url->getHost(); + $schema = $url->getScheme(); + $portInt = $url->getPort(); + $port = $portInt !== null ? ':'.$portInt : ''; + $urlPath = $url->getPath(); + $path = ($urlPath !== '' && $urlPath !== '/') ? $urlPath : ''; + + $preview_fqdn = str_replace('{{random}}', 'abc123', $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn{$port}{$path}"; + + expect($preview_fqdn)->toBe('https://99.api.example.com/api'); +}); + +it('handles multiple domains with different ports and paths', function () { + $domains = [ + 'https://app.example.com:3000/dashboard', + 'https://api.example.com:8080/api/v1', + 'https://web.example.com/admin', + 'https://static.example.com', + ]; + + $preview_fqdns = []; + $template = 'pr-{{pr_id}}.{{domain}}'; + $pullRequestId = 123; + + foreach ($domains as $domain) { + $url = Url::fromString($domain); + $host = $url->getHost(); + $schema = $url->getScheme(); + $portInt = $url->getPort(); + $port = $portInt !== null ? ':'.$portInt : ''; + $urlPath = $url->getPath(); + $path = ($urlPath !== '' && $urlPath !== '/') ? $urlPath : ''; + + $preview_fqdn = str_replace('{{random}}', 'xyz', $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn{$port}{$path}"; + $preview_fqdns[] = $preview_fqdn; + } + + expect($preview_fqdns[0])->toBe('https://pr-123.app.example.com:3000/dashboard'); + expect($preview_fqdns[1])->toBe('https://pr-123.api.example.com:8080/api/v1'); + expect($preview_fqdns[2])->toBe('https://pr-123.web.example.com/admin'); + expect($preview_fqdns[3])->toBe('https://pr-123.static.example.com'); +}); + +it('preserves trailing slash in URL path', function () { + $domain = 'https://api.example.com/api/'; + $url = Url::fromString($domain); + $template = '{{pr_id}}.{{domain}}'; + $pullRequestId = 55; + + $host = $url->getHost(); + $schema = $url->getScheme(); + $portInt = $url->getPort(); + $port = $portInt !== null ? ':'.$portInt : ''; + $urlPath = $url->getPath(); + $path = ($urlPath !== '' && $urlPath !== '/') ? $urlPath : ''; + + $preview_fqdn = str_replace('{{random}}', 'abc123', $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn{$port}{$path}"; + + // Trailing slash is preserved: /api/ stays as /api/ + expect($preview_fqdn)->toBe('https://55.api.example.com/api/'); +}); + +it('excludes root path from preview FQDN', function () { + // Edge case: URL with explicit root path "/" should NOT append the slash + $domain = 'https://example.com/'; + $url = Url::fromString($domain); + $template = '{{pr_id}}.{{domain}}'; + $pullRequestId = 42; + + $host = $url->getHost(); + $schema = $url->getScheme(); + $portInt = $url->getPort(); + $port = $portInt !== null ? ':'.$portInt : ''; + $urlPath = $url->getPath(); + $path = ($urlPath !== '' && $urlPath !== '/') ? $urlPath : ''; + + $preview_fqdn = str_replace('{{random}}', 'abc123', $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn{$port}{$path}"; + + // Root path "/" should be excluded, resulting in no trailing slash + expect($urlPath)->toBe('/'); + expect($path)->toBe(''); + expect($preview_fqdn)->toBe('https://42.example.com'); +});