Skip to content

Commit c92f55c

Browse files
committed
test: improve code coverage for client package
- Add comprehensive tests for user and group operations - Add error handling tests for all client methods - Add tests for unmarshal errors and edge cases - Increase client package coverage from 62.2% to 88.3% - Increase total coverage from 17.4% to 24.4%
1 parent 348cf93 commit c92f55c

2 files changed

Lines changed: 773 additions & 0 deletions

File tree

internal/client/errors_test.go

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
package client_test
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
"time"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
13+
"github.com/Trozz/terraform-provider-pocketid/internal/client"
14+
)
15+
16+
func TestClient_CreateClient_UnmarshalError(t *testing.T) {
17+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18+
w.Header().Set("Content-Type", "application/json")
19+
w.WriteHeader(http.StatusOK)
20+
fmt.Fprint(w, `{"invalid json":}`)
21+
}))
22+
defer server.Close()
23+
24+
c, err := client.NewClient(server.URL, "test-token", false, 30)
25+
require.NoError(t, err)
26+
27+
createReq := &client.OIDCClientCreateRequest{
28+
Name: "test-client",
29+
CallbackURLs: []string{"https://example.com/callback"},
30+
IsPublic: false,
31+
PkceEnabled: true,
32+
}
33+
34+
result, err := c.CreateClient(createReq)
35+
assert.Error(t, err)
36+
assert.Nil(t, result)
37+
assert.Contains(t, err.Error(), "error unmarshaling response")
38+
}
39+
40+
func TestClient_UpdateClient_UnmarshalError(t *testing.T) {
41+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
42+
w.Header().Set("Content-Type", "application/json")
43+
w.WriteHeader(http.StatusOK)
44+
fmt.Fprint(w, `{"invalid json":}`)
45+
}))
46+
defer server.Close()
47+
48+
c, err := client.NewClient(server.URL, "test-token", false, 30)
49+
require.NoError(t, err)
50+
51+
updateReq := &client.OIDCClientCreateRequest{
52+
Name: "updated-client",
53+
CallbackURLs: []string{"https://example.com/callback"},
54+
IsPublic: false,
55+
PkceEnabled: true,
56+
}
57+
58+
result, err := c.UpdateClient("test-id", updateReq)
59+
assert.Error(t, err)
60+
assert.Nil(t, result)
61+
assert.Contains(t, err.Error(), "error unmarshaling response")
62+
}
63+
64+
func TestClient_ListClients_UnmarshalError(t *testing.T) {
65+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66+
w.Header().Set("Content-Type", "application/json")
67+
w.WriteHeader(http.StatusOK)
68+
fmt.Fprint(w, `{"data": "should be array"}`)
69+
}))
70+
defer server.Close()
71+
72+
c, err := client.NewClient(server.URL, "test-token", false, 30)
73+
require.NoError(t, err)
74+
75+
result, err := c.ListClients()
76+
assert.Error(t, err)
77+
assert.Nil(t, result)
78+
assert.Contains(t, err.Error(), "error unmarshaling response")
79+
}
80+
81+
func TestClient_GenerateClientSecret_UnmarshalError(t *testing.T) {
82+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
83+
w.Header().Set("Content-Type", "application/json")
84+
w.WriteHeader(http.StatusOK)
85+
fmt.Fprint(w, `{"secret": 123}`) // secret should be string
86+
}))
87+
defer server.Close()
88+
89+
c, err := client.NewClient(server.URL, "test-token", false, 30)
90+
require.NoError(t, err)
91+
92+
_, err = c.GenerateClientSecret("test-id")
93+
assert.Error(t, err)
94+
assert.Contains(t, err.Error(), "error unmarshaling response")
95+
}
96+
97+
func TestClient_CreateUser_UnmarshalError(t *testing.T) {
98+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
99+
w.Header().Set("Content-Type", "application/json")
100+
w.WriteHeader(http.StatusOK)
101+
fmt.Fprint(w, `{"username": 123}`) // username should be string
102+
}))
103+
defer server.Close()
104+
105+
c, err := client.NewClient(server.URL, "test-token", false, 30)
106+
require.NoError(t, err)
107+
108+
createReq := &client.UserCreateRequest{
109+
Username: "testuser",
110+
Email: "test@example.com",
111+
}
112+
113+
result, err := c.CreateUser(createReq)
114+
assert.Error(t, err)
115+
assert.Nil(t, result)
116+
assert.Contains(t, err.Error(), "error unmarshaling response")
117+
}
118+
119+
func TestClient_UpdateUser_UnmarshalError(t *testing.T) {
120+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
121+
w.Header().Set("Content-Type", "application/json")
122+
w.WriteHeader(http.StatusOK)
123+
fmt.Fprint(w, `{"email": []}`) // email should be string
124+
}))
125+
defer server.Close()
126+
127+
c, err := client.NewClient(server.URL, "test-token", false, 30)
128+
require.NoError(t, err)
129+
130+
updateReq := &client.UserCreateRequest{
131+
Username: "testuser",
132+
Email: "test@example.com",
133+
}
134+
135+
result, err := c.UpdateUser("test-id", updateReq)
136+
assert.Error(t, err)
137+
assert.Nil(t, result)
138+
assert.Contains(t, err.Error(), "error unmarshaling response")
139+
}
140+
141+
func TestClient_ListUsers_UnmarshalError(t *testing.T) {
142+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
143+
w.Header().Set("Content-Type", "application/json")
144+
w.WriteHeader(http.StatusOK)
145+
fmt.Fprint(w, `{"data": "not an array", "pagination": {}}`)
146+
}))
147+
defer server.Close()
148+
149+
c, err := client.NewClient(server.URL, "test-token", false, 30)
150+
require.NoError(t, err)
151+
152+
result, err := c.ListUsers()
153+
assert.Error(t, err)
154+
assert.Nil(t, result)
155+
assert.Contains(t, err.Error(), "error unmarshaling response")
156+
}
157+
158+
func TestClient_CreateUserGroup_UnmarshalError(t *testing.T) {
159+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
160+
w.Header().Set("Content-Type", "application/json")
161+
w.WriteHeader(http.StatusOK)
162+
fmt.Fprint(w, `{"name": true}`) // name should be string
163+
}))
164+
defer server.Close()
165+
166+
c, err := client.NewClient(server.URL, "test-token", false, 30)
167+
require.NoError(t, err)
168+
169+
createReq := &client.UserGroupCreateRequest{
170+
Name: "test-group",
171+
FriendlyName: "Test Group",
172+
}
173+
174+
result, err := c.CreateUserGroup(createReq)
175+
assert.Error(t, err)
176+
assert.Nil(t, result)
177+
assert.Contains(t, err.Error(), "error unmarshaling response")
178+
}
179+
180+
func TestClient_UpdateUserGroup_UnmarshalError(t *testing.T) {
181+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
182+
w.Header().Set("Content-Type", "application/json")
183+
w.WriteHeader(http.StatusOK)
184+
fmt.Fprint(w, `{"friendlyName": 123}`) // friendlyName should be string
185+
}))
186+
defer server.Close()
187+
188+
c, err := client.NewClient(server.URL, "test-token", false, 30)
189+
require.NoError(t, err)
190+
191+
updateReq := &client.UserGroupCreateRequest{
192+
Name: "test-group",
193+
FriendlyName: "Test Group",
194+
}
195+
196+
result, err := c.UpdateUserGroup("test-id", updateReq)
197+
assert.Error(t, err)
198+
assert.Nil(t, result)
199+
assert.Contains(t, err.Error(), "error unmarshaling response")
200+
}
201+
202+
func TestClient_GetUser_UnmarshalError(t *testing.T) {
203+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
204+
w.Header().Set("Content-Type", "application/json")
205+
w.WriteHeader(http.StatusOK)
206+
fmt.Fprint(w, `{"id": [], "username": "test"}`) // id should be string
207+
}))
208+
defer server.Close()
209+
210+
c, err := client.NewClient(server.URL, "test-token", false, 30)
211+
require.NoError(t, err)
212+
213+
result, err := c.GetUser("test-id")
214+
assert.Error(t, err)
215+
assert.Nil(t, result)
216+
assert.Contains(t, err.Error(), "error unmarshaling response")
217+
}
218+
219+
func TestClient_GetUserGroup_UnmarshalError(t *testing.T) {
220+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
221+
w.Header().Set("Content-Type", "application/json")
222+
w.WriteHeader(http.StatusOK)
223+
fmt.Fprint(w, `{"id": {}, "name": "test"}`) // id should be string
224+
}))
225+
defer server.Close()
226+
227+
c, err := client.NewClient(server.URL, "test-token", false, 30)
228+
require.NoError(t, err)
229+
230+
result, err := c.GetUserGroup("test-id")
231+
assert.Error(t, err)
232+
assert.Nil(t, result)
233+
assert.Contains(t, err.Error(), "error unmarshaling response")
234+
}
235+
236+
func TestClient_ListUserGroups_UnmarshalError(t *testing.T) {
237+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
238+
w.Header().Set("Content-Type", "application/json")
239+
w.WriteHeader(http.StatusOK)
240+
fmt.Fprint(w, `{"data": {}, "pagination": "invalid"}`)
241+
}))
242+
defer server.Close()
243+
244+
c, err := client.NewClient(server.URL, "test-token", false, 30)
245+
require.NoError(t, err)
246+
247+
result, err := c.ListUserGroups()
248+
assert.Error(t, err)
249+
assert.Nil(t, result)
250+
assert.Contains(t, err.Error(), "error unmarshaling response")
251+
}
252+
253+
// Test rate limiting with Retry-After header (numeric seconds)
254+
func TestClient_RateLimitWithRetryAfterSeconds(t *testing.T) {
255+
attempts := 0
256+
257+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
258+
attempts++
259+
if attempts < 2 {
260+
w.Header().Set("Retry-After", "2") // 2 seconds
261+
w.WriteHeader(http.StatusTooManyRequests)
262+
fmt.Fprint(w, `{"error": "Rate limit exceeded"}`)
263+
return
264+
}
265+
w.Header().Set("Content-Type", "application/json")
266+
fmt.Fprint(w, `{"id": "test-id", "name": "Test Client"}`)
267+
}))
268+
defer server.Close()
269+
270+
c, err := client.NewClient(server.URL, "test-token", false, 30)
271+
require.NoError(t, err)
272+
273+
start := time.Now()
274+
result, err := c.GetClient("test-id")
275+
elapsed := time.Since(start)
276+
277+
assert.NoError(t, err)
278+
assert.NotNil(t, result)
279+
assert.Equal(t, 2, attempts)
280+
// Should wait approximately 2 seconds
281+
assert.True(t, elapsed >= 1900*time.Millisecond && elapsed <= 2500*time.Millisecond,
282+
"Expected wait time around 2 seconds, got %v", elapsed)
283+
}
284+
285+
// Test timeout handling
286+
func TestClient_RequestTimeout(t *testing.T) {
287+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
288+
// Sleep longer than client timeout
289+
time.Sleep(2 * time.Second)
290+
w.WriteHeader(http.StatusOK)
291+
fmt.Fprint(w, `{"id": "test-id"}`)
292+
}))
293+
defer server.Close()
294+
295+
// Create client with 1 second timeout
296+
c, err := client.NewClient(server.URL, "test-token", false, 1)
297+
require.NoError(t, err)
298+
299+
start := time.Now()
300+
result, err := c.GetClient("test-id")
301+
elapsed := time.Since(start)
302+
303+
assert.Error(t, err)
304+
assert.Nil(t, result)
305+
assert.Contains(t, err.Error(), "deadline exceeded")
306+
// Should timeout after approximately 1 second
307+
assert.True(t, elapsed >= 900*time.Millisecond && elapsed <= 1500*time.Millisecond,
308+
"Expected timeout around 1 second, got %v", elapsed)
309+
}
310+
311+
// Test error response with empty body
312+
func TestClient_ErrorResponseEmptyBody(t *testing.T) {
313+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
314+
w.WriteHeader(http.StatusInternalServerError)
315+
// No body
316+
}))
317+
defer server.Close()
318+
319+
c, err := client.NewClient(server.URL, "test-token", false, 30)
320+
require.NoError(t, err)
321+
322+
result, err := c.GetClient("test-id")
323+
assert.Error(t, err)
324+
assert.Nil(t, result)
325+
assert.Contains(t, err.Error(), "HTTP 500")
326+
}
327+
328+
// Test with non-retryable errors
329+
func TestClient_NonRetryableErrors(t *testing.T) {
330+
testCases := []struct {
331+
name string
332+
statusCode int
333+
attempts int
334+
}{
335+
{
336+
name: "400 Bad Request",
337+
statusCode: http.StatusBadRequest,
338+
attempts: 1,
339+
},
340+
{
341+
name: "401 Unauthorized",
342+
statusCode: http.StatusUnauthorized,
343+
attempts: 1,
344+
},
345+
{
346+
name: "403 Forbidden",
347+
statusCode: http.StatusForbidden,
348+
attempts: 1,
349+
},
350+
{
351+
name: "404 Not Found",
352+
statusCode: http.StatusNotFound,
353+
attempts: 1,
354+
},
355+
{
356+
name: "409 Conflict",
357+
statusCode: http.StatusConflict,
358+
attempts: 1,
359+
},
360+
}
361+
362+
for _, tc := range testCases {
363+
t.Run(tc.name, func(t *testing.T) {
364+
attempts := 0
365+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
366+
attempts++
367+
w.WriteHeader(tc.statusCode)
368+
fmt.Fprintf(w, `{"error": "Error %d"}`, tc.statusCode)
369+
}))
370+
defer server.Close()
371+
372+
c, err := client.NewClient(server.URL, "test-token", false, 30)
373+
require.NoError(t, err)
374+
375+
result, err := c.GetClient("test-id")
376+
assert.Error(t, err)
377+
assert.Nil(t, result)
378+
assert.Contains(t, err.Error(), fmt.Sprintf("HTTP %d", tc.statusCode))
379+
assert.Equal(t, tc.attempts, attempts, "Should not retry for %d errors", tc.statusCode)
380+
})
381+
}
382+
}

0 commit comments

Comments
 (0)