Skip to content

Commit 69b47cf

Browse files
committed
test(gateway): add recovery admin auth/method/throttle coverage
1 parent 6fb487b commit 69b47cf

2 files changed

Lines changed: 399 additions & 0 deletions

File tree

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package gateway
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
)
8+
9+
func setupRecoveryServer(ra *recoveryAdmin, token string) (*httptest.Server, func()) {
10+
orig := RecoveryAdminToken
11+
RecoveryAdminToken = token
12+
13+
mux := http.NewServeMux()
14+
ra.register(mux)
15+
ts := httptest.NewServer(mux)
16+
17+
cleanup := func() {
18+
ts.Close()
19+
RecoveryAdminToken = orig
20+
}
21+
return ts, cleanup
22+
}
23+
24+
func TestRecoveryAdminIntegration_AuthAndRouting(t *testing.T) {
25+
ra := &recoveryAdmin{
26+
enabled: true,
27+
reseedSem: make(chan struct{}, 1),
28+
statusSem: make(chan struct{}, 1),
29+
}
30+
ts, cleanup := setupRecoveryServer(ra, "secret")
31+
defer cleanup()
32+
33+
t.Run("health unauthorized without header", func(t *testing.T) {
34+
resp, err := http.Get(ts.URL + "/api/v1/recovery/health")
35+
if err != nil {
36+
t.Fatalf("request failed: %v", err)
37+
}
38+
defer resp.Body.Close()
39+
if resp.StatusCode != http.StatusUnauthorized {
40+
t.Fatalf("status=%d want=%d", resp.StatusCode, http.StatusUnauthorized)
41+
}
42+
})
43+
44+
t.Run("health authorized returns 200", func(t *testing.T) {
45+
req, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/recovery/health", nil)
46+
req.Header.Set(recoveryHeaderToken, "secret")
47+
resp, err := http.DefaultClient.Do(req)
48+
if err != nil {
49+
t.Fatalf("request failed: %v", err)
50+
}
51+
defer resp.Body.Close()
52+
if resp.StatusCode != http.StatusOK {
53+
t.Fatalf("status=%d want=%d", resp.StatusCode, http.StatusOK)
54+
}
55+
})
56+
57+
t.Run("action status route missing action id returns 404", func(t *testing.T) {
58+
req, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/recovery/actions//status", nil)
59+
req.Header.Set(recoveryHeaderToken, "secret")
60+
resp, err := http.DefaultClient.Do(req)
61+
if err != nil {
62+
t.Fatalf("request failed: %v", err)
63+
}
64+
defer resp.Body.Close()
65+
if resp.StatusCode != http.StatusNotFound {
66+
t.Fatalf("status=%d want=%d", resp.StatusCode, http.StatusNotFound)
67+
}
68+
})
69+
}
70+
71+
func TestRecoveryAdminIntegration_MethodAndDependencyBehavior(t *testing.T) {
72+
ra := &recoveryAdmin{
73+
enabled: true,
74+
reseedSem: make(chan struct{}, 1),
75+
statusSem: make(chan struct{}, 1),
76+
}
77+
ts, cleanup := setupRecoveryServer(ra, "secret")
78+
defer cleanup()
79+
80+
t.Run("reseed rejects GET", func(t *testing.T) {
81+
req, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/recovery/reseed?action_id=a1", nil)
82+
req.Header.Set(recoveryHeaderToken, "secret")
83+
resp, err := http.DefaultClient.Do(req)
84+
if err != nil {
85+
t.Fatalf("request failed: %v", err)
86+
}
87+
defer resp.Body.Close()
88+
if resp.StatusCode != http.StatusMethodNotAllowed {
89+
t.Fatalf("status=%d want=%d", resp.StatusCode, http.StatusMethodNotAllowed)
90+
}
91+
})
92+
93+
t.Run("status rejects POST", func(t *testing.T) {
94+
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/v1/recovery/actions/a1/status", nil)
95+
req.Header.Set(recoveryHeaderToken, "secret")
96+
resp, err := http.DefaultClient.Do(req)
97+
if err != nil {
98+
t.Fatalf("request failed: %v", err)
99+
}
100+
defer resp.Body.Close()
101+
if resp.StatusCode != http.StatusMethodNotAllowed {
102+
t.Fatalf("status=%d want=%d", resp.StatusCode, http.StatusMethodNotAllowed)
103+
}
104+
})
105+
106+
t.Run("reseed returns 503 when dependencies missing", func(t *testing.T) {
107+
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/v1/recovery/reseed?action_id=a1", nil)
108+
req.Header.Set(recoveryHeaderToken, "secret")
109+
resp, err := http.DefaultClient.Do(req)
110+
if err != nil {
111+
t.Fatalf("request failed: %v", err)
112+
}
113+
defer resp.Body.Close()
114+
if resp.StatusCode != http.StatusServiceUnavailable {
115+
t.Fatalf("status=%d want=%d", resp.StatusCode, http.StatusServiceUnavailable)
116+
}
117+
})
118+
119+
t.Run("status returns 503 when dependencies missing", func(t *testing.T) {
120+
req, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/recovery/actions/a1/status", nil)
121+
req.Header.Set(recoveryHeaderToken, "secret")
122+
resp, err := http.DefaultClient.Do(req)
123+
if err != nil {
124+
t.Fatalf("request failed: %v", err)
125+
}
126+
defer resp.Body.Close()
127+
if resp.StatusCode != http.StatusServiceUnavailable {
128+
t.Fatalf("status=%d want=%d", resp.StatusCode, http.StatusServiceUnavailable)
129+
}
130+
})
131+
}
132+
133+
func TestRecoveryAdminIntegration_SemaphoreThrottling(t *testing.T) {
134+
ra := &recoveryAdmin{
135+
enabled: true,
136+
cascadeFactory: stubCascadeFactory{},
137+
p2pClient: stubP2PClient{},
138+
reseedSem: make(chan struct{}, 1),
139+
statusSem: make(chan struct{}, 1),
140+
}
141+
ts, cleanup := setupRecoveryServer(ra, "secret")
142+
defer cleanup()
143+
144+
t.Run("reseed returns 429 while in-progress", func(t *testing.T) {
145+
ra.reseedSem <- struct{}{}
146+
defer func() { <-ra.reseedSem }()
147+
148+
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/v1/recovery/reseed?action_id=a1", nil)
149+
req.Header.Set(recoveryHeaderToken, "secret")
150+
resp, err := http.DefaultClient.Do(req)
151+
if err != nil {
152+
t.Fatalf("request failed: %v", err)
153+
}
154+
defer resp.Body.Close()
155+
if resp.StatusCode != http.StatusTooManyRequests {
156+
t.Fatalf("status=%d want=%d", resp.StatusCode, http.StatusTooManyRequests)
157+
}
158+
})
159+
160+
t.Run("status returns 429 while in-progress", func(t *testing.T) {
161+
ra.statusSem <- struct{}{}
162+
defer func() { <-ra.statusSem }()
163+
164+
req, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/recovery/actions/a1/status", nil)
165+
req.Header.Set(recoveryHeaderToken, "secret")
166+
resp, err := http.DefaultClient.Do(req)
167+
if err != nil {
168+
t.Fatalf("request failed: %v", err)
169+
}
170+
defer resp.Body.Close()
171+
if resp.StatusCode != http.StatusTooManyRequests {
172+
t.Fatalf("status=%d want=%d", resp.StatusCode, http.StatusTooManyRequests)
173+
}
174+
})
175+
}

0 commit comments

Comments
 (0)