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 climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks"
15)
16
17func TestTLSStatus_HumanOutput(t *testing.T) {
18 notAfter := time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC)
19
20 cpMock := climocks.NewMockControlPlane(t)
21 cpMock.EXPECT().GetTLSStatus(context.Background()).Return(&dto.TLSStatusResponse{
22 ACMEEnabled: true,
23 ConfiguredMode: "acme",
24 EffectiveMode: "acme-staging",
25 SelectionReason: "configured",
26 TokenSource: "env",
27 Certificates: []dto.TLSCertificateEntry{
28 {
29 ID: "cert-abc123",
30 Names: []string{"example.com", "www.example.com"},
31 Status: "valid",
32 NotAfter: notAfter,
33 LastError: "",
34 },
35 },
36 Routes: []dto.TLSRouteCoverage{
37 {
38 Domain: "example.com",
39 Covered: true,
40 CoveredBy: "cert-abc123",
41 RequiredACME: true,
42 },
43 {
44 Domain: "internal.local",
45 Covered: false,
46 RequiredACME: false,
47 Error: "self-signed cert",
48 },
49 },
50 Errors: []string{"route internal.local has no ACME cert"},
51 }, nil).Once()
52
53 var buf bytes.Buffer
54 err := runTLSStatusCmd(context.Background(), cpMock, &buf, false)
55 require.NoError(t, err)
56
57 output := buf.String()
58 t.Log("Human output:\n", output)
59
60 // Check mode info is present
61 assert.Contains(t, output, "ACME:")
62 assert.Contains(t, output, "enabled")
63 assert.Contains(t, output, "Configured Mode:")
64 assert.Contains(t, output, "acme")
65 assert.Contains(t, output, "Effective Mode:")
66 assert.Contains(t, output, "acme-staging")
67
68 // Check token source (not value)
69 assert.Contains(t, output, "Token Source:")
70 assert.Contains(t, output, "env")
71 assert.NotContains(t, output, "token_secret_value")
72
73 // Check certificate ID
74 assert.Contains(t, output, "cert-abc123")
75 assert.Contains(t, output, "example.com, www.example.com")
76 assert.Contains(t, output, "valid")
77 assert.Contains(t, output, notAfter.Format(time.DateTime))
78
79 // Check route coverage
80 assert.Contains(t, output, "example.com")
81 assert.Contains(t, output, "covered=yes")
82 assert.Contains(t, output, "covered_by=cert-abc123")
83 assert.Contains(t, output, "internal.local")
84 assert.Contains(t, output, "covered=no")
85
86 // Check errors section
87 assert.Contains(t, output, "Errors")
88 assert.Contains(t, output, "route internal.local has no ACME cert")
89}
90
91func TestTLSStatus_JSONOutput(t *testing.T) {
92 notAfter := time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC)
93
94 cpMock := climocks.NewMockControlPlane(t)
95 cpMock.EXPECT().GetTLSStatus(context.Background()).Return(&dto.TLSStatusResponse{
96 ACMEEnabled: true,
97 ConfiguredMode: "acme",
98 EffectiveMode: "acme-staging",
99 TokenSource: "env",
100 Certificates: []dto.TLSCertificateEntry{
101 {
102 ID: "cert-abc123",
103 Names: []string{"example.com", "www.example.com"},
104 Status: "valid",
105 NotAfter: notAfter,
106 LastError: "",
107 },
108 },
109 }, nil).Once()
110
111 var buf bytes.Buffer
112 err := runTLSStatusCmd(context.Background(), cpMock, &buf, true)
113 require.NoError(t, err)
114
115 var result dto.TLSStatusResponse
116 err = json.Unmarshal(buf.Bytes(), &result)
117 require.NoError(t, err)
118
119 assert.True(t, result.ACMEEnabled)
120 assert.Equal(t, "acme", result.ConfiguredMode)
121 assert.Equal(t, "acme-staging", result.EffectiveMode)
122 assert.Equal(t, "env", result.TokenSource)
123 require.Len(t, result.Certificates, 1)
124 assert.Equal(t, "cert-abc123", result.Certificates[0].ID)
125 assert.Equal(t, []string{"example.com", "www.example.com"}, result.Certificates[0].Names)
126 assert.Equal(t, "valid", result.Certificates[0].Status)
127 assert.Equal(t, notAfter.Format(time.RFC3339), result.Certificates[0].NotAfter.Format(time.RFC3339))
128}