request_test.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package autocomplete
  2. import (
  3. "errors"
  4. "testing"
  5. "time"
  6. "github.com/opencost/opencost/core/pkg/filter"
  7. "github.com/opencost/opencost/core/pkg/filter/ast"
  8. "github.com/opencost/opencost/core/pkg/opencost"
  9. "github.com/opencost/opencost/core/pkg/util/httputil"
  10. )
  11. func validateTestField(field string) (string, error) {
  12. if field == "" {
  13. return "", ErrBadRequest
  14. }
  15. return field, nil
  16. }
  17. func TestNormalizeRequest(t *testing.T) {
  18. start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
  19. req := &Request{
  20. TenantID: "t1",
  21. Field: "cluster",
  22. Search: " x ",
  23. Limit: 0,
  24. Window: opencost.NewClosedWindow(start, start.Add(24*time.Hour)),
  25. }
  26. field, err := NormalizeRequest(req, validateTestField, NormalizeOptions{RequireTenantID: true})
  27. if err != nil {
  28. t.Fatalf("unexpected error: %v", err)
  29. }
  30. if field != "cluster" || req.Search != "x" || req.Limit != DefaultResultLimit {
  31. t.Fatalf("unexpected normalized request: %+v", req)
  32. }
  33. _, err = NormalizeRequest(req, validateTestField, NormalizeOptions{RequireTenantID: true, EnsureLabelConfig: true})
  34. if err != nil {
  35. t.Fatalf("unexpected error: %v", err)
  36. }
  37. if req.LabelConfig == nil {
  38. t.Fatal("expected default label config")
  39. }
  40. nilFilterReq := &Request{
  41. TenantID: "t1",
  42. Field: "label",
  43. Window: opencost.NewClosedWindow(start, start.Add(24*time.Hour)),
  44. }
  45. _, err = NormalizeRequest(nilFilterReq, validateTestField, NormalizeOptions{RequireTenantID: true})
  46. if err != nil {
  47. t.Fatalf("unexpected error: %v", err)
  48. }
  49. if nilFilterReq.Filter == nil || nilFilterReq.Filter.Op() != ast.FilterOpVoid {
  50. t.Fatalf("expected nil filter normalized to void op, got %+v", nilFilterReq.Filter)
  51. }
  52. _, err = NormalizeRequest(nil, validateTestField, NormalizeOptions{})
  53. if err == nil || !errors.Is(err, ErrBadRequest) {
  54. t.Fatalf("expected nil request error, got %v", err)
  55. }
  56. openReq := &Request{Field: "cluster", Window: opencost.NewWindow(&start, nil)}
  57. _, err = NormalizeRequest(openReq, validateTestField, NormalizeOptions{})
  58. if err == nil || !errors.Is(err, ErrBadRequest) {
  59. t.Fatalf("expected open window error, got %v", err)
  60. }
  61. _, err = NormalizeRequest(&Request{Field: "cluster", Window: req.Window}, validateTestField, NormalizeOptions{RequireTenantID: true})
  62. if err == nil || !errors.Is(err, ErrBadRequest) {
  63. t.Fatalf("expected tenant ID error, got %v", err)
  64. }
  65. limitReq := &Request{
  66. TenantID: "t1",
  67. Field: "cluster",
  68. Window: opencost.NewClosedWindow(start, start.Add(24*time.Hour)),
  69. Limit: MaxResultLimit + 1,
  70. }
  71. _, err = NormalizeRequest(limitReq, validateTestField, NormalizeOptions{RequireTenantID: true})
  72. if err == nil || !errors.Is(err, ErrBadRequest) {
  73. t.Fatalf("expected limit error, got %v", err)
  74. }
  75. }
  76. func TestDefaultWindowValidator(t *testing.T) {
  77. start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
  78. end := start.Add(time.Hour)
  79. if err := DefaultWindowValidator(opencost.NewClosedWindow(start, end)); err != nil {
  80. t.Fatalf("unexpected error: %v", err)
  81. }
  82. if err := DefaultWindowValidator(opencost.NewWindow(&start, nil)); err == nil {
  83. t.Fatal("expected open window error")
  84. }
  85. }
  86. func TestParseRequest(t *testing.T) {
  87. windowStr := "2023-01-01T00:00:00Z,2023-01-02T00:00:00Z"
  88. qp := httputil.NewQueryParams(map[string][]string{
  89. "window": {windowStr},
  90. "field": {"cluster"},
  91. "search": {" ns "},
  92. "tenantId": {"t1"},
  93. })
  94. got, err := ParseRequest(qp, ParseOptions{}, validateTestField, nil)
  95. if err != nil {
  96. t.Fatalf("ParseRequest() error = %v", err)
  97. }
  98. if got.Field != "cluster" || got.Search != "ns" || got.TenantID != "t1" {
  99. t.Fatalf("unexpected request: %+v", got)
  100. }
  101. if got.Filter == nil || got.Filter.Op() != ast.FilterOpVoid {
  102. t.Fatalf("expected void filter when filter param omitted, got %+v", got.Filter)
  103. }
  104. _, err = ParseRequest(httputil.NewQueryParams(map[string][]string{"field": {"cluster"}}), ParseOptions{}, validateTestField, nil)
  105. if err == nil || !errors.Is(err, ErrBadRequest) {
  106. t.Fatalf("expected missing window error, got %v", err)
  107. }
  108. _, err = ParseRequest(httputil.NewQueryParams(map[string][]string{
  109. "window": {"bad"},
  110. "field": {"cluster"},
  111. }), ParseOptions{}, validateTestField, nil)
  112. if err == nil || !errors.Is(err, ErrBadRequest) {
  113. t.Fatalf("expected invalid window error, got %v", err)
  114. }
  115. offset := 5 * time.Hour
  116. _, err = ParseRequest(qp, ParseOptions{UTCOffset: &offset}, validateTestField, nil)
  117. if err != nil {
  118. t.Fatalf("ParseRequest with offset error = %v", err)
  119. }
  120. noFilterQP := httputil.NewQueryParams(map[string][]string{
  121. "window": {windowStr},
  122. "field": {"cluster"},
  123. "filter": {"cluster:\"prod\""},
  124. })
  125. _, err = ParseRequest(noFilterQP, ParseOptions{}, validateTestField, nil)
  126. if err == nil || !errors.Is(err, ErrBadRequest) {
  127. t.Fatalf("expected filter parser required error, got %v", err)
  128. }
  129. parseFilter := func(filterString string) (filter.Filter, error) {
  130. if filterString == "bad" {
  131. return nil, errors.New("bad filter")
  132. }
  133. return nil, nil
  134. }
  135. badFilterQP := httputil.NewQueryParams(map[string][]string{
  136. "window": {windowStr},
  137. "field": {"cluster"},
  138. "filter": {"bad"},
  139. })
  140. _, err = ParseRequest(badFilterQP, ParseOptions{}, validateTestField, parseFilter)
  141. if err == nil || !errors.Is(err, ErrBadRequest) {
  142. t.Fatalf("expected filter parse error, got %v", err)
  143. }
  144. okFilterQP := httputil.NewQueryParams(map[string][]string{
  145. "window": {windowStr},
  146. "field": {"cluster"},
  147. "filter": {"ok"},
  148. })
  149. got, err = ParseRequest(okFilterQP, ParseOptions{}, validateTestField, parseFilter)
  150. if err != nil {
  151. t.Fatalf("ParseRequest with filter error = %v", err)
  152. }
  153. if got.Field != "cluster" {
  154. t.Fatalf("unexpected request: %+v", got)
  155. }
  156. if got.Filter == nil || got.Filter.Op() != ast.FilterOpVoid {
  157. t.Fatalf("expected nil parse result normalized to void filter, got %+v", got.Filter)
  158. }
  159. }