diff --git a/.ai/lessons.md b/.ai/lessons.md index b4d51f943..89dce29f4 100644 --- a/.ai/lessons.md +++ b/.ai/lessons.md @@ -9,3 +9,6 @@ ## Alpine x-transition + tw-animate-css exit animations flash at the end ## Displayed defaults must not become stored overrides - When an edit form shows an inherited or computed default, trace an unchanged save and a related-field edit through persistence. - Preserve the inherited state when the displayed value still equals the computed default; store an override only when the user selects a different value. + +## Prove regressions against the unchanged baseline +- For a bug fix, run the same regression test before and after the production change. Use a stash when requested so the failure and success come from the exact same test. diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index f260e619a..6d42398c2 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2403,15 +2403,20 @@ private function set_coolify_variables() $fqdn = $this->preview->fqdn; } if (isset($fqdn)) { - $domains = str($fqdn)->explode(',')->map(fn (string $domain) => trim($domain))->filter(); - $url = $domains->map(fn (string $domain) => Url::fromString($domain)->withPort(null)->__toString())->implode(','); - $fqdn = $domains->map(fn (string $domain) => Url::fromString($domain)->getHost())->implode(','); - if ((int) $this->application->compose_parsing_version >= 3) { - $this->coolify_variables .= 'COOLIFY_URL='.escapeShellValue($url).' '; - $this->coolify_variables .= 'COOLIFY_FQDN='.escapeShellValue($fqdn).' '; - } else { - $this->coolify_variables .= 'COOLIFY_URL='.escapeShellValue($fqdn).' '; - $this->coolify_variables .= 'COOLIFY_FQDN='.escapeShellValue($url).' '; + $domains = str($fqdn)->explode(',') + ->map(fn (string $domain) => trim($domain)) + ->filter() + ->filter(fn (string $domain) => isValidDomainUrl($domain)); + if ($domains->isNotEmpty()) { + $url = $domains->map(fn (string $domain) => Url::fromString($domain)->withPort(null)->__toString())->implode(','); + $fqdn = $domains->map(fn (string $domain) => Url::fromString($domain)->getHost())->implode(','); + if ((int) $this->application->compose_parsing_version >= 3) { + $this->coolify_variables .= 'COOLIFY_URL='.escapeShellValue($url).' '; + $this->coolify_variables .= 'COOLIFY_FQDN='.escapeShellValue($fqdn).' '; + } else { + $this->coolify_variables .= 'COOLIFY_URL='.escapeShellValue($fqdn).' '; + $this->coolify_variables .= 'COOLIFY_FQDN='.escapeShellValue($url).' '; + } } } if (isset($this->application->git_branch)) { diff --git a/app/Support/DomainPortOverrides.php b/app/Support/DomainPortOverrides.php index 320540ad6..853a6f339 100644 --- a/app/Support/DomainPortOverrides.php +++ b/app/Support/DomainPortOverrides.php @@ -34,6 +34,7 @@ public static function normalize(?string $fqdn, ?array $existing): array $normalizedDomains = collect(explode(',', $fqdn)) ->map(fn (string $domain): string => trim($domain)) ->filter() + ->filter(fn (string $domain): bool => isValidDomainUrl($domain)) ->map(function (string $domain) use ($existingOverrides): array { $portlessDomain = self::withoutPort($domain); $parts = DomainUrlParts::split($domain); diff --git a/tests/Unit/ApplicationDeploymentCoolifyUrlDomainsTest.php b/tests/Unit/ApplicationDeploymentCoolifyUrlDomainsTest.php index f6684718e..8fb8e0bd6 100644 --- a/tests/Unit/ApplicationDeploymentCoolifyUrlDomainsTest.php +++ b/tests/Unit/ApplicationDeploymentCoolifyUrlDomainsTest.php @@ -44,6 +44,7 @@ function coolifyVariablesForFqdn(string $fqdn, string $composeParsingVersion = ' // The created hook resets this, so it has to be set afterwards. $application->compose_parsing_version = $composeParsingVersion; $application->save(); + Application::withoutGlobalScopes()->whereKey($application->id)->update(['fqdn' => $fqdn]); $job = new TestableCoolifyUrlDeploymentJob; $reflection = new ReflectionClass(ApplicationDeploymentJob::class); @@ -92,3 +93,19 @@ function coolifyVariablesForFqdn(string $fqdn, string $composeParsingVersion = ' ->toContain("COOLIFY_URL='https://a.example.com'") ->toContain("COOLIFY_FQDN='a.example.com'"); }); + +it('ignores a hostless stored domain while setting deployment variables', function () { + $variables = coolifyVariablesForFqdn('https://,https://a.example.com'); + + expect($variables) + ->toContain("COOLIFY_URL='https://a.example.com'") + ->toContain("COOLIFY_FQDN='a.example.com'"); +}); + +it('does not set domain variables for a hostless stored fqdn', function () { + $variables = coolifyVariablesForFqdn('https://'); + + expect($variables) + ->not->toContain('COOLIFY_URL=') + ->not->toContain('COOLIFY_FQDN='); +}); diff --git a/tests/Unit/DomainPortOverridesTest.php b/tests/Unit/DomainPortOverridesTest.php index 42345e5df..9753b2d1f 100644 --- a/tests/Unit/DomainPortOverridesTest.php +++ b/tests/Unit/DomainPortOverridesTest.php @@ -34,3 +34,25 @@ 'www redirect' => ['https://example.com', 'https://www.example.com'], 'non-www redirect' => ['https://www.example.com', 'https://example.com'], ]); + +it('drops hostless domains and their port overrides', function () { + $result = DomainPortOverrides::normalize( + 'https://,https://example.com', + [ + 'https://' => 3000, + 'https://example.com' => 8080, + ], + ); + + expect($result)->toBe([ + 'fqdn' => 'https://example.com', + 'overrides' => ['https://example.com' => 8080], + ]); +}); + +it('clears an fqdn that contains only a hostless domain', function () { + expect(DomainPortOverrides::normalize('https://', ['https://' => 3000]))->toBe([ + 'fqdn' => null, + 'overrides' => null, + ]); +});