|
|
@@ -0,0 +1,255 @@
|
|
|
+package nodestats
|
|
|
+
|
|
|
+import (
|
|
|
+ "io"
|
|
|
+ "net/http"
|
|
|
+ "strings"
|
|
|
+ "testing"
|
|
|
+)
|
|
|
+
|
|
|
+// MockHttpClient is a mock implementation of HttpClient interface for testing
|
|
|
+type MockHttpClient struct {
|
|
|
+ // capturedRequest stores the last request that was sent
|
|
|
+ capturedRequest *http.Request
|
|
|
+ // responseToReturn is the response that will be returned by Do
|
|
|
+ responseToReturn *http.Response
|
|
|
+ // errorToReturn is the error that will be returned by Do
|
|
|
+ errorToReturn error
|
|
|
+}
|
|
|
+
|
|
|
+// Do captures the request and returns the configured response/error
|
|
|
+func (m *MockHttpClient) Do(req *http.Request) (*http.Response, error) {
|
|
|
+ m.capturedRequest = req
|
|
|
+ return m.responseToReturn, m.errorToReturn
|
|
|
+}
|
|
|
+
|
|
|
+// GetCapturedRequest returns the last captured request
|
|
|
+func (m *MockHttpClient) GetCapturedRequest() *http.Request {
|
|
|
+ return m.capturedRequest
|
|
|
+}
|
|
|
+
|
|
|
+func TestNodeHttpClient_BearerTokenCapitalization(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ bearerToken string
|
|
|
+ wantHeader string
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "Bearer token with correct capitalization",
|
|
|
+ bearerToken: "test-token-123",
|
|
|
+ wantHeader: "Bearer test-token-123",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "Empty bearer token should not set header",
|
|
|
+ bearerToken: "",
|
|
|
+ wantHeader: "",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "Bearer token with special characters",
|
|
|
+ bearerToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test",
|
|
|
+ wantHeader: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test",
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ // Create a mock HTTP client
|
|
|
+ mockClient := &MockHttpClient{
|
|
|
+ responseToReturn: &http.Response{
|
|
|
+ StatusCode: 200,
|
|
|
+ Body: io.NopCloser(strings.NewReader("")),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create NodeHttpClient with mock
|
|
|
+ nodeClient := NewNodeHttpClient(mockClient)
|
|
|
+
|
|
|
+ // Make request through the client
|
|
|
+ _, err := nodeClient.AttemptEndPoint("GET", "http://example.com/test", tt.bearerToken)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("AttemptEndPoint returned error: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get the captured request
|
|
|
+ capturedReq := mockClient.GetCapturedRequest()
|
|
|
+ if capturedReq == nil {
|
|
|
+ t.Fatal("Expected request to be captured, but got nil")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify the Authorization header
|
|
|
+ authHeader := capturedReq.Header.Get("Authorization")
|
|
|
+ if tt.wantHeader == "" {
|
|
|
+ // Empty token case - should not have Authorization header
|
|
|
+ if authHeader != "" {
|
|
|
+ t.Errorf("Expected no Authorization header, but got: %s", authHeader)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // Verify exact header value including capitalization
|
|
|
+ if authHeader != tt.wantHeader {
|
|
|
+ t.Errorf("Authorization header = %q, want %q", authHeader, tt.wantHeader)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Specifically verify "Bearer" capitalization (capital B, lowercase rest)
|
|
|
+ if !strings.HasPrefix(authHeader, "Bearer ") {
|
|
|
+ t.Errorf("Authorization header does not start with 'Bearer ' (with capital B): %s", authHeader)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check for common incorrect capitalizations
|
|
|
+ if strings.HasPrefix(authHeader, "bearer ") {
|
|
|
+ t.Error("Authorization header uses lowercase 'bearer' instead of 'Bearer'")
|
|
|
+ }
|
|
|
+ if strings.HasPrefix(authHeader, "BEARER ") {
|
|
|
+ t.Error("Authorization header uses uppercase 'BEARER' instead of 'Bearer'")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestNodeHttpClient_BearerCapitalization_HappyPath(t *testing.T) {
|
|
|
+ // HAPPY PATH TEST: Verify "Bearer" uses correct capitalization (capital B, lowercase e-a-r-e-r)
|
|
|
+ // According to RFC 6750, the correct format is "Bearer" not "bearer" or "BEARER"
|
|
|
+
|
|
|
+ mockClient := &MockHttpClient{
|
|
|
+ responseToReturn: &http.Response{
|
|
|
+ StatusCode: 200,
|
|
|
+ Body: io.NopCloser(strings.NewReader("")),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ nodeClient := NewNodeHttpClient(mockClient)
|
|
|
+ bearerToken := "test-token-123"
|
|
|
+
|
|
|
+ _, err := nodeClient.makeRequest("GET", "http://example.com/api", bearerToken)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("makeRequest returned error: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ capturedReq := mockClient.GetCapturedRequest()
|
|
|
+ authHeader := capturedReq.Header.Get("Authorization")
|
|
|
+
|
|
|
+ // PRIMARY ASSERTION: Verify exact string match with correct capitalization
|
|
|
+ expectedHeader := "Bearer test-token-123"
|
|
|
+ if authHeader != expectedHeader {
|
|
|
+ t.Errorf("FAILED: Authorization header = %q, want %q", authHeader, expectedHeader)
|
|
|
+ t.Errorf(" This means 'Bearer' capitalization is incorrect")
|
|
|
+ }
|
|
|
+
|
|
|
+ // EXPLICIT CHECK: Verify "Bearer" has capital B
|
|
|
+ if !strings.HasPrefix(authHeader, "Bearer ") {
|
|
|
+ t.Errorf("FAILED: Authorization header must start with 'Bearer ' (capital B, lowercase e-a-r-e-r)")
|
|
|
+ t.Errorf(" Got: %q", authHeader)
|
|
|
+ }
|
|
|
+
|
|
|
+ // SUCCESS: Log happy path success
|
|
|
+ if authHeader == expectedHeader {
|
|
|
+ t.Logf("✓ PASS: Bearer token has correct capitalization: %q", authHeader)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestNodeHttpClient_MakeRequestWithBearerToken(t *testing.T) {
|
|
|
+ // Create a mock HTTP client
|
|
|
+ mockClient := &MockHttpClient{
|
|
|
+ responseToReturn: &http.Response{
|
|
|
+ StatusCode: 200,
|
|
|
+ Body: io.NopCloser(strings.NewReader(`{"status":"ok"}`)),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create NodeHttpClient with mock
|
|
|
+ nodeClient := NewNodeHttpClient(mockClient)
|
|
|
+
|
|
|
+ // Test with a bearer token
|
|
|
+ bearerToken := "my-secret-token"
|
|
|
+ _, err := nodeClient.makeRequest("GET", "http://example.com/api", bearerToken)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("makeRequest returned error: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify the Authorization header
|
|
|
+ capturedReq := mockClient.GetCapturedRequest()
|
|
|
+ if capturedReq == nil {
|
|
|
+ t.Fatal("Expected request to be captured")
|
|
|
+ }
|
|
|
+
|
|
|
+ authHeader := capturedReq.Header.Get("Authorization")
|
|
|
+ expectedHeader := "Bearer my-secret-token"
|
|
|
+ if authHeader != expectedHeader {
|
|
|
+ t.Errorf("Authorization header = %q, want %q", authHeader, expectedHeader)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify the exact capitalization of "Bearer"
|
|
|
+ if !strings.HasPrefix(authHeader, "Bearer ") {
|
|
|
+ t.Errorf("Expected 'Bearer ' with capital B, got: %s", authHeader)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestNodeHttpClient_NoAuthorizationHeaderWhenTokenEmpty(t *testing.T) {
|
|
|
+ // Create a mock HTTP client
|
|
|
+ mockClient := &MockHttpClient{
|
|
|
+ responseToReturn: &http.Response{
|
|
|
+ StatusCode: 200,
|
|
|
+ Body: io.NopCloser(strings.NewReader("")),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create NodeHttpClient with mock
|
|
|
+ nodeClient := NewNodeHttpClient(mockClient)
|
|
|
+
|
|
|
+ // Test with empty bearer token
|
|
|
+ _, err := nodeClient.makeRequest("GET", "http://example.com/api", "")
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("makeRequest returned error: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify no Authorization header is set
|
|
|
+ capturedReq := mockClient.GetCapturedRequest()
|
|
|
+ if capturedReq == nil {
|
|
|
+ t.Fatal("Expected request to be captured")
|
|
|
+ }
|
|
|
+
|
|
|
+ authHeader := capturedReq.Header.Get("Authorization")
|
|
|
+ if authHeader != "" {
|
|
|
+ t.Errorf("Expected no Authorization header when token is empty, got: %s", authHeader)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestNodeHttpClient_RequestMethod(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ method string
|
|
|
+ }{
|
|
|
+ {"GET request", "GET"},
|
|
|
+ {"POST request", "POST"},
|
|
|
+ {"PUT request", "PUT"},
|
|
|
+ {"DELETE request", "DELETE"},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ mockClient := &MockHttpClient{
|
|
|
+ responseToReturn: &http.Response{
|
|
|
+ StatusCode: 200,
|
|
|
+ Body: io.NopCloser(strings.NewReader("")),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ nodeClient := NewNodeHttpClient(mockClient)
|
|
|
+ _, err := nodeClient.makeRequest(tt.method, "http://example.com/test", "token123")
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("makeRequest returned error: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ capturedReq := mockClient.GetCapturedRequest()
|
|
|
+ if capturedReq.Method != tt.method {
|
|
|
+ t.Errorf("Request method = %s, want %s", capturedReq.Method, tt.method)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Also verify Bearer token is set correctly regardless of HTTP method
|
|
|
+ authHeader := capturedReq.Header.Get("Authorization")
|
|
|
+ if authHeader != "Bearer token123" {
|
|
|
+ t.Errorf("Authorization header = %q, want %q", authHeader, "Bearer token123")
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|