handlers_test.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package costmodel
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/http/httptest"
  7. "testing"
  8. "time"
  9. "github.com/julienschmidt/httprouter"
  10. coremodel "github.com/opencost/opencost/core/pkg/model/kubemodel"
  11. "github.com/opencost/opencost/core/pkg/opencost"
  12. "github.com/stretchr/testify/require"
  13. )
  14. // mockKubeModelQuerier implements kubeModelQuerier for handler tests.
  15. type mockKubeModelQuerier struct {
  16. results []*coremodel.KubeModelSet
  17. err error
  18. }
  19. func (m *mockKubeModelQuerier) Query(_ opencost.Window) ([]*coremodel.KubeModelSet, error) {
  20. return m.results, m.err
  21. }
  22. // newKubeModelRequest builds a GET request to /kubemodel with the given window query param.
  23. func newKubeModelRequest(window string) *http.Request {
  24. url := "/kubemodel"
  25. if window != "" {
  26. url += "?window=" + window
  27. }
  28. r, _ := http.NewRequest(http.MethodGet, url, nil)
  29. return r
  30. }
  31. func TestKubeModelHandler_NilQuerier_Returns503(t *testing.T) {
  32. a := &Accesses{KubeModelQuerier: nil}
  33. w := httptest.NewRecorder()
  34. a.KubeModelHandler(w, newKubeModelRequest("1d"), httprouter.Params{})
  35. require.Equal(t, http.StatusServiceUnavailable, w.Code)
  36. }
  37. func TestKubeModelHandler_MissingWindow_Returns400(t *testing.T) {
  38. a := &Accesses{KubeModelQuerier: &mockKubeModelQuerier{}}
  39. w := httptest.NewRecorder()
  40. a.KubeModelHandler(w, newKubeModelRequest(""), httprouter.Params{})
  41. require.Equal(t, http.StatusBadRequest, w.Code)
  42. }
  43. func TestKubeModelHandler_InvalidWindow_Returns400(t *testing.T) {
  44. a := &Accesses{KubeModelQuerier: &mockKubeModelQuerier{}}
  45. w := httptest.NewRecorder()
  46. a.KubeModelHandler(w, newKubeModelRequest("notawindow"), httprouter.Params{})
  47. require.Equal(t, http.StatusBadRequest, w.Code)
  48. }
  49. func TestKubeModelHandler_QuerierError_Returns500(t *testing.T) {
  50. a := &Accesses{
  51. KubeModelQuerier: &mockKubeModelQuerier{err: fmt.Errorf("storage unavailable")},
  52. }
  53. w := httptest.NewRecorder()
  54. a.KubeModelHandler(w, newKubeModelRequest("1d"), httprouter.Params{})
  55. require.Equal(t, http.StatusInternalServerError, w.Code)
  56. }
  57. func TestKubeModelHandler_Success_Returns200WithData(t *testing.T) {
  58. now := time.Now().UTC().Truncate(time.Hour)
  59. kms := coremodel.NewMockKubeModelSet(now.Add(-time.Hour), now)
  60. a := &Accesses{
  61. KubeModelQuerier: &mockKubeModelQuerier{results: []*coremodel.KubeModelSet{kms}},
  62. }
  63. w := httptest.NewRecorder()
  64. a.KubeModelHandler(w, newKubeModelRequest("1d"), httprouter.Params{})
  65. require.Equal(t, http.StatusOK, w.Code)
  66. require.Equal(t, "application/json", w.Header().Get("Content-Type"))
  67. // Response must be valid JSON and contain a non-empty data payload.
  68. var body map[string]json.RawMessage
  69. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body))
  70. _, hasData := body["data"]
  71. require.True(t, hasData, "response body should contain a 'data' field")
  72. }
  73. func TestKubeModelHandler_EmptyResults_Returns200(t *testing.T) {
  74. a := &Accesses{
  75. KubeModelQuerier: &mockKubeModelQuerier{results: []*coremodel.KubeModelSet{}},
  76. }
  77. w := httptest.NewRecorder()
  78. a.KubeModelHandler(w, newKubeModelRequest("1d"), httprouter.Params{})
  79. require.Equal(t, http.StatusOK, w.Code)
  80. }