1package domain
2
3import (
4 "strings"
5 "testing"
6 "time"
7
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10)
11
12func TestIsValidHTTP01Token(t *testing.T) {
13 tests := []struct {
14 name string
15 token string
16 want bool
17 }{
18 {name: "valid", token: "abc123_-token", want: true},
19 {name: "empty", token: "", want: false},
20 {name: "slash", token: "abc/def", want: false},
21 {name: "backslash", token: `abc\def`, want: false},
22 {name: "dot dot", token: "abc..def", want: false},
23 {name: "nul", token: "abc\x00def", want: false},
24 {name: "too long", token: strings.Repeat("a", MaxHTTP01TokenLength+1), want: false},
25 }
26
27 for _, tt := range tests {
28 t.Run(tt.name, func(t *testing.T) {
29 assert.Equal(t, tt.want, IsValidHTTP01Token(tt.token))
30 })
31 }
32}
33
34func TestACMEChallengeModeParse(t *testing.T) {
35 tests := []struct {
36 input string
37 want ACMEChallengeMode
38 ok bool
39 }{
40 {"", ACMEChallengeAuto, true},
41 {"auto", ACMEChallengeAuto, true},
42 {"http-01", ACMEChallengeHTTP01, true},
43 {"cloudflare-dns-01", ACMEChallengeCloudflareDNS01, true},
44 {"dns", "", false},
45 }
46
47 for _, tt := range tests {
48 got, err := ParseACMEChallengeMode(tt.input)
49 if tt.ok {
50 require.NoError(t, err)
51 assert.Equal(t, tt.want, got)
52 } else {
53 require.Error(t, err)
54 assert.ErrorIs(t, err, ErrACMEChallengeInvalid)
55 }
56 }
57}
58
59func TestManagedCertificateCovers(t *testing.T) {
60 apexCert := ManagedCertificate{Names: []string{"example.com", "*.example.com"}}
61 prodCert := ManagedCertificate{Names: []string{"prod.example.com", "*.prod.example.com"}}
62
63 assert.True(t, apexCert.Covers("example.com"))
64 assert.True(t, apexCert.Covers("app.example.com"))
65 assert.False(t, apexCert.Covers("api.prod.example.com"), "*.example.com must not cover two labels")
66 assert.True(t, prodCert.Covers("api.prod.example.com"))
67 assert.False(t, apexCert.Covers("other.test"))
68}
69
70func TestManagedCertificateHealth(t *testing.T) {
71 // Fixed reference time for deterministic tests.
72 now := time.Date(2026, 4, 29, 12, 0, 0, 0, time.UTC)
73
74 tests := []struct {
75 name string
76 cert ManagedCertificate
77 want TLSCertificateStatus
78 }{
79 {
80 name: "valid certificate far from expiry",
81 cert: ManagedCertificate{NotAfter: now.Add(60 * 24 * time.Hour)},
82 want: TLSCertificateStatusValid,
83 },
84 {
85 name: "expired certificate",
86 cert: ManagedCertificate{NotAfter: now.Add(-time.Hour)},
87 want: TLSCertificateStatusExpired,
88 },
89 {
90 name: "warning when less than 30 days remain",
91 cert: ManagedCertificate{NotAfter: now.Add(15 * 24 * time.Hour)},
92 want: TLSCertificateStatusWarning,
93 },
94 {
95 name: "warning exactly at 30 day boundary",
96 cert: ManagedCertificate{NotAfter: now.Add(30 * 24 * time.Hour)},
97 want: TLSCertificateStatusWarning,
98 },
99 {
100 name: "missing when NotAfter is zero",
101 cert: ManagedCertificate{},
102 want: TLSCertificateStatusMissing,
103 },
104 {
105 name: "error when LastError is set",
106 cert: ManagedCertificate{
107 NotAfter: now.Add(60 * 24 * time.Hour),
108 LastError: "issuer unavailable",
109 },
110 want: TLSCertificateStatusError,
111 },
112 {
113 name: "error takes priority over expired",
114 cert: ManagedCertificate{
115 NotAfter: now.Add(-time.Hour),
116 LastError: "something went wrong",
117 },
118 want: TLSCertificateStatusError,
119 },
120 {
121 name: "error takes priority over zero NotAfter",
122 cert: ManagedCertificate{
123 LastError: "something went wrong",
124 },
125 want: TLSCertificateStatusError,
126 },
127 }
128
129 for _, tt := range tests {
130 t.Run(tt.name, func(t *testing.T) {
131 got := tt.cert.Health(now)
132 assert.Equal(t, tt.want, got)
133 })
134 }
135}