v3.3.1 31f1eec

config_test.go RawHistoryFolder

1package publictls
2
3import (
4	"context"
5	"errors"
6	"testing"
7
8	"github.com/stretchr/testify/assert"
9	"github.com/stretchr/testify/mock"
10	"github.com/stretchr/testify/require"
11
12	"github.com/bnema/gordon/internal/boundaries/out"
13	outmocks "github.com/bnema/gordon/internal/boundaries/out/mocks"
14	"github.com/bnema/gordon/internal/domain"
15)
16
17func newSecretResolverMock(t *testing.T, value out.SecretValue, err error) *outmocks.MockSecretResolver {
18	t.Helper()
19	resolver := outmocks.NewMockSecretResolver(t)
20	resolver.EXPECT().ResolveCloudflareToken(mock.Anything).Return(value, err)
21	return resolver
22}
23
24func TestResolveEffectiveChallenge(t *testing.T) {
25	t.Run("disabled returns auto mode with none source", func(t *testing.T) {
26		cfg := Config{Enabled: false}
27		ec, err := ResolveEffectiveChallenge(context.Background(), cfg, nil)
28		require.NoError(t, err)
29		assert.Equal(t, domain.ACMEChallengeAuto, ec.ConfiguredMode)
30		assert.Equal(t, domain.ACMEChallengeAuto, ec.Mode)
31		assert.Equal(t, domain.ACMETokenSourceNone, ec.TokenSource)
32		assert.Equal(t, "ACME disabled", ec.Reason)
33	})
34
35	t.Run("disabled preserves configured challenge in ConfiguredMode", func(t *testing.T) {
36		cfg := Config{Enabled: false, Challenge: "http-01"}
37		ec, err := ResolveEffectiveChallenge(context.Background(), cfg, nil)
38		require.NoError(t, err)
39		assert.Equal(t, domain.ACMEChallengeHTTP01, ec.ConfiguredMode, "should preserve http-01")
40		assert.Equal(t, domain.ACMEChallengeAuto, ec.Mode, "Mode should remain auto")
41		assert.Equal(t, domain.ACMETokenSourceNone, ec.TokenSource)
42		assert.Equal(t, "ACME disabled", ec.Reason)
43	})
44
45	t.Run("disabled preserves cloudflare-dns-01 in ConfiguredMode", func(t *testing.T) {
46		cfg := Config{Enabled: false, Challenge: "cloudflare-dns-01"}
47		ec, err := ResolveEffectiveChallenge(context.Background(), cfg, nil)
48		require.NoError(t, err)
49		assert.Equal(t, domain.ACMEChallengeCloudflareDNS01, ec.ConfiguredMode, "should preserve cloudflare-dns-01")
50		assert.Equal(t, domain.ACMEChallengeAuto, ec.Mode, "Mode should remain auto")
51		assert.Equal(t, domain.ACMETokenSourceNone, ec.TokenSource)
52		assert.Equal(t, "ACME disabled", ec.Reason)
53	})
54
55	t.Run("disabled with unparseable challenge defaults ConfiguredMode to auto", func(t *testing.T) {
56		cfg := Config{Enabled: false, Challenge: "unknown-challenge"}
57		ec, err := ResolveEffectiveChallenge(context.Background(), cfg, nil)
58		require.NoError(t, err)
59		assert.Equal(t, domain.ACMEChallengeAuto, ec.ConfiguredMode, "unparseable defaults to auto")
60		assert.Equal(t, domain.ACMEChallengeAuto, ec.Mode)
61		assert.Equal(t, "ACME disabled", ec.Reason)
62	})
63
64	t.Run("enabled requires non-empty email", func(t *testing.T) {
65		cfg := Config{Enabled: true, Email: "", TLSPort: 8443}
66		_, err := ResolveEffectiveChallenge(context.Background(), cfg, nil)
67		require.Error(t, err)
68		assert.ErrorIs(t, err, domain.ErrACMEEmailRequired)
69	})
70
71	t.Run("enabled requires valid tls port in 1..65535", func(t *testing.T) {
72		cfg := Config{Enabled: true, Email: "admin@example.com", TLSPort: 0}
73		_, err := ResolveEffectiveChallenge(context.Background(), cfg, nil)
74		require.Error(t, err)
75		assert.ErrorIs(t, err, domain.ErrACMEChallengeInvalid)
76		assert.Contains(t, err.Error(), "tls_port")
77
78		cfg2 := Config{Enabled: true, Email: "admin@example.com", TLSPort: 65536}
79		_, err2 := ResolveEffectiveChallenge(context.Background(), cfg2, nil)
80		require.Error(t, err2)
81		assert.ErrorIs(t, err2, domain.ErrACMEChallengeInvalid)
82		assert.Contains(t, err2.Error(), "tls_port")
83	})
84
85	t.Run("auto with cloudflare token returns dns-01", func(t *testing.T) {
86		cfg := Config{Enabled: true, Email: "admin@example.com", Challenge: "auto", TLSPort: 8443, HTTPPort: 8088}
87		resolver := newSecretResolverMock(t, out.SecretValue{Value: "token", Source: domain.ACMETokenSourcePass}, nil)
88		ec, err := ResolveEffectiveChallenge(context.Background(), cfg, resolver)
89		require.NoError(t, err)
90		assert.Equal(t, domain.ACMEChallengeCloudflareDNS01, ec.Mode)
91		assert.Equal(t, domain.ACMETokenSourcePass, ec.TokenSource)
92		assert.Equal(t, "token", ec.Token)
93		assert.Equal(t, domain.ACMEChallengeAuto, ec.ConfiguredMode)
94	})
95
96	t.Run("auto without token returns http-01", func(t *testing.T) {
97		cfg := Config{Enabled: true, Email: "admin@example.com", Challenge: "auto", TLSPort: 8443, HTTPPort: 8088}
98		resolver := newSecretResolverMock(t, out.SecretValue{Value: "", Source: domain.ACMETokenSourceNone}, nil)
99		ec, err := ResolveEffectiveChallenge(context.Background(), cfg, resolver)
100		require.NoError(t, err)
101		assert.Equal(t, domain.ACMEChallengeHTTP01, ec.Mode)
102		assert.Equal(t, domain.ACMETokenSourceNone, ec.TokenSource)
103		assert.Equal(t, domain.ACMEChallengeAuto, ec.ConfiguredMode)
104	})
105
106	t.Run("forced cloudflare-dns-01 requires token", func(t *testing.T) {
107		cfg := Config{Enabled: true, Email: "admin@example.com", Challenge: "cloudflare-dns-01", TLSPort: 8443, HTTPPort: 8088}
108		resolver := newSecretResolverMock(t, out.SecretValue{Value: "", Source: domain.ACMETokenSourceNone}, nil)
109		_, err := ResolveEffectiveChallenge(context.Background(), cfg, resolver)
110		require.Error(t, err)
111		assert.ErrorIs(t, err, domain.ErrCloudflareTokenMissing)
112	})
113
114	t.Run("http-01 requires valid http port in 1..65535", func(t *testing.T) {
115		cfg := Config{Enabled: true, Email: "admin@example.com", Challenge: "http-01", TLSPort: 8443, HTTPPort: 0}
116		_, err := ResolveEffectiveChallenge(context.Background(), cfg, nil)
117		require.Error(t, err)
118		assert.ErrorIs(t, err, domain.ErrACMEChallengeInvalid)
119		assert.Contains(t, err.Error(), "http_port")
120
121		cfg2 := Config{Enabled: true, Email: "admin@example.com", Challenge: "http-01", TLSPort: 8443, HTTPPort: 65536}
122		_, err2 := ResolveEffectiveChallenge(context.Background(), cfg2, nil)
123		require.Error(t, err2)
124		assert.ErrorIs(t, err2, domain.ErrACMEChallengeInvalid)
125		assert.Contains(t, err2.Error(), "http_port")
126	})
127
128	t.Run("cloudflare-dns-01 preserves resolver errors", func(t *testing.T) {
129		cfg := Config{Enabled: true, Email: "admin@example.com", Challenge: "cloudflare-dns-01", TLSPort: 8443}
130		resolver := newSecretResolverMock(t, out.SecretValue{}, errors.New("pass failed"))
131		_, err := ResolveEffectiveChallenge(context.Background(), cfg, resolver)
132		require.Error(t, err)
133		assert.Contains(t, err.Error(), "resolve Cloudflare token")
134		assert.False(t, errors.Is(err, domain.ErrCloudflareTokenMissing), "must not collapse to missing token")
135	})
136
137	t.Run("nil resolver in auto mode returns http-01", func(t *testing.T) {
138		cfg := Config{Enabled: true, Email: "admin@example.com", Challenge: "auto", TLSPort: 8443, HTTPPort: 8088}
139		ec, err := ResolveEffectiveChallenge(context.Background(), cfg, nil)
140		require.NoError(t, err)
141		assert.Equal(t, domain.ACMEChallengeHTTP01, ec.Mode)
142		assert.Equal(t, domain.ACMETokenSourceNone, ec.TokenSource)
143	})
144
145	t.Run("forced cloudflare-dns-01 with nil resolver returns missing token", func(t *testing.T) {
146		cfg := Config{Enabled: true, Email: "admin@example.com", Challenge: "cloudflare-dns-01", TLSPort: 8443}
147		_, err := ResolveEffectiveChallenge(context.Background(), cfg, nil)
148		require.Error(t, err)
149		assert.ErrorIs(t, err, domain.ErrCloudflareTokenMissing)
150	})
151}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close