internal/caddylogs/hub_ari_test.go
Rama main · réplica de lectura del repositorio original.
// SPDX-License-Identifier: Apache-2.0
package caddylogs
import (
"path/filepath"
"testing"
appdb "github.com/X4Applegate/caddyui/internal/db"
"github.com/X4Applegate/caddyui/internal/models"
)
// Issue #151: a failed ACME renewal-information (ARI) update left the
// certificate stuck on "Issuance error" forever. Caddy recovers by logging a
// successful ARI update, which issues no certificate and therefore emits no
// cert_obtained event, so nothing cleared the stored failure.
func TestHubClearsErrorAfterSuccessfulRenewalInfoUpdate(t *testing.T) {
conn, err := appdb.Open(filepath.Join(t.TempDir(), "caddyui.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = conn.Close() })
hub := New(conn)
// The reporter's failure: a transient DNS outage while updating ARI.
hub.AcceptLine([]byte(`{"level":"error","ts":1760000000,"logger":"tls.cache.maintenance","msg":"failed updating renewal info from ACME CA","identifiers":["*.example.com"],"error":"provisioning client: performing request: Get \"https://acme-v02.api.letsencrypt.org/directory\": dial tcp: lookup acme-v02.api.letsencrypt.org on 127.0.0.11:53: server misbehaving","caddyui_server_id":3,"caddyui_server_name":"edge"}`))
states, err := models.ListCertificateLifecycle(conn, 3)
if err != nil {
t.Fatal(err)
}
if len(states) != 1 || states[0].Phase != "error" || states[0].Identifier != "*.example.com" {
t.Fatalf("ARI failure state = %#v, want error for *.example.com", states)
}
// Two weeks later Caddy updates the renewal information successfully. The
// certificate is valid and served, so the stored error must clear.
hub.AcceptLine([]byte(`{"level":"info","ts":1761400000,"logger":"tls.cache.maintenance","msg":"updated and stored ACME renewal information","identifiers":["*.example.com"],"caddyui_server_id":3,"caddyui_server_name":"edge"}`))
states, err = models.ListCertificateLifecycle(conn, 3)
if err != nil {
t.Fatal(err)
}
if len(states) != 1 {
t.Fatalf("lifecycle rows = %#v, want one compact row", states)
}
if states[0].Phase != "active" {
t.Fatalf("phase after successful ARI update = %q, want active", states[0].Phase)
}
if states[0].Error != "" {
t.Fatalf("stale issuance error survived recovery: %q", states[0].Error)
}
}
// A scheduled ARI refresh is routine maintenance on a healthy certificate. It
// must not be reported to the operator as an in-flight renewal.
func TestHubDoesNotTreatRenewalInfoCheckAsRenewing(t *testing.T) {
conn, err := appdb.Open(filepath.Join(t.TempDir(), "caddyui.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = conn.Close() })
hub := New(conn)
hub.AcceptLine([]byte(`{"level":"info","ts":1761400001,"logger":"tls.cache.maintenance","msg":"certificate expires soon; renewing","identifiers":["renew.example"],"caddyui_server_id":4,"caddyui_server_name":"edge"}`))
states, err := models.ListCertificateLifecycle(conn, 4)
if err != nil {
t.Fatal(err)
}
if len(states) != 1 || states[0].Phase != "renewing" {
t.Fatalf("renewal state = %#v, want renewing", states)
}
}