Files
coolify/tests/Unit/TraefikAcmeServiceTest.php
T
Andras Bacsai 554b79e8dd feat: add Traefik ACME cert UI and shared managed DNS record ownership
- Proxy: list and delete Traefik ACME certificates from the server proxy
  page via new TraefikAcmeService and Get/DeleteTraefikCertificate actions
- DNS: track ownership and cross-resource references for managed DNS
  records so records are only deleted when no longer referenced; release
  records asynchronously on resource deletion via ReleaseManagedDnsRecordsJob
  and ManagedDnsRecordCleanup; harden Cloudflare provider deletion results
- Databases: fail closed on start when prerequisites or the CA certificate
  are missing (DatabaseStartException, Server::ensureCaCertificate) and
  clean up stale start activities via ResourceStartActivity
- Webhooks: throttle repeated manual webhook signature failures for
  GitHub, GitLab, Gitea and Bitbucket
- Deployments: improve compose build-context handling and compose file
  load error reporting
- Install scripts: rework terminal UI output in install.sh (stable and
  nightly)
- Misc: settings sidebar accordion fixes, log drain toggle rollback,
  add Serverside to README sponsors
- Add migrations and tests covering the above
2026-09-25 19:00:00 +02:00

76 lines
2.9 KiB
PHP

<?php
use App\Services\TraefikAcmeService;
function traefikAcmeFixture(): string
{
return json_encode([
'letsencrypt' => [
'Account' => ['Email' => 'admin@example.com'],
'Certificates' => [
[
'domain' => [
'main' => 'example.com',
'sans' => ['www.example.com', '*.example.com'],
],
'certificate' => base64_encode('first-certificate'),
'key' => base64_encode('first-key'),
'Store' => 'default',
],
[
'domain' => ['main' => 'api.example.net'],
'certificate' => base64_encode('second-certificate'),
'key' => base64_encode('second-key'),
],
],
],
'zerossl' => [
'Account' => ['Email' => 'admin@example.org'],
'Certificates' => [[
'domain' => ['main' => 'example.org', 'sans' => []],
'certificate' => base64_encode('third-certificate'),
'key' => base64_encode('third-key'),
]],
],
], JSON_THROW_ON_ERROR);
}
it('lists every certificate from every ACME resolver without exposing secrets', function () {
$certificates = app(TraefikAcmeService::class)->certificates(traefikAcmeFixture());
expect($certificates)->toHaveCount(3)
->and($certificates[0])->toMatchArray([
'resolver' => 'letsencrypt',
'main_domain' => 'example.com',
'sans' => ['www.example.com', '*.example.com'],
'store' => 'default',
])
->and($certificates[0])->toHaveKeys(['id', 'expires_at'])
->and($certificates[0])->not->toHaveKeys(['certificate', 'key', 'account']);
});
it('removes only the selected certificate and preserves all other ACME data', function () {
$service = app(TraefikAcmeService::class);
$certificates = $service->certificates(traefikAcmeFixture());
$updated = json_decode($service->deleteCertificate(
traefikAcmeFixture(),
'letsencrypt',
$certificates[0]['id'],
), true, flags: JSON_THROW_ON_ERROR);
expect($updated['letsencrypt']['Account']['Email'])->toBe('admin@example.com')
->and($updated['letsencrypt']['Certificates'])->toHaveCount(1)
->and($updated['letsencrypt']['Certificates'][0]['domain']['main'])->toBe('api.example.net')
->and($updated['zerossl']['Certificates'])->toHaveCount(1);
});
it('rejects invalid files and unknown certificate identifiers', function () {
$service = app(TraefikAcmeService::class);
expect(fn () => $service->certificates('{invalid'))
->toThrow(RuntimeException::class, 'invalid JSON')
->and(fn () => $service->deleteCertificate(traefikAcmeFixture(), 'letsencrypt', 'missing'))
->toThrow(RuntimeException::class, 'could not be found');
});