github.com/google/osv-scalibr@v0.4.1/veles/secrets/hashicorpvault/validator_test.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package hashicorpvault 16 17 import ( 18 "context" 19 "net/http" 20 "net/http/httptest" 21 "testing" 22 23 "github.com/google/osv-scalibr/veles" 24 ) 25 26 func TestTokenValidator_Validate(t *testing.T) { 27 tests := []struct { 28 name string 29 statusCode int 30 expectedStatus veles.ValidationStatus 31 expectError bool 32 }{ 33 { 34 name: "valid token", 35 statusCode: http.StatusOK, 36 expectedStatus: veles.ValidationValid, 37 expectError: false, 38 }, 39 { 40 name: "invalid token - unauthorized", 41 statusCode: http.StatusUnauthorized, 42 expectedStatus: veles.ValidationInvalid, 43 expectError: false, 44 }, 45 { 46 name: "invalid token - forbidden", 47 statusCode: http.StatusForbidden, 48 expectedStatus: veles.ValidationInvalid, 49 expectError: false, 50 }, 51 { 52 name: "server error", 53 statusCode: http.StatusInternalServerError, 54 expectedStatus: veles.ValidationFailed, 55 expectError: true, 56 }, 57 { 58 name: "bad gateway", 59 statusCode: http.StatusBadGateway, 60 expectedStatus: veles.ValidationFailed, 61 expectError: true, 62 }, 63 } 64 65 for _, test := range tests { 66 t.Run(test.name, func(t *testing.T) { 67 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 68 // Verify the correct endpoint and headers 69 if r.URL.Path != "/v1/auth/token/lookup-self" { 70 t.Errorf("Expected path /v1/auth/token/lookup-self, got %s", r.URL.Path) 71 } 72 if r.Method != http.MethodGet { 73 t.Errorf("Expected GET method, got %s", r.Method) 74 } 75 if token := r.Header.Get("X-Vault-Token"); token != "hvs.test-token" { 76 t.Errorf("Expected X-Vault-Token header with test token, got %s", token) 77 } 78 79 w.WriteHeader(test.statusCode) 80 })) 81 defer server.Close() 82 83 serverURL := server.URL 84 validator := NewTokenValidator(serverURL) 85 validator.HTTPC = server.Client() 86 87 token := Token{Token: "hvs.test-token"} 88 status, err := validator.Validate(t.Context(), token) 89 90 if test.expectError && err == nil { 91 t.Fatal("Expected error, got nil") 92 } 93 if !test.expectError && err != nil { 94 t.Fatalf("Unexpected error: %v", err) 95 } 96 97 if status != test.expectedStatus { 98 t.Errorf("Expected status %v, got %v", test.expectedStatus, status) 99 } 100 }) 101 } 102 } 103 104 func TestAppRoleValidator_Validate(t *testing.T) { 105 tests := []struct { 106 name string 107 credentials AppRoleCredentials 108 statusCode int 109 expectedStatus veles.ValidationStatus 110 expectError bool 111 }{ 112 { 113 name: "valid_credentials", 114 credentials: AppRoleCredentials{ 115 RoleID: "12345678-1234-1234-1234-123456789012", 116 SecretID: "87654321-4321-4321-4321-210987654321", 117 }, 118 statusCode: http.StatusOK, 119 expectedStatus: veles.ValidationValid, 120 expectError: false, 121 }, 122 { 123 name: "invalid_credentials_-_unauthorized", 124 credentials: AppRoleCredentials{ 125 RoleID: "12345678-1234-1234-1234-123456789012", 126 SecretID: "invalid-secret", 127 }, 128 statusCode: http.StatusUnauthorized, 129 expectedStatus: veles.ValidationInvalid, 130 expectError: false, 131 }, 132 { 133 name: "invalid_credentials_-_bad_request", 134 credentials: AppRoleCredentials{ 135 RoleID: "invalid-role-id", 136 SecretID: "87654321-4321-4321-4321-210987654321", 137 }, 138 statusCode: http.StatusBadRequest, 139 expectedStatus: veles.ValidationInvalid, 140 expectError: false, 141 }, 142 { 143 name: "server_error", 144 credentials: AppRoleCredentials{ 145 RoleID: "12345678-1234-1234-1234-123456789012", 146 SecretID: "87654321-4321-4321-4321-210987654321", 147 }, 148 statusCode: http.StatusInternalServerError, 149 expectedStatus: veles.ValidationFailed, 150 expectError: true, 151 }, 152 { 153 name: "missing_role_id", 154 credentials: AppRoleCredentials{ 155 RoleID: "", 156 SecretID: "87654321-4321-4321-4321-210987654321", 157 }, 158 statusCode: 0, // Won't make HTTP request 159 expectedStatus: veles.ValidationFailed, 160 expectError: true, 161 }, 162 { 163 name: "missing_secret_id", 164 credentials: AppRoleCredentials{ 165 RoleID: "12345678-1234-1234-1234-123456789012", 166 SecretID: "", 167 }, 168 statusCode: 0, // Won't make HTTP request 169 expectedStatus: veles.ValidationFailed, 170 expectError: true, 171 }, 172 } 173 174 for _, test := range tests { 175 t.Run(test.name, func(t *testing.T) { 176 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 177 // Verify the correct endpoint and headers 178 if r.URL.Path != "/v1/auth/approle/login" { 179 t.Errorf("Expected path /v1/auth/approle/login, got %s", r.URL.Path) 180 } 181 if r.Method != http.MethodPost { 182 t.Errorf("Expected POST method, got %s", r.Method) 183 } 184 if contentType := r.Header.Get("Content-Type"); contentType != "application/json" { 185 t.Errorf("Expected Content-Type application/json, got %s", contentType) 186 } 187 188 w.WriteHeader(test.statusCode) 189 })) 190 defer server.Close() 191 192 serverURL := server.URL 193 validator := NewAppRoleValidator(serverURL) 194 validator.HTTPC = server.Client() 195 196 status, err := validator.Validate(t.Context(), test.credentials) 197 198 if test.expectError && err == nil { 199 t.Fatal("Expected error, got nil") 200 } 201 if !test.expectError && err != nil { 202 t.Fatalf("Unexpected error: %v", err) 203 } 204 205 if status != test.expectedStatus { 206 t.Errorf("Expected status %v, got %v", test.expectedStatus, status) 207 } 208 }) 209 } 210 } 211 212 func TestValidator_InvalidVaultURL(t *testing.T) { 213 validator := NewTokenValidator("://invalid-url") 214 token := Token{Token: "hvs.test-token"} 215 status, err := validator.Validate(t.Context(), token) 216 217 if err == nil { 218 t.Fatal("Expected error for invalid URL, got nil") 219 } 220 if status != veles.ValidationFailed { 221 t.Errorf("Expected ValidationFailed status, got %v", status) 222 } 223 } 224 225 func TestValidator_NetworkError(t *testing.T) { 226 // Use a URL that will cause a network error 227 validator := NewTokenValidator("http://localhost:1") 228 token := Token{Token: "hvs.test-token"} 229 status, err := validator.Validate(t.Context(), token) 230 231 if err == nil { 232 t.Fatal("Expected network error, got nil") 233 } 234 if status != veles.ValidationFailed { 235 t.Errorf("Expected ValidationFailed status, got %v", status) 236 } 237 } 238 239 func TestValidator_ContextCancellation(t *testing.T) { 240 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 241 // This handler will never respond, allowing us to test context cancellation 242 select {} 243 })) 244 defer server.Close() 245 246 serverURL := server.URL 247 validator := NewTokenValidator(serverURL) 248 validator.HTTPC = server.Client() 249 250 ctx, cancel := context.WithCancel(t.Context()) 251 cancel() // Cancel immediately 252 253 token := Token{Token: "hvs.test-token"} 254 status, err := validator.Validate(ctx, token) 255 256 if err == nil { 257 t.Fatal("Expected context cancellation error, got nil") 258 } 259 if status != veles.ValidationFailed { 260 t.Errorf("Expected ValidationFailed status, got %v", status) 261 } 262 }