1package remote
2
3import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "sync/atomic"
8 "testing"
9 "time"
10
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13)
14
15func withFastRetry(t *testing.T) {
16 t.Helper()
17 prevAttempts := retryMaxAttempts
18 prevDelay := retryBaseDelay
19 retryMaxAttempts = 3
20 retryBaseDelay = 10 * time.Millisecond
21 t.Cleanup(func() {
22 retryMaxAttempts = prevAttempts
23 retryBaseDelay = prevDelay
24 })
25}
26
27func TestClientRestartRetriesOn5xx(t *testing.T) {
28 withFastRetry(t)
29
30 var attempts int32
31 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
32 require.Equal(t, "/admin/restart/test.example.com", r.URL.Path)
33 require.Equal(t, http.MethodPost, r.Method)
34 current := atomic.AddInt32(&attempts, 1)
35 if current < 3 {
36 w.WriteHeader(http.StatusServiceUnavailable)
37 _, _ = w.Write([]byte(`{"error":"temporary outage"}`))
38 return
39 }
40 w.Header().Set("Content-Type", "application/json")
41 _, _ = w.Write([]byte(`{"status":"restarted","domain":"test.example.com"}`))
42 }))
43 defer srv.Close()
44
45 client := NewClient(srv.URL)
46 result, err := client.Restart(context.Background(), "test.example.com", false)
47 require.NoError(t, err)
48 require.NotNil(t, result)
49 assert.Equal(t, "test.example.com", result.Domain)
50 assert.EqualValues(t, 3, atomic.LoadInt32(&attempts))
51}
52
53func TestClientReloadRetriesOn5xx(t *testing.T) {
54 withFastRetry(t)
55
56 var attempts int32
57 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
58 require.Equal(t, "/admin/reload", r.URL.Path)
59 require.Equal(t, http.MethodPost, r.Method)
60 current := atomic.AddInt32(&attempts, 1)
61 if current < 2 {
62 w.WriteHeader(http.StatusBadGateway)
63 _, _ = w.Write([]byte(`upstream down`))
64 return
65 }
66 w.Header().Set("Content-Type", "application/json")
67 _, _ = w.Write([]byte(`{"status":"reloaded"}`))
68 }))
69 defer srv.Close()
70
71 client := NewClient(srv.URL)
72 err := client.Reload(context.Background())
73 require.NoError(t, err)
74 assert.EqualValues(t, 2, atomic.LoadInt32(&attempts))
75}
76
77func TestClientDeployReturnsErrorAfterRetryExhaustion(t *testing.T) {
78 withFastRetry(t)
79
80 var attempts int32
81 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
82 require.Equal(t, "/admin/deploy/test.example.com", r.URL.Path)
83 require.Equal(t, http.MethodPost, r.Method)
84 atomic.AddInt32(&attempts, 1)
85 w.WriteHeader(http.StatusBadGateway)
86 _, _ = w.Write([]byte(`{"error":"upstream unavailable"}`))
87 }))
88 defer srv.Close()
89
90 client := NewClient(srv.URL)
91 _, err := client.Deploy(context.Background(), "test.example.com")
92 require.Error(t, err)
93 assert.Contains(t, err.Error(), "502 Bad Gateway: upstream unavailable")
94 assert.EqualValues(t, retryMaxAttempts, atomic.LoadInt32(&attempts))
95}
96
97func TestClientWithInsecureTLS_AllowsSelfSignedCertificate(t *testing.T) {
98 srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
99 require.Equal(t, "/admin/status", r.URL.Path)
100 w.Header().Set("Content-Type", "application/json")
101 _, _ = w.Write([]byte(`{"routes":0,"registry_domain":"","registry_port":0,"server_port":0,"auto_route":false,"network_isolation":false,"container_status":{}}`))
102 }))
103 defer srv.Close()
104
105 _, err := NewClient(srv.URL).GetStatus(context.Background())
106 require.Error(t, err)
107
108 _, err = NewClient(srv.URL, WithInsecureTLS(true)).GetStatus(context.Background())
109 require.NoError(t, err)
110}