router_test.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. package costmodel
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "strings"
  6. "testing"
  7. "github.com/julienschmidt/httprouter"
  8. "github.com/opencost/opencost/pkg/env"
  9. )
  10. func TestAdminAuthMiddleware(t *testing.T) {
  11. const testToken = "test-admin-token-123"
  12. nextCalled := false
  13. next := func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  14. nextCalled = true
  15. w.WriteHeader(http.StatusOK)
  16. }
  17. tests := []struct {
  18. name string
  19. setToken string
  20. authHeader string
  21. wantStatus int
  22. wantNextCalled bool
  23. wantBodySubstr string
  24. wantCacheControl string
  25. }{
  26. {
  27. name: "no admin token configured - returns 503",
  28. setToken: "",
  29. authHeader: "",
  30. wantStatus: http.StatusServiceUnavailable,
  31. wantNextCalled: false,
  32. wantBodySubstr: "Admin token is required to activate this endpoint",
  33. wantCacheControl: "no-store",
  34. },
  35. {
  36. name: "no admin token configured - bearer ignored, still 503",
  37. setToken: "",
  38. authHeader: "Bearer anything",
  39. wantStatus: http.StatusServiceUnavailable,
  40. wantNextCalled: false,
  41. wantBodySubstr: "Admin token is required to activate this endpoint",
  42. wantCacheControl: "no-store",
  43. },
  44. {
  45. name: "missing authorization header",
  46. setToken: testToken,
  47. authHeader: "",
  48. wantStatus: http.StatusUnauthorized,
  49. wantNextCalled: false,
  50. },
  51. {
  52. name: "wrong authorization scheme",
  53. setToken: testToken,
  54. authHeader: "Basic dXNlcjpwYXNz",
  55. wantStatus: http.StatusUnauthorized,
  56. wantNextCalled: false,
  57. },
  58. {
  59. name: "bearer with wrong token",
  60. setToken: testToken,
  61. authHeader: "Bearer wrong-token",
  62. wantStatus: http.StatusForbidden,
  63. wantNextCalled: false,
  64. },
  65. {
  66. name: "bearer with correct token",
  67. setToken: testToken,
  68. authHeader: "Bearer " + testToken,
  69. wantStatus: http.StatusOK,
  70. wantNextCalled: true,
  71. },
  72. {
  73. name: "bearer token with extra spaces after prefix",
  74. setToken: testToken,
  75. authHeader: "Bearer " + testToken,
  76. wantStatus: http.StatusForbidden,
  77. wantNextCalled: false,
  78. },
  79. }
  80. for _, tt := range tests {
  81. t.Run(tt.name, func(t *testing.T) {
  82. if tt.setToken != "" {
  83. t.Setenv(env.AdminTokenEnvVar, tt.setToken)
  84. } else {
  85. t.Setenv(env.AdminTokenEnvVar, "")
  86. }
  87. nextCalled = false
  88. req := httptest.NewRequest(http.MethodPost, "/serviceKey", nil)
  89. if tt.authHeader != "" {
  90. req.Header.Set("Authorization", tt.authHeader)
  91. }
  92. rec := httptest.NewRecorder()
  93. handler := adminAuthMiddleware(next)
  94. handler(rec, req, httprouter.Params{})
  95. if rec.Code != tt.wantStatus {
  96. t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
  97. }
  98. if nextCalled != tt.wantNextCalled {
  99. t.Errorf("nextCalled = %v, want %v", nextCalled, tt.wantNextCalled)
  100. }
  101. if tt.wantBodySubstr != "" && !strings.Contains(rec.Body.String(), tt.wantBodySubstr) {
  102. t.Errorf("body = %q, want substring %q", rec.Body.String(), tt.wantBodySubstr)
  103. }
  104. if tt.wantCacheControl != "" && rec.Header().Get("Cache-Control") != tt.wantCacheControl {
  105. t.Errorf("Cache-Control = %q, want %q", rec.Header().Get("Cache-Control"), tt.wantCacheControl)
  106. }
  107. })
  108. }
  109. }