v2.31.0-dev12 6a38a35

tls_test.go RawHistoryFolder

1package cli
2
3import (
4	"bytes"
5	"context"
6	"encoding/json"
7	"testing"
8	"time"
9
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12
13	"github.com/bnema/gordon/internal/adapters/dto"
14	"github.com/bnema/gordon/internal/adapters/in/cli/remote"
15	"github.com/bnema/gordon/internal/domain"
16)
17
18// tlsTestControlPlane implements ControlPlane for TLS tests.
19type tlsTestControlPlane struct {
20	getTLSStatus func(context.Context) (*dto.TLSStatusResponse, error)
21}
22
23var _ ControlPlane = (*tlsTestControlPlane)(nil)
24
25func (c *tlsTestControlPlane) ListRoutesWithDetails(context.Context) ([]remote.RouteInfo, error) {
26	panic("unexpected call")
27}
28
29func (c *tlsTestControlPlane) GetHealth(context.Context) (map[string]*remote.RouteHealth, error) {
30	panic("unexpected call")
31}
32
33func (c *tlsTestControlPlane) GetRoute(context.Context, string) (*domain.Route, error) {
34	panic("unexpected call")
35}
36
37func (c *tlsTestControlPlane) FindRoutesByImage(context.Context, string) ([]domain.Route, error) {
38	panic("unexpected call")
39}
40
41func (c *tlsTestControlPlane) AddRoute(context.Context, domain.Route) error {
42	panic("unexpected call")
43}
44
45func (c *tlsTestControlPlane) UpdateRoute(context.Context, domain.Route) error {
46	panic("unexpected call")
47}
48
49func (c *tlsTestControlPlane) RemoveRoute(context.Context, string) error {
50	panic("unexpected call")
51}
52
53func (c *tlsTestControlPlane) Bootstrap(context.Context, dto.BootstrapRequest) (*dto.BootstrapResponse, error) {
54	panic("unexpected call")
55}
56
57func (c *tlsTestControlPlane) ListSecretsWithAttachments(context.Context, string) (*remote.SecretsListResult, error) {
58	panic("unexpected call")
59}
60
61func (c *tlsTestControlPlane) SetSecrets(context.Context, string, map[string]string) error {
62	panic("unexpected call")
63}
64
65func (c *tlsTestControlPlane) DeleteSecret(context.Context, string, string) error {
66	panic("unexpected call")
67}
68
69func (c *tlsTestControlPlane) SetAttachmentSecrets(context.Context, string, string, map[string]string) error {
70	panic("unexpected call")
71}
72
73func (c *tlsTestControlPlane) DeleteAttachmentSecret(context.Context, string, string, string) error {
74	panic("unexpected call")
75}
76
77func (c *tlsTestControlPlane) GetAllAttachmentsConfig(context.Context) (map[string][]string, error) {
78	panic("unexpected call")
79}
80
81func (c *tlsTestControlPlane) GetAttachmentsConfig(context.Context, string) ([]string, error) {
82	panic("unexpected call")
83}
84
85func (c *tlsTestControlPlane) FindAttachmentTargetsByImage(context.Context, string) ([]string, error) {
86	panic("unexpected call")
87}
88
89func (c *tlsTestControlPlane) AddAttachment(context.Context, string, string) error {
90	panic("unexpected call")
91}
92
93func (c *tlsTestControlPlane) RemoveAttachment(context.Context, string, string) error {
94	panic("unexpected call")
95}
96
97func (c *tlsTestControlPlane) GetAutoRouteAllowedDomains(context.Context) ([]string, error) {
98	panic("unexpected call")
99}
100
101func (c *tlsTestControlPlane) AddAutoRouteAllowedDomain(context.Context, string) error {
102	panic("unexpected call")
103}
104
105func (c *tlsTestControlPlane) RemoveAutoRouteAllowedDomain(context.Context, string) error {
106	panic("unexpected call")
107}
108
109func (c *tlsTestControlPlane) GetTLSStatus(ctx context.Context) (*dto.TLSStatusResponse, error) {
110	if c.getTLSStatus != nil {
111		return c.getTLSStatus(ctx)
112	}
113	return nil, nil
114}
115
116func (c *tlsTestControlPlane) GetStatus(context.Context) (*remote.Status, error) {
117	panic("unexpected call")
118}
119
120func (c *tlsTestControlPlane) Reload(context.Context) error {
121	panic("unexpected call")
122}
123
124func (c *tlsTestControlPlane) ListNetworks(context.Context) ([]*domain.NetworkInfo, error) {
125	panic("unexpected call")
126}
127
128func (c *tlsTestControlPlane) GetConfig(context.Context) (*remote.Config, error) {
129	panic("unexpected call")
130}
131
132func (c *tlsTestControlPlane) DeployIntent(context.Context, string) error {
133	panic("unexpected call")
134}
135
136func (c *tlsTestControlPlane) Deploy(context.Context, string) (*remote.DeployResult, error) {
137	panic("unexpected call")
138}
139
140func (c *tlsTestControlPlane) Restart(context.Context, string, bool) (*remote.RestartResult, error) {
141	panic("unexpected call")
142}
143
144func (c *tlsTestControlPlane) ListTags(context.Context, string) ([]string, error) {
145	panic("unexpected call")
146}
147
148func (c *tlsTestControlPlane) ListBackups(context.Context, string) ([]dto.BackupJob, error) {
149	panic("unexpected call")
150}
151
152func (c *tlsTestControlPlane) BackupStatus(context.Context) ([]dto.BackupJob, error) {
153	panic("unexpected call")
154}
155
156func (c *tlsTestControlPlane) RunBackup(context.Context, string, string) (*dto.BackupRunResponse, error) {
157	panic("unexpected call")
158}
159
160func (c *tlsTestControlPlane) DetectDatabases(context.Context, string) ([]dto.DatabaseInfo, error) {
161	panic("unexpected call")
162}
163
164func (c *tlsTestControlPlane) GetProcessLogs(context.Context, int) ([]string, error) {
165	panic("unexpected call")
166}
167
168func (c *tlsTestControlPlane) GetContainerLogs(context.Context, string, int) ([]string, error) {
169	panic("unexpected call")
170}
171
172func (c *tlsTestControlPlane) StreamProcessLogs(context.Context, int) (<-chan string, error) {
173	panic("unexpected call")
174}
175
176func (c *tlsTestControlPlane) StreamContainerLogs(context.Context, string, int) (<-chan string, error) {
177	panic("unexpected call")
178}
179
180func (c *tlsTestControlPlane) ListVolumes(context.Context) ([]dto.Volume, error) {
181	panic("unexpected call")
182}
183
184func (c *tlsTestControlPlane) PruneVolumes(context.Context, dto.VolumePruneRequest) (*dto.VolumePruneResponse, error) {
185	panic("unexpected call")
186}
187
188func TestTLSStatus_HumanOutput(t *testing.T) {
189	notAfter := time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC)
190
191	cp := &tlsTestControlPlane{
192		getTLSStatus: func(_ context.Context) (*dto.TLSStatusResponse, error) {
193			return &dto.TLSStatusResponse{
194				ACMEEnabled:     true,
195				ConfiguredMode:  "acme",
196				EffectiveMode:   "acme-staging",
197				SelectionReason: "configured",
198				TokenSource:     "env",
199				Certificates: []dto.TLSCertificateEntry{
200					{
201						ID:        "cert-abc123",
202						Names:     []string{"example.com", "www.example.com"},
203						Status:    "valid",
204						NotAfter:  notAfter,
205						LastError: "",
206					},
207				},
208				Routes: []dto.TLSRouteCoverage{
209					{
210						Domain:       "example.com",
211						Covered:      true,
212						CoveredBy:    "cert-abc123",
213						RequiredACME: true,
214					},
215					{
216						Domain:       "internal.local",
217						Covered:      false,
218						RequiredACME: false,
219						Error:        "self-signed cert",
220					},
221				},
222				Errors: []string{"route internal.local has no ACME cert"},
223			}, nil
224		},
225	}
226
227	var buf bytes.Buffer
228	err := runTLSStatusCmd(context.Background(), cp, &buf, false)
229	require.NoError(t, err)
230
231	output := buf.String()
232	t.Log("Human output:\n", output)
233
234	// Check mode info is present
235	assert.Contains(t, output, "ACME:")
236	assert.Contains(t, output, "enabled")
237	assert.Contains(t, output, "Configured Mode:")
238	assert.Contains(t, output, "acme")
239	assert.Contains(t, output, "Effective Mode:")
240	assert.Contains(t, output, "acme-staging")
241
242	// Check token source (not value)
243	assert.Contains(t, output, "Token Source:")
244	assert.Contains(t, output, "env")
245	assert.NotContains(t, output, "token_secret_value")
246
247	// Check certificate ID
248	assert.Contains(t, output, "cert-abc123")
249	assert.Contains(t, output, "example.com, www.example.com")
250	assert.Contains(t, output, "valid")
251	assert.Contains(t, output, notAfter.Format(time.DateTime))
252
253	// Check route coverage
254	assert.Contains(t, output, "example.com")
255	assert.Contains(t, output, "covered=yes")
256	assert.Contains(t, output, "covered_by=cert-abc123")
257	assert.Contains(t, output, "internal.local")
258	assert.Contains(t, output, "covered=no")
259
260	// Check errors section
261	assert.Contains(t, output, "Errors")
262	assert.Contains(t, output, "route internal.local has no ACME cert")
263}
264
265func TestTLSStatus_JSONOutput(t *testing.T) {
266	notAfter := time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC)
267
268	cp := &tlsTestControlPlane{
269		getTLSStatus: func(_ context.Context) (*dto.TLSStatusResponse, error) {
270			return &dto.TLSStatusResponse{
271				ACMEEnabled:    true,
272				ConfiguredMode: "acme",
273				EffectiveMode:  "acme-staging",
274				TokenSource:    "env",
275				Certificates: []dto.TLSCertificateEntry{
276					{
277						ID:        "cert-abc123",
278						Names:     []string{"example.com", "www.example.com"},
279						Status:    "valid",
280						NotAfter:  notAfter,
281						LastError: "",
282					},
283				},
284			}, nil
285		},
286	}
287
288	var buf bytes.Buffer
289	err := runTLSStatusCmd(context.Background(), cp, &buf, true)
290	require.NoError(t, err)
291
292	var result dto.TLSStatusResponse
293	err = json.Unmarshal(buf.Bytes(), &result)
294	require.NoError(t, err)
295
296	assert.True(t, result.ACMEEnabled)
297	assert.Equal(t, "acme", result.ConfiguredMode)
298	assert.Equal(t, "acme-staging", result.EffectiveMode)
299	assert.Equal(t, "env", result.TokenSource)
300	require.Len(t, result.Certificates, 1)
301	assert.Equal(t, "cert-abc123", result.Certificates[0].ID)
302	assert.Equal(t, []string{"example.com", "www.example.com"}, result.Certificates[0].Names)
303	assert.Equal(t, "valid", result.Certificates[0].Status)
304	assert.Equal(t, notAfter.Format(time.RFC3339), result.Certificates[0].NotAfter.Format(time.RFC3339))
305}

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