1package cli
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "net/http"
9 "net/http/httptest"
10 "testing"
11
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/require"
14
15 "github.com/bnema/gordon/internal/adapters/dto"
16 "github.com/bnema/gordon/internal/adapters/in/cli/remote"
17 "github.com/bnema/gordon/internal/domain"
18)
19
20func TestTrafficCommandExists(t *testing.T) {
21 cmd := NewRootCmd()
22 traffic, _, err := cmd.Find([]string{"traffic"})
23 require.NoError(t, err)
24 require.NotNil(t, traffic)
25 assert.Equal(t, "traffic", traffic.Name())
26
27 status, _, err := cmd.Find([]string{"traffic", "status"})
28 require.NoError(t, err)
29 require.NotNil(t, status)
30 assert.Equal(t, "status", status.Name())
31}
32
33func TestTrafficStatusRejectsArgs(t *testing.T) {
34 cmd := newTrafficStatusCmd()
35 require.Error(t, cmd.Args(cmd, []string{"extra"}))
36}
37
38func TestTrafficStatusHumanOutput(t *testing.T) {
39 status := &dto.TrafficStatusResponse{
40 LastReloadStatus: "ok",
41 EntryPoints: []dto.TrafficEntryPointStatus{{
42 Name: "postgres", Address: "127.0.0.1:5432", Protocol: domain.EntryPointProtocolTCP,
43 Active: true, ActiveTCPConnections: 1, TotalAccepted: 2, BytesIn: 10, BytesOut: 20,
44 SmartTCP: dto.SmartTCPCounters{HTTPAccepted: 1, PROXYRefused: 1},
45 }},
46 Routers: []dto.TrafficRouterStatus{{
47 Name: "pg-router", EntryPoint: "postgres", Protocol: domain.RouterProtocolTCP,
48 Service: "network_service:postgres:db", Active: true,
49 }},
50 Services: []dto.TrafficServiceStatus{{
51 Name: "network_service:postgres:db", Active: true,
52 Backends: []dto.TrafficBackendStatus{{Name: "postgres:db", Host: "127.0.0.1", Port: 5432, Protocol: domain.NetworkProtocolTCP, Active: true}},
53 }},
54 Counters: dto.TrafficCounters{ActiveTCPConnections: 1, TotalAccepted: 2, BytesIn: 10, BytesOut: 20, SmartTCP: dto.SmartTCPCounters{HTTPAccepted: 1, PROXYRefused: 1}},
55 }
56 var buf bytes.Buffer
57 require.NoError(t, renderTrafficStatus(&buf, status, false))
58 output := buf.String()
59 assert.Contains(t, output, "Traffic Status")
60 assert.Contains(t, output, "postgres")
61 assert.Contains(t, output, "127.0.0.1:5432")
62 assert.Contains(t, output, "pg-router")
63 assert.Contains(t, output, "network_service:postgres:db")
64 assert.Contains(t, output, "tcp://127.0.0.1:5432")
65 assert.Contains(t, output, "accepted=2")
66 assert.Contains(t, output, "smart_tcp")
67 assert.Contains(t, output, "http_accepted=1")
68 assert.Contains(t, output, "proxy_refused=1")
69}
70
71func TestTrafficStatusJSONOutput(t *testing.T) {
72 status := &dto.TrafficStatusResponse{LastReloadStatus: "ok", Counters: dto.TrafficCounters{ActiveUDPSessions: 3, SmartTCP: dto.SmartTCPCounters{SniffTimeout: 1}}}
73 var buf bytes.Buffer
74 require.NoError(t, renderTrafficStatus(&buf, status, true))
75 var got dto.TrafficStatusResponse
76 require.NoError(t, json.Unmarshal(buf.Bytes(), &got))
77 assert.Equal(t, "ok", got.LastReloadStatus)
78 assert.Equal(t, int64(3), got.Counters.ActiveUDPSessions)
79 assert.Equal(t, int64(1), got.Counters.SmartTCP.SniffTimeout)
80}
81
82func TestRunTrafficStatusUsesControlPlane(t *testing.T) {
83 status := &dto.TrafficStatusResponse{LastReloadStatus: "ok"}
84 cp := &trafficStatusPlane{status: status}
85
86 var buf bytes.Buffer
87 require.NoError(t, runTrafficStatus(context.Background(), cp, &buf, true))
88
89 assert.True(t, cp.called)
90 assert.Contains(t, buf.String(), `"last_reload_status": "ok"`)
91}
92
93func TestRemoteTrafficStatusControlPlaneStillRenders(t *testing.T) {
94 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
95 require.Equal(t, "/admin/traffic/status", r.URL.Path)
96 require.Equal(t, http.MethodGet, r.Method)
97 _, err := w.Write([]byte(`{"last_reload_status":"ok","counters":{"active_udp_sessions":3}}`))
98 require.NoError(t, err)
99 }))
100 defer srv.Close()
101
102 cp := NewRemoteControlPlane(remote.NewClient(srv.URL))
103 var buf bytes.Buffer
104 require.NoError(t, runTrafficStatus(context.Background(), cp, &buf, false))
105
106 output := buf.String()
107 assert.Contains(t, output, "Traffic Status")
108 assert.Contains(t, output, "Reload")
109 assert.Contains(t, output, "ok")
110 assert.Contains(t, output, "udp=3")
111}
112
113func TestLocalTrafficStatusReturnsActionableDaemonGuidance(t *testing.T) {
114 _, err := (&localControlPlane{}).GetTrafficStatus(context.Background())
115 require.Error(t, err)
116 assert.True(t, errors.Is(err, domain.ErrTrafficStatusUnavailable))
117 assert.Contains(t, err.Error(), "in-process CLI control plane")
118 assert.Contains(t, err.Error(), "running Gordon daemon")
119 assert.Contains(t, err.Error(), "--remote")
120 assert.Contains(t, err.Error(), "GORDON_REMOTE")
121}
122
123type trafficStatusPlane struct {
124 ControlPlane
125 status *dto.TrafficStatusResponse
126 called bool
127}
128
129func (p *trafficStatusPlane) GetTrafficStatus(context.Context) (*dto.TrafficStatusResponse, error) {
130 p.called = true
131 return p.status, nil
132}