1package secrets
2
3import (
4 "context"
5 "errors"
6 "os"
7 "path/filepath"
8 "testing"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12
13 "github.com/bnema/gordon/internal/boundaries/out"
14 "github.com/bnema/gordon/internal/domain"
15)
16
17func TestPublicTLSResolverPrefersPass(t *testing.T) {
18 env := map[string]string{
19 "GORDON_CLOUDFLARE_API_TOKEN": "env-token",
20 }
21 resolver := NewPublicTLSResolver(PublicTLSResolverConfig{
22 PassLookup: func(_ context.Context, _ string) (string, error) {
23 return " pass-token\n", nil
24 },
25 EnvLookup: func(key string) string {
26 return env[key]
27 },
28 })
29
30 sv, err := resolver.ResolveCloudflareToken(context.Background())
31 require.NoError(t, err)
32 assert.Equal(t, "pass-token", sv.Value)
33 assert.Equal(t, domain.ACMETokenSourcePass, sv.Source)
34}
35
36func TestPublicTLSResolverFallsBackToTokenFile(t *testing.T) {
37 tmpDir := t.TempDir()
38 tmpFile := filepath.Join(tmpDir, "cf-token")
39 err := os.WriteFile(tmpFile, []byte("file-token\n"), 0644)
40 require.NoError(t, err)
41
42 env := map[string]string{
43 "GORDON_CLOUDFLARE_API_TOKEN_FILE": tmpFile,
44 }
45 resolver := NewPublicTLSResolver(PublicTLSResolverConfig{
46 PassLookup: func(_ context.Context, _ string) (string, error) {
47 return "", os.ErrNotExist
48 },
49 EnvLookup: func(key string) string {
50 return env[key]
51 },
52 })
53
54 sv, err := resolver.ResolveCloudflareToken(context.Background())
55 require.NoError(t, err)
56 assert.Equal(t, "file-token", sv.Value)
57 assert.Equal(t, domain.ACMETokenSourceFile, sv.Source)
58}
59
60func TestPublicTLSResolverFallsBackToEnv(t *testing.T) {
61 env := map[string]string{
62 "GORDON_CLOUDFLARE_API_TOKEN": "env-token",
63 }
64 resolver := NewPublicTLSResolver(PublicTLSResolverConfig{
65 PassLookup: func(_ context.Context, _ string) (string, error) {
66 return "", os.ErrNotExist
67 },
68 EnvLookup: func(key string) string {
69 return env[key]
70 },
71 })
72
73 sv, err := resolver.ResolveCloudflareToken(context.Background())
74 require.NoError(t, err)
75 assert.Equal(t, "env-token", sv.Value)
76 assert.Equal(t, domain.ACMETokenSourceEnv, sv.Source)
77}
78
79func TestPublicTLSResolverNoToken(t *testing.T) {
80 env := map[string]string{}
81 resolver := NewPublicTLSResolver(PublicTLSResolverConfig{
82 PassLookup: func(_ context.Context, _ string) (string, error) {
83 return "", os.ErrNotExist
84 },
85 EnvLookup: func(key string) string {
86 return env[key]
87 },
88 })
89
90 sv, err := resolver.ResolveCloudflareToken(context.Background())
91 require.NoError(t, err)
92 assert.Empty(t, sv.Value)
93 assert.Equal(t, domain.ACMETokenSourceNone, sv.Source)
94}
95
96func TestPublicTLSResolverPassErrorWithoutFallback(t *testing.T) {
97 expectedErr := errors.New("pass show failed")
98 resolver := NewPublicTLSResolver(PublicTLSResolverConfig{
99 PassLookup: func(_ context.Context, _ string) (string, error) {
100 return "", expectedErr
101 },
102 EnvLookup: func(key string) string {
103 return ""
104 },
105 })
106
107 sv, err := resolver.ResolveCloudflareToken(context.Background())
108 require.Error(t, err)
109 assert.Contains(t, err.Error(), "resolve pass token")
110 assert.Contains(t, err.Error(), expectedErr.Error())
111 assert.Empty(t, sv.Value)
112 assert.Equal(t, domain.ACMETokenSourceNone, sv.Source)
113}
114
115func TestPublicTLSResolverPassErrorWithEnvFallback(t *testing.T) {
116 resolver := NewPublicTLSResolver(PublicTLSResolverConfig{
117 PassLookup: func(_ context.Context, _ string) (string, error) {
118 return "", errors.New("pass not available")
119 },
120 EnvLookup: func(key string) string {
121 if key == "GORDON_CLOUDFLARE_API_TOKEN" {
122 return "env-token"
123 }
124 return ""
125 },
126 })
127
128 sv, err := resolver.ResolveCloudflareToken(context.Background())
129 require.NoError(t, err)
130 assert.Equal(t, "env-token", sv.Value)
131 assert.Equal(t, domain.ACMETokenSourceEnv, sv.Source)
132}
133
134func TestPublicTLSResolverTokenFileErrorWithoutFallback(t *testing.T) {
135 resolver := NewPublicTLSResolver(PublicTLSResolverConfig{
136 PassLookup: func(_ context.Context, _ string) (string, error) {
137 return "", os.ErrNotExist
138 },
139 EnvLookup: func(key string) string {
140 if key == "GORDON_CLOUDFLARE_API_TOKEN_FILE" {
141 return "/nonexistent/token-file"
142 }
143 return ""
144 },
145 })
146
147 sv, err := resolver.ResolveCloudflareToken(context.Background())
148 require.Error(t, err)
149 assert.Contains(t, err.Error(), "read token file")
150 assert.Empty(t, sv.Value)
151 assert.Equal(t, domain.ACMETokenSourceNone, sv.Source)
152}
153
154func TestPublicTLSResolverTokenFileErrorWithEnvFallback(t *testing.T) {
155 resolver := NewPublicTLSResolver(PublicTLSResolverConfig{
156 PassLookup: func(_ context.Context, _ string) (string, error) {
157 return "", os.ErrNotExist
158 },
159 EnvLookup: func(key string) string {
160 if key == "GORDON_CLOUDFLARE_API_TOKEN_FILE" {
161 return "/nonexistent/token-file"
162 }
163 if key == "GORDON_CLOUDFLARE_API_TOKEN" {
164 return "env-token"
165 }
166 return ""
167 },
168 })
169
170 sv, err := resolver.ResolveCloudflareToken(context.Background())
171 require.NoError(t, err)
172 assert.Equal(t, "env-token", sv.Value)
173 assert.Equal(t, domain.ACMETokenSourceEnv, sv.Source)
174}
175
176func TestPublicTLSResolverEmptyPassValueFallback(t *testing.T) {
177 // Empty string from pass (no error) should fall through to other sources
178 resolver := NewPublicTLSResolver(PublicTLSResolverConfig{
179 PassLookup: func(_ context.Context, _ string) (string, error) {
180 return "", nil // no error but empty value
181 },
182 EnvLookup: func(key string) string {
183 if key == "GORDON_CLOUDFLARE_API_TOKEN" {
184 return "env-token"
185 }
186 return ""
187 },
188 })
189
190 sv, err := resolver.ResolveCloudflareToken(context.Background())
191 require.NoError(t, err)
192 assert.Equal(t, "env-token", sv.Value)
193 assert.Equal(t, domain.ACMETokenSourceEnv, sv.Source)
194}
195
196// Ensure PublicTLSResolver implements out.SecretResolver at compile time.
197var _ out.SecretResolver = (*PublicTLSResolver)(nil)