1 package webserver
2
3 import (
4 "bytes"
5 "errors"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11
12 "github.com/cybertec-postgresql/pgwatch/v5/internal/metrics"
13 "github.com/cybertec-postgresql/pgwatch/v5/internal/testutil"
14 jsoniter "github.com/json-iterator/go"
15 "github.com/stretchr/testify/assert"
16 )
17
18 func newTestMetricServer(mrw *testutil.MockMetricsReaderWriter) *WebUIServer {
19 return &WebUIServer{
20 metricsReaderWriter: mrw,
21 }
22 }
23
24 func TestHandleMetrics(t *testing.T) {
25 t.Run("GET", func(t *testing.T) {
26 t.Run("Success", func(t *testing.T) {
27 mock := &testutil.MockMetricsReaderWriter{
28 GetMetricsFunc: func() (*metrics.Metrics, error) {
29 return &metrics.Metrics{MetricDefs: map[string]metrics.Metric{"foo": {Description: "foo"}}}, nil
30 },
31 }
32 ts := newTestMetricServer(mock)
33 r := httptest.NewRequest(http.MethodGet, "/metric", nil)
34 w := httptest.NewRecorder()
35 ts.handleMetrics(w, r)
36 resp := w.Result()
37 defer resp.Body.Close()
38 assert.Equal(t, http.StatusOK, resp.StatusCode)
39 body, _ := io.ReadAll(resp.Body)
40 var got map[string]metrics.Metric
41 assert.NoError(t, jsoniter.ConfigFastest.Unmarshal(body, &got))
42 assert.Contains(t, got, "foo")
43 })
44
45 t.Run("Failure", func(t *testing.T) {
46 mock := &testutil.MockMetricsReaderWriter{
47 GetMetricsFunc: func() (*metrics.Metrics, error) {
48 return nil, errors.New("fail")
49 },
50 }
51 ts := newTestMetricServer(mock)
52 r := httptest.NewRequest(http.MethodGet, "/metric", nil)
53 w := httptest.NewRecorder()
54 ts.handleMetrics(w, r)
55 resp := w.Result()
56 defer resp.Body.Close()
57 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
58 body, _ := io.ReadAll(resp.Body)
59 assert.Contains(t, string(body), "fail")
60 })
61 })
62
63 t.Run("POST", func(t *testing.T) {
64 t.Run("Success", func(t *testing.T) {
65 var createdName string
66 var createdMetric metrics.Metric
67 mock := &testutil.MockMetricsReaderWriter{
68 CreateMetricFunc: func(name string, m metrics.Metric) error {
69 createdName = name
70 createdMetric = m
71 return nil
72 },
73 }
74 ts := newTestMetricServer(mock)
75
76
77 metricData := map[string]metrics.Metric{
78 "bar": {Description: "bar"},
79 }
80 b, _ := jsoniter.ConfigFastest.Marshal(metricData)
81 r := httptest.NewRequest(http.MethodPost, "/metric", bytes.NewReader(b))
82 w := httptest.NewRecorder()
83 ts.handleMetrics(w, r)
84 resp := w.Result()
85 defer resp.Body.Close()
86 assert.Equal(t, http.StatusCreated, resp.StatusCode)
87 assert.Equal(t, "bar", createdName)
88 assert.Equal(t, metrics.Metric{Description: "bar"}, createdMetric)
89 })
90
91 t.Run("ReaderFailure", func(t *testing.T) {
92 mock := &testutil.MockMetricsReaderWriter{
93 CreateMetricFunc: func(_ string, _ metrics.Metric) error {
94 return nil
95 },
96 }
97 ts := newTestMetricServer(mock)
98 r := httptest.NewRequest(http.MethodPost, "/metric", &errorReader{})
99 w := httptest.NewRecorder()
100 ts.handleMetrics(w, r)
101 resp := w.Result()
102 defer resp.Body.Close()
103 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
104 body, _ := io.ReadAll(resp.Body)
105 assert.Contains(t, string(body), "mock read error")
106 })
107
108 t.Run("CreateFailure", func(t *testing.T) {
109 mock := &testutil.MockMetricsReaderWriter{
110 CreateMetricFunc: func(_ string, _ metrics.Metric) error {
111 return errors.New("fail")
112 },
113 }
114 ts := newTestMetricServer(mock)
115 metricData := map[string]metrics.Metric{
116 "bar": {Description: "bar"},
117 }
118 b, _ := jsoniter.ConfigFastest.Marshal(metricData)
119 r := httptest.NewRequest(http.MethodPost, "/metric", bytes.NewReader(b))
120 w := httptest.NewRecorder()
121 ts.handleMetrics(w, r)
122 resp := w.Result()
123 defer resp.Body.Close()
124 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
125 body, _ := io.ReadAll(resp.Body)
126 assert.Contains(t, string(body), "fail")
127 })
128
129 t.Run("Conflict", func(t *testing.T) {
130 mock := &testutil.MockMetricsReaderWriter{
131 CreateMetricFunc: func(_ string, _ metrics.Metric) error {
132 return metrics.ErrMetricExists
133 },
134 }
135 ts := newTestMetricServer(mock)
136 metricData := map[string]metrics.Metric{
137 "bar": {Description: "bar"},
138 }
139 b, _ := jsoniter.ConfigFastest.Marshal(metricData)
140 r := httptest.NewRequest(http.MethodPost, "/metric", bytes.NewReader(b))
141 w := httptest.NewRecorder()
142 ts.handleMetrics(w, r)
143 resp := w.Result()
144 defer resp.Body.Close()
145 assert.Equal(t, http.StatusConflict, resp.StatusCode)
146 body, _ := io.ReadAll(resp.Body)
147 assert.Contains(t, string(body), "metric already exists")
148 })
149 })
150
151 t.Run("OPTIONS", func(t *testing.T) {
152 mock := &testutil.MockMetricsReaderWriter{}
153 ts := newTestMetricServer(mock)
154 r := httptest.NewRequest(http.MethodOptions, "/metric", nil)
155 w := httptest.NewRecorder()
156 ts.handleMetrics(w, r)
157 resp := w.Result()
158 defer resp.Body.Close()
159 assert.Equal(t, http.StatusOK, resp.StatusCode)
160 assert.Equal(t, "GET, POST, OPTIONS", resp.Header.Get("Allow"))
161 })
162
163 t.Run("MethodNotAllowed", func(t *testing.T) {
164 mock := &testutil.MockMetricsReaderWriter{}
165 ts := newTestMetricServer(mock)
166 r := httptest.NewRequest(http.MethodPut, "/metric", nil)
167 w := httptest.NewRecorder()
168 ts.handleMetrics(w, r)
169 resp := w.Result()
170 defer resp.Body.Close()
171 assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
172 assert.Equal(t, "GET, POST, OPTIONS", resp.Header.Get("Allow"))
173 })
174 }
175
176 type errorReader struct{}
177
178 func (e *errorReader) Read([]byte) (n int, err error) {
179 return 0, errors.New("mock read error")
180 }
181
182 func TestGetMetrics_Error(t *testing.T) {
183 mock := &testutil.MockMetricsReaderWriter{
184 GetMetricsFunc: func() (*metrics.Metrics, error) {
185 return nil, errors.New("fail")
186 },
187 }
188 ts := newTestMetricServer(mock)
189 _, err := ts.GetMetrics()
190 assert.Error(t, err)
191 }
192
193 func TestUpdateMetric_Error(t *testing.T) {
194 mock := &testutil.MockMetricsReaderWriter{
195 UpdateMetricFunc: func(_ string, _ metrics.Metric) error {
196 return errors.New("fail")
197 },
198 }
199 ts := newTestMetricServer(mock)
200 err := ts.UpdateMetric("foo", []byte("notjson"))
201 assert.Error(t, err)
202 }
203
204 func TestDeleteMetric_Error(t *testing.T) {
205 mock := &testutil.MockMetricsReaderWriter{
206 DeleteMetricFunc: func(_ string) error {
207 return errors.New("fail")
208 },
209 }
210 ts := newTestMetricServer(mock)
211 err := ts.DeleteMetric("foo")
212 assert.Error(t, err)
213 }
214
215 func TestHandlePresets(t *testing.T) {
216 t.Run("GET", func(t *testing.T) {
217 t.Run("Success", func(t *testing.T) {
218 mock := &testutil.MockMetricsReaderWriter{
219 GetMetricsFunc: func() (*metrics.Metrics, error) {
220 return &metrics.Metrics{PresetDefs: map[string]metrics.Preset{"foo": {Description: "foo"}}}, nil
221 },
222 }
223 ts := newTestMetricServer(mock)
224 r := httptest.NewRequest(http.MethodGet, "/preset", nil)
225 w := httptest.NewRecorder()
226 ts.handlePresets(w, r)
227 resp := w.Result()
228 defer resp.Body.Close()
229 assert.Equal(t, http.StatusOK, resp.StatusCode)
230 body, _ := io.ReadAll(resp.Body)
231 var got map[string]metrics.Preset
232 assert.NoError(t, jsoniter.ConfigFastest.Unmarshal(body, &got))
233 assert.Contains(t, got, "foo")
234 })
235
236 t.Run("Failure", func(t *testing.T) {
237 mock := &testutil.MockMetricsReaderWriter{
238 GetMetricsFunc: func() (*metrics.Metrics, error) {
239 return nil, errors.New("fail")
240 },
241 }
242 ts := newTestMetricServer(mock)
243 r := httptest.NewRequest(http.MethodGet, "/preset", nil)
244 w := httptest.NewRecorder()
245 ts.handlePresets(w, r)
246 resp := w.Result()
247 defer resp.Body.Close()
248 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
249 body, _ := io.ReadAll(resp.Body)
250 assert.Contains(t, string(body), "fail")
251 })
252 })
253
254 t.Run("POST", func(t *testing.T) {
255 t.Run("Success", func(t *testing.T) {
256 var createdName string
257 var createdPreset metrics.Preset
258 mock := &testutil.MockMetricsReaderWriter{
259 CreatePresetFunc: func(name string, p metrics.Preset) error {
260 createdName = name
261 createdPreset = p
262 return nil
263 },
264 }
265 ts := newTestMetricServer(mock)
266 p := metrics.Preset{Description: "bar"}
267
268 presetMap := map[string]metrics.Preset{"bar": p}
269 b, _ := jsoniter.ConfigFastest.Marshal(presetMap)
270 r := httptest.NewRequest(http.MethodPost, "/preset", bytes.NewReader(b))
271 w := httptest.NewRecorder()
272 ts.handlePresets(w, r)
273 resp := w.Result()
274 defer resp.Body.Close()
275 assert.Equal(t, http.StatusCreated, resp.StatusCode)
276 assert.Equal(t, "bar", createdName)
277 assert.Equal(t, p, createdPreset)
278 })
279
280 t.Run("ReaderFailure", func(t *testing.T) {
281 mock := &testutil.MockMetricsReaderWriter{
282 UpdatePresetFunc: func(string, metrics.Preset) error {
283 return nil
284 },
285 }
286 ts := newTestMetricServer(mock)
287 r := httptest.NewRequest(http.MethodPost, "/preset?name=bar", &errorReader{})
288 w := httptest.NewRecorder()
289 ts.handlePresets(w, r)
290 resp := w.Result()
291 defer resp.Body.Close()
292 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
293 body, _ := io.ReadAll(resp.Body)
294 assert.Contains(t, string(body), "mock read error")
295 })
296
297 t.Run("CreateFailure", func(t *testing.T) {
298 mock := &testutil.MockMetricsReaderWriter{
299 CreatePresetFunc: func(string, metrics.Preset) error {
300 return errors.New("fail")
301 },
302 }
303 ts := newTestMetricServer(mock)
304 p := metrics.Preset{Description: "bar"}
305
306 presetMap := map[string]metrics.Preset{"bar": p}
307 b, _ := jsoniter.ConfigFastest.Marshal(presetMap)
308 r := httptest.NewRequest(http.MethodPost, "/preset", bytes.NewReader(b))
309 w := httptest.NewRecorder()
310 ts.handlePresets(w, r)
311 resp := w.Result()
312 defer resp.Body.Close()
313 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
314 body, _ := io.ReadAll(resp.Body)
315 assert.Contains(t, string(body), "fail")
316 })
317 })
318
319 t.Run("OPTIONS", func(t *testing.T) {
320 mock := &testutil.MockMetricsReaderWriter{}
321 ts := newTestMetricServer(mock)
322 r := httptest.NewRequest(http.MethodOptions, "/preset", nil)
323 w := httptest.NewRecorder()
324 ts.handlePresets(w, r)
325 resp := w.Result()
326 defer resp.Body.Close()
327 assert.Equal(t, http.StatusOK, resp.StatusCode)
328 assert.Equal(t, "GET, POST, OPTIONS", resp.Header.Get("Allow"))
329 })
330
331 t.Run("MethodNotAllowed", func(t *testing.T) {
332 mock := &testutil.MockMetricsReaderWriter{}
333 ts := newTestMetricServer(mock)
334 r := httptest.NewRequest(http.MethodPut, "/preset", nil)
335 w := httptest.NewRecorder()
336 ts.handlePresets(w, r)
337 resp := w.Result()
338 defer resp.Body.Close()
339 assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
340 assert.Equal(t, "GET, POST, OPTIONS", resp.Header.Get("Allow"))
341 })
342 }
343
344 func TestGetPresets_Error(t *testing.T) {
345 mock := &testutil.MockMetricsReaderWriter{
346 GetMetricsFunc: func() (*metrics.Metrics, error) {
347 return nil, errors.New("fail")
348 },
349 }
350 ts := newTestMetricServer(mock)
351 _, err := ts.GetPresets()
352 assert.Error(t, err)
353 }
354
355 func TestUpdatePreset_Error(t *testing.T) {
356 mock := &testutil.MockMetricsReaderWriter{
357 UpdatePresetFunc: func(string, metrics.Preset) error {
358 return errors.New("fail")
359 },
360 }
361 ts := newTestMetricServer(mock)
362 err := ts.UpdatePreset("foo", []byte("notjson"))
363 assert.Error(t, err)
364 }
365
366 func TestDeletePreset_Error(t *testing.T) {
367 mock := &testutil.MockMetricsReaderWriter{
368 DeletePresetFunc: func(string) error {
369 return errors.New("fail")
370 },
371 }
372 ts := newTestMetricServer(mock)
373 err := ts.DeletePreset("foo")
374 assert.Error(t, err)
375 }
376
377
378 func newMetricItemRequest(method, name string, body io.Reader) *http.Request {
379 url := "/metric/" + name
380 r := httptest.NewRequest(method, url, body)
381 r.SetPathValue("name", name)
382 return r
383 }
384
385 func TestHandleMetricItem(t *testing.T) {
386 t.Run("GET", func(t *testing.T) {
387 t.Run("Success", func(t *testing.T) {
388 metric := metrics.Metric{Description: "test metric", SQLs: map[int]string{130000: "SELECT 1"}}
389 mock := &testutil.MockMetricsReaderWriter{
390 GetMetricsFunc: func() (*metrics.Metrics, error) {
391 return &metrics.Metrics{
392 MetricDefs: map[string]metrics.Metric{"test-metric": metric},
393 }, nil
394 },
395 }
396 ts := newTestMetricServer(mock)
397 r := newMetricItemRequest(http.MethodGet, "test-metric", nil)
398 w := httptest.NewRecorder()
399 ts.handleMetricItem(w, r)
400 resp := w.Result()
401 defer resp.Body.Close()
402 assert.Equal(t, http.StatusOK, resp.StatusCode)
403 assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
404
405 var returnedMetric metrics.Metric
406 body, _ := io.ReadAll(resp.Body)
407 assert.NoError(t, jsoniter.ConfigFastest.Unmarshal(body, &returnedMetric))
408 assert.Equal(t, metric.Description, returnedMetric.Description)
409 })
410
411 t.Run("NotFound", func(t *testing.T) {
412 mock := &testutil.MockMetricsReaderWriter{
413 GetMetricsFunc: func() (*metrics.Metrics, error) {
414 return &metrics.Metrics{MetricDefs: map[string]metrics.Metric{}}, nil
415 },
416 }
417 ts := newTestMetricServer(mock)
418 r := newMetricItemRequest(http.MethodGet, "nonexistent", nil)
419 w := httptest.NewRecorder()
420 ts.handleMetricItem(w, r)
421 resp := w.Result()
422 defer resp.Body.Close()
423 assert.Equal(t, http.StatusNotFound, resp.StatusCode)
424 body, _ := io.ReadAll(resp.Body)
425 assert.Contains(t, string(body), "metric not found")
426 })
427
428 t.Run("GetMetricsError", func(t *testing.T) {
429 mock := &testutil.MockMetricsReaderWriter{
430 GetMetricsFunc: func() (*metrics.Metrics, error) {
431 return nil, errors.New("database connection failed")
432 },
433 }
434 ts := newTestMetricServer(mock)
435 r := newMetricItemRequest(http.MethodGet, "test-metric", nil)
436 w := httptest.NewRecorder()
437 ts.handleMetricItem(w, r)
438 resp := w.Result()
439 defer resp.Body.Close()
440 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
441 body, _ := io.ReadAll(resp.Body)
442 assert.Contains(t, string(body), "database connection failed")
443 })
444 })
445
446 t.Run("PUT", func(t *testing.T) {
447 t.Run("Success", func(t *testing.T) {
448 var updatedName string
449 var updatedMetric metrics.Metric
450 mock := &testutil.MockMetricsReaderWriter{
451 UpdateMetricFunc: func(name string, m metrics.Metric) error {
452 updatedName = name
453 updatedMetric = m
454 return nil
455 },
456 }
457 ts := newTestMetricServer(mock)
458
459 metric := metrics.Metric{Description: "updated metric", SQLs: map[int]string{130000: "SELECT 2"}}
460 b, _ := jsoniter.ConfigFastest.Marshal(metric)
461 r := newMetricItemRequest(http.MethodPut, "test-metric", bytes.NewReader(b))
462 w := httptest.NewRecorder()
463 ts.handleMetricItem(w, r)
464 resp := w.Result()
465 defer resp.Body.Close()
466 assert.Equal(t, http.StatusOK, resp.StatusCode)
467 assert.Equal(t, "test-metric", updatedName)
468 assert.Equal(t, metric.Description, updatedMetric.Description)
469 })
470
471 t.Run("InvalidRequestBody", func(t *testing.T) {
472 mock := &testutil.MockMetricsReaderWriter{}
473 ts := newTestMetricServer(mock)
474 r := newMetricItemRequest(http.MethodPut, "test-metric", &errorReader{})
475 w := httptest.NewRecorder()
476 ts.handleMetricItem(w, r)
477 resp := w.Result()
478 defer resp.Body.Close()
479 assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
480 body, _ := io.ReadAll(resp.Body)
481 assert.Contains(t, string(body), "mock read error")
482 })
483
484 t.Run("InvalidJSON", func(t *testing.T) {
485 mock := &testutil.MockMetricsReaderWriter{}
486 ts := newTestMetricServer(mock)
487 r := newMetricItemRequest(http.MethodPut, "test-metric", strings.NewReader("invalid json"))
488 w := httptest.NewRecorder()
489 ts.handleMetricItem(w, r)
490 resp := w.Result()
491 defer resp.Body.Close()
492 assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
493 body, _ := io.ReadAll(resp.Body)
494 assert.Contains(t, string(body), "invalid json")
495 })
496
497 t.Run("UpdateError", func(t *testing.T) {
498 mock := &testutil.MockMetricsReaderWriter{
499 UpdateMetricFunc: func(string, metrics.Metric) error {
500 return errors.New("update operation failed")
501 },
502 }
503 ts := newTestMetricServer(mock)
504
505 metric := metrics.Metric{Description: "test metric"}
506 b, _ := jsoniter.ConfigFastest.Marshal(metric)
507 r := newMetricItemRequest(http.MethodPut, "test-metric", bytes.NewReader(b))
508 w := httptest.NewRecorder()
509 ts.handleMetricItem(w, r)
510 resp := w.Result()
511 defer resp.Body.Close()
512 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
513 body, _ := io.ReadAll(resp.Body)
514 assert.Contains(t, string(body), "update operation failed")
515 })
516 })
517
518 t.Run("DELETE", func(t *testing.T) {
519 t.Run("Success", func(t *testing.T) {
520 var deletedName string
521 mock := &testutil.MockMetricsReaderWriter{
522 DeleteMetricFunc: func(name string) error {
523 deletedName = name
524 return nil
525 },
526 }
527 ts := newTestMetricServer(mock)
528 r := newMetricItemRequest(http.MethodDelete, "test-metric", nil)
529 w := httptest.NewRecorder()
530 ts.handleMetricItem(w, r)
531 resp := w.Result()
532 defer resp.Body.Close()
533 assert.Equal(t, http.StatusOK, resp.StatusCode)
534 assert.Equal(t, "test-metric", deletedName)
535 })
536
537 t.Run("DeleteError", func(t *testing.T) {
538 mock := &testutil.MockMetricsReaderWriter{
539 DeleteMetricFunc: func(string) error {
540 return errors.New("delete operation failed")
541 },
542 }
543 ts := newTestMetricServer(mock)
544 r := newMetricItemRequest(http.MethodDelete, "test-metric", nil)
545 w := httptest.NewRecorder()
546 ts.handleMetricItem(w, r)
547 resp := w.Result()
548 defer resp.Body.Close()
549 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
550 body, _ := io.ReadAll(resp.Body)
551 assert.Contains(t, string(body), "delete operation failed")
552 })
553 })
554
555 t.Run("EmptyName", func(t *testing.T) {
556 mock := &testutil.MockMetricsReaderWriter{}
557 ts := newTestMetricServer(mock)
558 r := newMetricItemRequest(http.MethodGet, "", nil)
559 w := httptest.NewRecorder()
560 ts.handleMetricItem(w, r)
561 resp := w.Result()
562 defer resp.Body.Close()
563 assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
564 body, _ := io.ReadAll(resp.Body)
565 assert.Contains(t, string(body), "metric name is required")
566 })
567
568 t.Run("OPTIONS", func(t *testing.T) {
569 mock := &testutil.MockMetricsReaderWriter{}
570 ts := newTestMetricServer(mock)
571 r := newMetricItemRequest(http.MethodOptions, "test", nil)
572 w := httptest.NewRecorder()
573 ts.handleMetricItem(w, r)
574 resp := w.Result()
575 defer resp.Body.Close()
576 assert.Equal(t, http.StatusOK, resp.StatusCode)
577 assert.Equal(t, "GET, PUT, DELETE, OPTIONS", resp.Header.Get("Allow"))
578 })
579
580 t.Run("MethodNotAllowed", func(t *testing.T) {
581 mock := &testutil.MockMetricsReaderWriter{}
582 ts := newTestMetricServer(mock)
583 r := newMetricItemRequest(http.MethodPost, "test", nil)
584 w := httptest.NewRecorder()
585 ts.handleMetricItem(w, r)
586 resp := w.Result()
587 defer resp.Body.Close()
588 assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
589 assert.Equal(t, "GET, PUT, DELETE, OPTIONS", resp.Header.Get("Allow"))
590 })
591 }
592
593
594 func newPresetItemRequest(method, name string, body io.Reader) *http.Request {
595 url := "/preset/" + name
596 r := httptest.NewRequest(method, url, body)
597 r.SetPathValue("name", name)
598 return r
599 }
600
601
602
603 func TestHandlePresetItem(t *testing.T) {
604 t.Run("GET", func(t *testing.T) {
605 t.Run("Success", func(t *testing.T) {
606 preset := metrics.Preset{Description: "test preset", Metrics: map[string]float64{"cpu": 1.0}}
607 mock := &testutil.MockMetricsReaderWriter{
608 GetMetricsFunc: func() (*metrics.Metrics, error) {
609 return &metrics.Metrics{
610 PresetDefs: map[string]metrics.Preset{"test-preset": preset},
611 }, nil
612 },
613 }
614 ts := newTestMetricServer(mock)
615 r := newPresetItemRequest(http.MethodGet, "test-preset", nil)
616 w := httptest.NewRecorder()
617 ts.handlePresetItem(w, r)
618 resp := w.Result()
619 defer resp.Body.Close()
620 assert.Equal(t, http.StatusOK, resp.StatusCode)
621 assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
622
623 var returnedPreset metrics.Preset
624 body, _ := io.ReadAll(resp.Body)
625 assert.NoError(t, jsoniter.ConfigFastest.Unmarshal(body, &returnedPreset))
626 assert.Equal(t, preset.Description, returnedPreset.Description)
627 })
628
629 t.Run("NotFound", func(t *testing.T) {
630 mock := &testutil.MockMetricsReaderWriter{
631 GetMetricsFunc: func() (*metrics.Metrics, error) {
632 return &metrics.Metrics{PresetDefs: map[string]metrics.Preset{}}, nil
633 },
634 }
635 ts := newTestMetricServer(mock)
636 r := newPresetItemRequest(http.MethodGet, "nonexistent", nil)
637 w := httptest.NewRecorder()
638 ts.handlePresetItem(w, r)
639 resp := w.Result()
640 defer resp.Body.Close()
641 assert.Equal(t, http.StatusNotFound, resp.StatusCode)
642 body, _ := io.ReadAll(resp.Body)
643 assert.Contains(t, string(body), "preset not found")
644 })
645
646 t.Run("GetMetricsError", func(t *testing.T) {
647 mock := &testutil.MockMetricsReaderWriter{
648 GetMetricsFunc: func() (*metrics.Metrics, error) {
649 return nil, errors.New("database connection failed")
650 },
651 }
652 ts := newTestMetricServer(mock)
653 r := newPresetItemRequest(http.MethodGet, "test-preset", nil)
654 w := httptest.NewRecorder()
655 ts.handlePresetItem(w, r)
656 resp := w.Result()
657 defer resp.Body.Close()
658 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
659 body, _ := io.ReadAll(resp.Body)
660 assert.Contains(t, string(body), "database connection failed")
661 })
662 })
663
664 t.Run("PUT", func(t *testing.T) {
665 t.Run("Success", func(t *testing.T) {
666 var updatedName string
667 var updatedPreset metrics.Preset
668 mock := &testutil.MockMetricsReaderWriter{
669 UpdatePresetFunc: func(name string, p metrics.Preset) error {
670 updatedName = name
671 updatedPreset = p
672 return nil
673 },
674 }
675 ts := newTestMetricServer(mock)
676
677 preset := metrics.Preset{Description: "updated preset", Metrics: map[string]float64{"memory": 2.0}}
678 b, _ := jsoniter.ConfigFastest.Marshal(preset)
679 r := newPresetItemRequest(http.MethodPut, "test-preset", bytes.NewReader(b))
680 w := httptest.NewRecorder()
681 ts.handlePresetItem(w, r)
682 resp := w.Result()
683 defer resp.Body.Close()
684 assert.Equal(t, http.StatusOK, resp.StatusCode)
685 assert.Equal(t, "test-preset", updatedName)
686 assert.Equal(t, preset.Description, updatedPreset.Description)
687 })
688
689 t.Run("InvalidRequestBody", func(t *testing.T) {
690 mock := &testutil.MockMetricsReaderWriter{}
691 ts := newTestMetricServer(mock)
692 r := newPresetItemRequest(http.MethodPut, "test-preset", &errorReader{})
693 w := httptest.NewRecorder()
694 ts.handlePresetItem(w, r)
695 resp := w.Result()
696 defer resp.Body.Close()
697 assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
698 body, _ := io.ReadAll(resp.Body)
699 assert.Contains(t, string(body), "invalid request body")
700 })
701
702 t.Run("InvalidJSON", func(t *testing.T) {
703 mock := &testutil.MockMetricsReaderWriter{}
704 ts := newTestMetricServer(mock)
705 r := newPresetItemRequest(http.MethodPut, "test-preset", strings.NewReader("invalid json"))
706 w := httptest.NewRecorder()
707 ts.handlePresetItem(w, r)
708 resp := w.Result()
709 defer resp.Body.Close()
710 assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
711 body, _ := io.ReadAll(resp.Body)
712 assert.Contains(t, string(body), "invalid JSON format")
713 })
714
715 t.Run("UpdateError", func(t *testing.T) {
716 mock := &testutil.MockMetricsReaderWriter{
717 UpdatePresetFunc: func(string, metrics.Preset) error {
718 return errors.New("update operation failed")
719 },
720 }
721 ts := newTestMetricServer(mock)
722
723 preset := metrics.Preset{Description: "test preset"}
724 b, _ := jsoniter.ConfigFastest.Marshal(preset)
725 r := newPresetItemRequest(http.MethodPut, "test-preset", bytes.NewReader(b))
726 w := httptest.NewRecorder()
727 ts.handlePresetItem(w, r)
728 resp := w.Result()
729 defer resp.Body.Close()
730 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
731 body, _ := io.ReadAll(resp.Body)
732 assert.Contains(t, string(body), "update operation failed")
733 })
734 })
735
736 t.Run("DELETE", func(t *testing.T) {
737 t.Run("Success", func(t *testing.T) {
738 var deletedName string
739 mock := &testutil.MockMetricsReaderWriter{
740 DeletePresetFunc: func(name string) error {
741 deletedName = name
742 return nil
743 },
744 }
745 ts := newTestMetricServer(mock)
746 r := newPresetItemRequest(http.MethodDelete, "test-preset", nil)
747 w := httptest.NewRecorder()
748 ts.handlePresetItem(w, r)
749 resp := w.Result()
750 defer resp.Body.Close()
751 assert.Equal(t, http.StatusOK, resp.StatusCode)
752 assert.Equal(t, "test-preset", deletedName)
753 })
754
755 t.Run("DeleteError", func(t *testing.T) {
756 mock := &testutil.MockMetricsReaderWriter{
757 DeletePresetFunc: func(string) error {
758 return errors.New("delete operation failed")
759 },
760 }
761 ts := newTestMetricServer(mock)
762 r := newPresetItemRequest(http.MethodDelete, "test-preset", nil)
763 w := httptest.NewRecorder()
764 ts.handlePresetItem(w, r)
765 resp := w.Result()
766 defer resp.Body.Close()
767 assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
768 body, _ := io.ReadAll(resp.Body)
769 assert.Contains(t, string(body), "delete operation failed")
770 })
771 })
772
773 t.Run("EmptyName", func(t *testing.T) {
774 mock := &testutil.MockMetricsReaderWriter{}
775 ts := newTestMetricServer(mock)
776 r := newPresetItemRequest(http.MethodGet, "", nil)
777 w := httptest.NewRecorder()
778 ts.handlePresetItem(w, r)
779 resp := w.Result()
780 defer resp.Body.Close()
781 assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
782 body, _ := io.ReadAll(resp.Body)
783 assert.Contains(t, string(body), "preset name is required")
784 })
785
786 t.Run("OPTIONS", func(t *testing.T) {
787 mock := &testutil.MockMetricsReaderWriter{}
788 ts := newTestMetricServer(mock)
789 r := newPresetItemRequest(http.MethodOptions, "test", nil)
790 w := httptest.NewRecorder()
791 ts.handlePresetItem(w, r)
792 resp := w.Result()
793 defer resp.Body.Close()
794 assert.Equal(t, http.StatusOK, resp.StatusCode)
795 assert.Equal(t, "GET, PUT, DELETE, OPTIONS", resp.Header.Get("Allow"))
796 })
797
798 t.Run("MethodNotAllowed", func(t *testing.T) {
799 mock := &testutil.MockMetricsReaderWriter{}
800 ts := newTestMetricServer(mock)
801 r := newPresetItemRequest(http.MethodPost, "test", nil)
802 w := httptest.NewRecorder()
803 ts.handlePresetItem(w, r)
804 resp := w.Result()
805 defer resp.Body.Close()
806 assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
807 assert.Equal(t, "GET, PUT, DELETE, OPTIONS", resp.Header.Get("Allow"))
808 })
809 }
810