Skip to content

Commit 650b19f

Browse files
authored
Merge pull request #1 from lamaral/master
Handle Serveradmin API errors correctly
2 parents 6963fb3 + 1ca48f2 commit 650b19f

File tree

2 files changed

+95
-0
lines changed

2 files changed

+95
-0
lines changed

adminapi/api.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,29 @@ func sendRequest(endpoint string, postData any) (*http.Response, error) {
9999
return nil, err
100100
}
101101

102+
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
103+
defer resp.Body.Close()
104+
105+
bodyBytes, readErr := io.ReadAll(resp.Body)
106+
if readErr != nil {
107+
return nil, fmt.Errorf("HTTP error %d %s (failed to read error details: %w)",
108+
resp.StatusCode, http.StatusText(resp.StatusCode), readErr)
109+
}
110+
111+
var nestedErrorResp struct {
112+
Error struct {
113+
Message string `json:"message"`
114+
} `json:"error"`
115+
}
116+
if jsonErr := json.Unmarshal(bodyBytes, &nestedErrorResp); jsonErr == nil && nestedErrorResp.Error.Message != "" {
117+
return nil, fmt.Errorf("HTTP error %d %s: %s",
118+
resp.StatusCode, http.StatusText(resp.StatusCode), nestedErrorResp.Error.Message)
119+
}
120+
121+
// If body is empty, just return the status code
122+
return nil, fmt.Errorf("HTTP error %d %s", resp.StatusCode, http.StatusText(resp.StatusCode))
123+
}
124+
102125
// If the server responded with gzip encoding, wrap the response body accordingly.
103126
if resp.Header.Get("Content-Encoding") == "gzip" {
104127
gz, err := gzip.NewReader(resp.Body)

adminapi/api_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@ import (
55
"net/http"
66
"net/http/httptest"
77
"os"
8+
"sync"
89
"testing"
910

1011
"github.com/stretchr/testify/assert"
1112
"github.com/stretchr/testify/require"
1213
)
1314

15+
// Because getConfig in config.go calls sync.OnceValues, the new values set to
16+
// SERVERADMIN_BASE_URL between test runs is never changed, as getConfig returns
17+
// cached values.
18+
// We use resetConfig() to reinitialize things, forcing getConfig() to return the
19+
// values from the new env variables.
20+
func resetConfig() {
21+
getConfig = sync.OnceValues(loadConfig)
22+
}
23+
1424
func TestFakeServer(t *testing.T) {
1525
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1626
req, _ := io.ReadAll(r.Body)
@@ -26,6 +36,7 @@ func TestFakeServer(t *testing.T) {
2636
}))
2737
defer server.Close()
2838

39+
resetConfig()
2940
os.Clearenv()
3041
_ = os.Setenv("SERVERADMIN_TOKEN", "1234567890")
3142
_ = os.Setenv("SERVERADMIN_BASE_URL", server.URL)
@@ -104,3 +115,64 @@ func BenchmarkCalcSecurityToken(b *testing.B) {
104115
calcSecurityToken(authToken, now, message)
105116
}
106117
}
118+
119+
// TestHTTPErrorHandling verifies that HTTP error codes are properly captured and reported
120+
func TestHTTPErrorHandling(t *testing.T) {
121+
testCases := []struct {
122+
name string
123+
statusCode int
124+
responseBody string
125+
expectedError string
126+
}{
127+
{
128+
name: "400 Bad Request - ValidationError",
129+
statusCode: 400,
130+
responseBody: `{"error": {"message": "Bad Request: Invalid filter format"}}`,
131+
expectedError: "HTTP error 400 Bad Request: Bad Request: Invalid filter format",
132+
},
133+
{
134+
name: "400 Bad Request - FilterValueError",
135+
statusCode: 400,
136+
responseBody: `{"error": {"message": "Bad Request: hostname must be a string"}}`,
137+
expectedError: "HTTP error 400 Bad Request: Bad Request: hostname must be a string",
138+
},
139+
{
140+
name: "403 Forbidden - PermissionDenied",
141+
statusCode: 403,
142+
responseBody: `{"error": {"message": "Forbidden: No known public key found"}}`,
143+
expectedError: "HTTP error 403 Forbidden: Forbidden: No known public key found",
144+
},
145+
{
146+
name: "404 Not Found - ObjectDoesNotExist",
147+
statusCode: 404,
148+
responseBody: `{"error": {"message": "Not Found: Server object with id 12345 does not exist"}}`,
149+
expectedError: "HTTP error 404 Not Found: Not Found: Server object with id 12345 does not exist",
150+
},
151+
}
152+
153+
for _, tc := range testCases {
154+
t.Run(tc.name, func(t *testing.T) {
155+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
156+
w.WriteHeader(tc.statusCode)
157+
_, _ = w.Write([]byte(tc.responseBody))
158+
}))
159+
defer server.Close()
160+
161+
resetConfig()
162+
os.Clearenv()
163+
_ = os.Setenv("SERVERADMIN_TOKEN", "1234567890")
164+
_ = os.Setenv("SERVERADMIN_BASE_URL", server.URL)
165+
166+
query := NewQuery(Filters{
167+
"hostname": Regexp("test.local"),
168+
})
169+
query.SetAttributes([]string{"hostname"})
170+
171+
servers, err := query.All()
172+
require.Error(t, err)
173+
assert.Nil(t, servers)
174+
assert.Contains(t, err.Error(), tc.expectedError)
175+
assert.NotContains(t, err.Error(), "expected exactly one server object")
176+
})
177+
}
178+
}

0 commit comments

Comments
 (0)