Skip to content

Commit df38192

Browse files
authored
Add Ground Control Handler Unit Tests (#325)
1 parent a8657b5 commit df38192

File tree

6 files changed

+1201
-0
lines changed

6 files changed

+1201
-0
lines changed

ground-control/internal/server/cached_images_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"database/sql"
66
"encoding/json"
7+
"fmt"
78
"net/http"
89
"net/http/httptest"
910
"testing"
@@ -246,6 +247,106 @@ func TestGetCachedImagesHandler(t *testing.T) {
246247
})
247248
}
248249

250+
func TestSyncHandler_InvalidBody(t *testing.T) {
251+
server, _ := newMockServer(t)
252+
253+
req := httptest.NewRequest(http.MethodPost, "/satellites/sync", bytes.NewReader([]byte("not json")))
254+
req.Header.Set("Content-Type", "application/json")
255+
256+
rr := httptest.NewRecorder()
257+
server.syncHandler(rr, req)
258+
259+
require.Equal(t, http.StatusBadRequest, rr.Code)
260+
}
261+
262+
func TestSyncHandler_InvalidHeartbeatInterval(t *testing.T) {
263+
server, mock := newMockServer(t)
264+
265+
now := time.Now().UTC().Truncate(time.Second)
266+
267+
satRows := sqlmock.NewRows([]string{"id", "name", "created_at", "updated_at", "last_seen", "heartbeat_interval"}).
268+
AddRow(1, "edge-01", now, now, sql.NullTime{}, sql.NullString{})
269+
mock.ExpectQuery("SELECT .+ FROM satellites WHERE name").
270+
WithArgs("edge-01").
271+
WillReturnRows(satRows)
272+
273+
reqBody := SatelliteStatusParams{
274+
Name: "edge-01",
275+
StateReportInterval: "bad-format",
276+
RequestCreatedTime: now,
277+
}
278+
body, _ := json.Marshal(reqBody)
279+
req := httptest.NewRequest(http.MethodPost, "/satellites/sync", bytes.NewReader(body))
280+
req.Header.Set("Content-Type", "application/json")
281+
282+
rr := httptest.NewRecorder()
283+
server.syncHandler(rr, req)
284+
285+
require.Equal(t, http.StatusBadRequest, rr.Code)
286+
require.NoError(t, mock.ExpectationsWereMet())
287+
}
288+
289+
func TestSyncHandler_BatchInsertArtifactsFails(t *testing.T) {
290+
server, mock := newMockServer(t)
291+
292+
now := time.Now().UTC().Truncate(time.Second)
293+
294+
satRows := sqlmock.NewRows([]string{"id", "name", "created_at", "updated_at", "last_seen", "heartbeat_interval"}).
295+
AddRow(1, "edge-01", now, now, sql.NullTime{}, sql.NullString{})
296+
mock.ExpectQuery("SELECT .+ FROM satellites WHERE name").
297+
WithArgs("edge-01").
298+
WillReturnRows(satRows)
299+
300+
mock.ExpectExec("INSERT INTO artifacts").
301+
WithArgs(
302+
pq.Array([]string{"localhost:8585/nginx:latest@sha256:abc"}),
303+
pq.Array([]int64{50000}),
304+
).
305+
WillReturnError(fmt.Errorf("db connection lost"))
306+
307+
reqBody := SatelliteStatusParams{
308+
Name: "edge-01",
309+
RequestCreatedTime: now,
310+
CachedImages: []CachedImage{
311+
{Reference: "localhost:8585/nginx:latest@sha256:abc", SizeBytes: 50000},
312+
},
313+
}
314+
body, _ := json.Marshal(reqBody)
315+
req := httptest.NewRequest(http.MethodPost, "/satellites/sync", bytes.NewReader(body))
316+
req.Header.Set("Content-Type", "application/json")
317+
318+
rr := httptest.NewRecorder()
319+
server.syncHandler(rr, req)
320+
321+
require.Equal(t, http.StatusInternalServerError, rr.Code)
322+
require.NoError(t, mock.ExpectationsWereMet())
323+
}
324+
325+
func TestGetCachedImagesHandler_DBFailure(t *testing.T) {
326+
server, mock := newMockServer(t)
327+
328+
now := time.Now().UTC().Truncate(time.Second)
329+
330+
satRows := sqlmock.NewRows([]string{"id", "name", "created_at", "updated_at", "last_seen", "heartbeat_interval"}).
331+
AddRow(1, "edge-01", now, now, sql.NullTime{}, sql.NullString{})
332+
mock.ExpectQuery("SELECT .+ FROM satellites WHERE name").
333+
WithArgs("edge-01").
334+
WillReturnRows(satRows)
335+
336+
mock.ExpectQuery("SELECT .+ FROM artifacts").
337+
WithArgs(int32(1)).
338+
WillReturnError(fmt.Errorf("db timeout"))
339+
340+
req := httptest.NewRequest(http.MethodGet, "/api/satellites/edge-01/images", nil)
341+
req = mux.SetURLVars(req, map[string]string{"satellite": "edge-01"})
342+
343+
rr := httptest.NewRecorder()
344+
server.getCachedImagesHandler(rr, req)
345+
346+
require.Equal(t, http.StatusInternalServerError, rr.Code)
347+
require.NoError(t, mock.ExpectationsWereMet())
348+
}
349+
249350
func TestCachedImageJSON(t *testing.T) {
250351
t.Run("serialization roundtrip", func(t *testing.T) {
251352
original := SatelliteStatusParams{
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package server
2+
3+
import (
4+
"bytes"
5+
"database/sql"
6+
"encoding/json"
7+
"fmt"
8+
"net/http"
9+
"net/http/httptest"
10+
"testing"
11+
"time"
12+
13+
"github.com/DATA-DOG/go-sqlmock"
14+
"github.com/gorilla/mux"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func TestListConfigsHandler(t *testing.T) {
19+
t.Run("returns config list", func(t *testing.T) {
20+
server, mock := newMockServer(t)
21+
now := time.Now().UTC().Truncate(time.Second)
22+
23+
rows := sqlmock.NewRows([]string{"id", "config_name", "registry_url", "config", "created_at", "updated_at"}).
24+
AddRow(1, "test-config", "http://harbor:8080", json.RawMessage(`{"app_config":{}}`), now, now).
25+
AddRow(2, "prod-config", "http://harbor:8080", json.RawMessage(`{"app_config":{}}`), now, now)
26+
mock.ExpectQuery("SELECT .+ FROM configs").WillReturnRows(rows)
27+
28+
req := httptest.NewRequest(http.MethodGet, "/api/configs", nil)
29+
rr := httptest.NewRecorder()
30+
server.listConfigsHandler(rr, req)
31+
32+
require.Equal(t, http.StatusOK, rr.Code)
33+
require.Contains(t, rr.Body.String(), "test-config")
34+
require.Contains(t, rr.Body.String(), "prod-config")
35+
require.NoError(t, mock.ExpectationsWereMet())
36+
})
37+
38+
t.Run("empty list", func(t *testing.T) {
39+
server, mock := newMockServer(t)
40+
41+
rows := sqlmock.NewRows([]string{"id", "config_name", "registry_url", "config", "created_at", "updated_at"})
42+
mock.ExpectQuery("SELECT .+ FROM configs").WillReturnRows(rows)
43+
44+
req := httptest.NewRequest(http.MethodGet, "/api/configs", nil)
45+
rr := httptest.NewRecorder()
46+
server.listConfigsHandler(rr, req)
47+
48+
require.Equal(t, http.StatusOK, rr.Code)
49+
require.NoError(t, mock.ExpectationsWereMet())
50+
})
51+
52+
t.Run("db error returns 500", func(t *testing.T) {
53+
server, mock := newMockServer(t)
54+
55+
mock.ExpectQuery("SELECT .+ FROM configs").WillReturnError(fmt.Errorf("db error"))
56+
57+
req := httptest.NewRequest(http.MethodGet, "/api/configs", nil)
58+
rr := httptest.NewRecorder()
59+
server.listConfigsHandler(rr, req)
60+
61+
require.Equal(t, http.StatusInternalServerError, rr.Code)
62+
require.NoError(t, mock.ExpectationsWereMet())
63+
})
64+
}
65+
66+
func TestGetConfigHandler(t *testing.T) {
67+
t.Run("found returns 200", func(t *testing.T) {
68+
server, mock := newMockServer(t)
69+
now := time.Now().UTC().Truncate(time.Second)
70+
71+
rows := sqlmock.NewRows([]string{"id", "config_name", "registry_url", "config", "created_at", "updated_at"}).
72+
AddRow(1, "test-config", "http://harbor:8080", json.RawMessage(`{"app_config":{}}`), now, now)
73+
mock.ExpectQuery("SELECT .+ FROM configs WHERE config_name").
74+
WithArgs("test-config").
75+
WillReturnRows(rows)
76+
77+
req := httptest.NewRequest(http.MethodGet, "/api/configs/test-config", nil)
78+
req = mux.SetURLVars(req, map[string]string{"config": "test-config"})
79+
80+
rr := httptest.NewRecorder()
81+
server.getConfigHandler(rr, req)
82+
83+
require.Equal(t, http.StatusOK, rr.Code)
84+
require.Contains(t, rr.Body.String(), "test-config")
85+
require.NoError(t, mock.ExpectationsWereMet())
86+
})
87+
88+
t.Run("not found returns 404", func(t *testing.T) {
89+
server, mock := newMockServer(t)
90+
91+
mock.ExpectQuery("SELECT .+ FROM configs WHERE config_name").
92+
WithArgs("nonexistent").
93+
WillReturnError(sql.ErrNoRows)
94+
95+
req := httptest.NewRequest(http.MethodGet, "/api/configs/nonexistent", nil)
96+
req = mux.SetURLVars(req, map[string]string{"config": "nonexistent"})
97+
98+
rr := httptest.NewRecorder()
99+
server.getConfigHandler(rr, req)
100+
101+
require.Equal(t, http.StatusNotFound, rr.Code)
102+
require.NoError(t, mock.ExpectationsWereMet())
103+
})
104+
}
105+
106+
func TestCreateConfigHandler_InvalidBody(t *testing.T) {
107+
server, _ := newMockServer(t)
108+
109+
req := httptest.NewRequest(http.MethodPost, "/api/configs", bytes.NewReader([]byte("not json")))
110+
req.Header.Set("Content-Type", "application/json")
111+
112+
rr := httptest.NewRecorder()
113+
server.createConfigHandler(rr, req)
114+
115+
require.Equal(t, http.StatusBadRequest, rr.Code)
116+
}
117+
118+
func TestCreateConfigHandler_EmptyName(t *testing.T) {
119+
server, _ := newMockServer(t)
120+
121+
body, _ := json.Marshal(map[string]string{"config_name": ""})
122+
req := httptest.NewRequest(http.MethodPost, "/api/configs", bytes.NewReader(body))
123+
req.Header.Set("Content-Type", "application/json")
124+
125+
rr := httptest.NewRecorder()
126+
server.createConfigHandler(rr, req)
127+
128+
require.Equal(t, http.StatusBadRequest, rr.Code)
129+
}
130+
131+
func TestDeleteConfigHandler_NotFound(t *testing.T) {
132+
server, mock := newMockServer(t)
133+
134+
mock.ExpectQuery("SELECT .+ FROM configs WHERE config_name").
135+
WithArgs("nonexistent").
136+
WillReturnError(fmt.Errorf("db error"))
137+
138+
req := httptest.NewRequest(http.MethodDelete, "/api/configs/nonexistent", nil)
139+
req = mux.SetURLVars(req, map[string]string{"config": "nonexistent"})
140+
141+
rr := httptest.NewRecorder()
142+
server.deleteConfigHandler(rr, req)
143+
144+
require.Equal(t, http.StatusInternalServerError, rr.Code)
145+
require.NoError(t, mock.ExpectationsWereMet())
146+
}
147+
148+
func TestDeleteConfigHandler_ConfigInUse(t *testing.T) {
149+
server, mock := newMockServer(t)
150+
now := time.Now().UTC().Truncate(time.Second)
151+
152+
mock.ExpectQuery("SELECT .+ FROM configs WHERE config_name").
153+
WithArgs("used-config").
154+
WillReturnRows(sqlmock.NewRows([]string{"id", "config_name", "registry_url", "config", "created_at", "updated_at"}).
155+
AddRow(1, "used-config", "http://harbor:8080", json.RawMessage(`{}`), now, now))
156+
157+
mock.ExpectQuery("SELECT .+ FROM satellite_configs").
158+
WithArgs(int32(1)).
159+
WillReturnRows(sqlmock.NewRows([]string{"satellite_id", "config_id"}).
160+
AddRow(1, 1))
161+
162+
req := httptest.NewRequest(http.MethodDelete, "/api/configs/used-config", nil)
163+
req = mux.SetURLVars(req, map[string]string{"config": "used-config"})
164+
165+
rr := httptest.NewRecorder()
166+
server.deleteConfigHandler(rr, req)
167+
168+
require.Equal(t, http.StatusBadRequest, rr.Code)
169+
require.NoError(t, mock.ExpectationsWereMet())
170+
}

0 commit comments

Comments
 (0)