github.com/google/osv-scalibr@v0.4.1/veles/secrets/slacktoken/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 slacktoken_test 16 17 import ( 18 "context" 19 "net/http" 20 "net/http/httptest" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/google/osv-scalibr/veles" 26 "github.com/google/osv-scalibr/veles/secrets/slacktoken" 27 ) 28 29 // mockSlackServer creates a mock Slack API server for testing 30 func mockSlackServer(t *testing.T, expectedKey string, responseBody string, expectedEndpoint string) *httptest.Server { 31 t.Helper() 32 33 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 34 // Check if it's a POST request to the expected endpoint 35 if r.Method != http.MethodPost { 36 t.Errorf("unexpected request method: got %s, expected POST", r.Method) 37 http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 38 return 39 } 40 if r.URL.Path != expectedEndpoint { 41 t.Errorf("unexpected request path: got %s, expected %s", r.URL.Path, expectedEndpoint) 42 http.Error(w, "not found", http.StatusNotFound) 43 return 44 } 45 46 // Check Authorization header for auth.test endpoint 47 if expectedEndpoint == "/api/auth.test" { 48 authHeader := r.Header.Get("Authorization") 49 if !strings.Contains(authHeader, expectedKey) { 50 w.Header().Set("Content-Type", "application/json") 51 w.WriteHeader(http.StatusOK) 52 _, _ = w.Write([]byte(`{"ok":false,"error":"invalid_auth"}`)) 53 return 54 } 55 } 56 // Set response 57 w.Header().Set("Content-Type", "application/json") 58 w.WriteHeader(http.StatusOK) 59 _, _ = w.Write([]byte(responseBody)) 60 })) 61 } 62 63 func TestAppLevelTokenValidator(t *testing.T) { 64 cases := []struct { 65 name string 66 key slacktoken.SlackAppLevelToken 67 serverExpectedKey string 68 responseBody string 69 expectedEndpoint string 70 want veles.ValidationStatus 71 expectError bool 72 }{ 73 { 74 name: "valid_app_level_token", 75 key: slacktoken.SlackAppLevelToken{Token: testAppLevelToken}, 76 serverExpectedKey: testAppLevelToken, 77 responseBody: `{"ok":true}`, 78 expectedEndpoint: "/api/auth.test", 79 want: veles.ValidationValid, 80 }, 81 { 82 name: "invalid_app_level_token", 83 key: slacktoken.SlackAppLevelToken{Token: "random_string"}, 84 serverExpectedKey: testAppLevelToken, 85 responseBody: `{"ok":false,"error":"invalid_auth"}`, 86 expectedEndpoint: "/api/auth.test", 87 want: veles.ValidationInvalid, 88 }, 89 { 90 name: "server_error_app_level", 91 key: slacktoken.SlackAppLevelToken{Token: testAppLevelToken}, 92 serverExpectedKey: testAppLevelToken, 93 responseBody: `{"ok":false,"error":"server_error"}`, 94 expectedEndpoint: "/api/auth.test", 95 want: veles.ValidationFailed, 96 }, 97 } 98 99 for _, tc := range cases { 100 t.Run(tc.name, func(t *testing.T) { 101 // Create a mock server 102 server := mockSlackServer(t, tc.serverExpectedKey, tc.responseBody, tc.expectedEndpoint) 103 defer server.Close() 104 105 validator := slacktoken.NewAppLevelTokenValidator() 106 validator.HTTPC = server.Client() 107 validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint 108 109 got, err := validator.Validate(t.Context(), tc.key) 110 111 // Check error expectation 112 if tc.expectError { 113 if err == nil { 114 t.Errorf("Validate() expected error, got nil") 115 } 116 } else { 117 if err != nil { 118 t.Errorf("Validate() unexpected error: %v", err) 119 } 120 } 121 122 // Check validation status 123 if got != tc.want { 124 t.Errorf("Validate() = %v, want %v", got, tc.want) 125 } 126 }) 127 } 128 } 129 130 func TestAppConfigAccessTokenValidator(t *testing.T) { 131 cases := []struct { 132 name string 133 key slacktoken.SlackAppConfigAccessToken 134 serverExpectedKey string 135 responseBody string 136 expectedEndpoint string 137 want veles.ValidationStatus 138 expectError bool 139 }{ 140 { 141 name: "valid_access_token", 142 key: slacktoken.SlackAppConfigAccessToken{Token: testAppConfigAccessToken}, 143 serverExpectedKey: testAppConfigAccessToken, 144 responseBody: `{"ok":true}`, 145 expectedEndpoint: "/api/auth.test", 146 want: veles.ValidationValid, 147 }, 148 { 149 name: "invalid_access_token", 150 key: slacktoken.SlackAppConfigAccessToken{Token: "invalid_access_token"}, 151 serverExpectedKey: testAppConfigAccessToken, 152 responseBody: `{"ok":false,"error":"invalid_auth"}`, 153 expectedEndpoint: "/api/auth.test", 154 want: veles.ValidationInvalid, 155 }, 156 } 157 158 for _, tc := range cases { 159 t.Run(tc.name, func(t *testing.T) { 160 // Create a mock server 161 server := mockSlackServer(t, tc.serverExpectedKey, tc.responseBody, tc.expectedEndpoint) 162 defer server.Close() 163 164 validator := slacktoken.NewAppConfigAccessTokenValidator() 165 validator.HTTPC = server.Client() 166 validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint 167 168 got, err := validator.Validate(t.Context(), tc.key) 169 170 // Check error expectation 171 if tc.expectError { 172 if err == nil { 173 t.Errorf("Validate() expected error, got nil") 174 } 175 } else { 176 if err != nil { 177 t.Errorf("Validate() unexpected error: %v", err) 178 } 179 } 180 181 // Check validation status 182 if got != tc.want { 183 t.Errorf("Validate() = %v, want %v", got, tc.want) 184 } 185 }) 186 } 187 } 188 189 func TestAppConfigRefreshTokenValidator(t *testing.T) { 190 cases := []struct { 191 name string 192 key slacktoken.SlackAppConfigRefreshToken 193 serverExpectedKey string 194 responseBody string 195 expectedEndpoint string 196 want veles.ValidationStatus 197 expectError bool 198 }{ 199 { 200 name: "valid_refresh_token", 201 key: slacktoken.SlackAppConfigRefreshToken{Token: testAppConfigRefreshToken}, 202 serverExpectedKey: testAppConfigRefreshToken, 203 responseBody: `{"ok":true}`, 204 expectedEndpoint: "/api/auth.test", 205 want: veles.ValidationValid, 206 }, 207 { 208 name: "invalid_refresh_token", 209 key: slacktoken.SlackAppConfigRefreshToken{Token: "invalid_refresh_token"}, 210 serverExpectedKey: testAppConfigRefreshToken, 211 responseBody: `{"ok":false,"error":"invalid_auth"}`, 212 expectedEndpoint: "/api/auth.test", 213 want: veles.ValidationInvalid, 214 }, 215 } 216 217 for _, tc := range cases { 218 t.Run(tc.name, func(t *testing.T) { 219 // Create a mock server 220 server := mockSlackServer(t, tc.serverExpectedKey, tc.responseBody, tc.expectedEndpoint) 221 defer server.Close() 222 223 validator := slacktoken.NewAppConfigRefreshTokenValidator() 224 validator.HTTPC = server.Client() 225 validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint 226 227 got, err := validator.Validate(t.Context(), tc.key) 228 229 // Check error expectation 230 if tc.expectError { 231 if err == nil { 232 t.Errorf("Validate() expected error, got nil") 233 } 234 } else { 235 if err != nil { 236 t.Errorf("Validate() unexpected error: %v", err) 237 } 238 } 239 240 // Check validation status 241 if got != tc.want { 242 t.Errorf("Validate() = %v, want %v", got, tc.want) 243 } 244 }) 245 } 246 } 247 248 func TestValidator_ContextCancellation(t *testing.T) { 249 // Create a server that delays response significantly 250 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 251 // Sleep longer than the context timeout to trigger cancellation 252 time.Sleep(100 * time.Millisecond) 253 w.Header().Set("Content-Type", "application/json") 254 w.WriteHeader(http.StatusOK) 255 _, _ = w.Write([]byte(`{"ok":true"}`)) 256 })) 257 defer server.Close() 258 259 t.Run("app_level_token", func(t *testing.T) { 260 validator := slacktoken.NewAppLevelTokenValidator() 261 validator.HTTPC = server.Client() 262 validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint 263 key := slacktoken.SlackAppLevelToken{Token: testAppLevelToken} 264 265 // Create context with a very short timeout 266 ctx, cancel := context.WithTimeout(t.Context(), 10*time.Millisecond) 267 defer cancel() 268 269 // Test validation with cancelled context 270 got, err := validator.Validate(ctx, key) 271 272 if err == nil { 273 t.Errorf("Validate() expected error due to context cancellation, got nil") 274 } 275 if got != veles.ValidationFailed { 276 t.Errorf("Validate() = %v, want %v", got, veles.ValidationFailed) 277 } 278 }) 279 280 // Test with App Config Access Token validator 281 t.Run("app_config_access_token", func(t *testing.T) { 282 validator := slacktoken.NewAppConfigAccessTokenValidator() 283 validator.HTTPC = server.Client() 284 validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint 285 key := slacktoken.SlackAppConfigAccessToken{Token: testAppConfigAccessToken} 286 ctx, cancel := context.WithTimeout(t.Context(), 10*time.Millisecond) 287 defer cancel() 288 289 // Test validation with cancelled context 290 got, err := validator.Validate(ctx, key) 291 292 if err == nil { 293 t.Errorf("Validate() expected error due to context cancellation, got nil") 294 } 295 if got != veles.ValidationFailed { 296 t.Errorf("Validate() = %v, want %v", got, veles.ValidationFailed) 297 } 298 }) 299 300 // Test with App Config Refresh Token validator 301 t.Run("app_config_refresh_token", func(t *testing.T) { 302 validator := slacktoken.NewAppConfigRefreshTokenValidator() 303 validator.HTTPC = server.Client() 304 validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint 305 key := slacktoken.SlackAppConfigRefreshToken{Token: testAppConfigRefreshToken} 306 307 // Create context with a very short timeout 308 ctx, cancel := context.WithTimeout(t.Context(), 10*time.Millisecond) 309 defer cancel() 310 311 // Test validation with cancelled context 312 got, err := validator.Validate(ctx, key) 313 314 if err == nil { 315 t.Errorf("Validate() expected error due to context cancellation, got nil") 316 } 317 if got != veles.ValidationFailed { 318 t.Errorf("Validate() = %v, want %v", got, veles.ValidationFailed) 319 } 320 }) 321 } 322 323 func TestValidator_InvalidRequest(t *testing.T) { 324 // Create a mock server that returns invalid auth response 325 server := mockSlackServer(t, "any-key", `{"ok":false,"error":"invalid_auth"}`, "/api/auth.test") 326 defer server.Close() 327 328 t.Run("app_level_token", func(t *testing.T) { 329 validator := slacktoken.NewAppLevelTokenValidator() 330 validator.HTTPC = server.Client() 331 validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint 332 testCases := []struct { 333 name string 334 key slacktoken.SlackAppLevelToken 335 expected veles.ValidationStatus 336 }{ 337 { 338 name: "empty_key", 339 key: slacktoken.SlackAppLevelToken{Token: ""}, 340 expected: veles.ValidationInvalid, 341 }, 342 { 343 name: "invalid_key_format", 344 key: slacktoken.SlackAppLevelToken{Token: "invalid-key-format"}, 345 expected: veles.ValidationInvalid, 346 }, 347 } 348 349 for _, tc := range testCases { 350 t.Run(tc.name, func(t *testing.T) { 351 got, err := validator.Validate(t.Context(), tc.key) 352 353 if err != nil { 354 t.Errorf("Validate() unexpected error for %s: %v", tc.name, err) 355 } 356 if got != tc.expected { 357 t.Errorf("Validate() = %v, want %v for %s", got, tc.expected, tc.name) 358 } 359 }) 360 } 361 }) 362 363 // Test with App Config Access Token validator 364 t.Run("app_config_access_token", func(t *testing.T) { 365 validator := slacktoken.NewAppConfigAccessTokenValidator() 366 validator.HTTPC = server.Client() 367 validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint 368 testCases := []struct { 369 name string 370 key slacktoken.SlackAppConfigAccessToken 371 expected veles.ValidationStatus 372 }{ 373 { 374 name: "empty_key", 375 key: slacktoken.SlackAppConfigAccessToken{Token: ""}, 376 expected: veles.ValidationInvalid, 377 }, 378 { 379 name: "invalid_key_format", 380 key: slacktoken.SlackAppConfigAccessToken{Token: "invalid-key-format"}, 381 expected: veles.ValidationInvalid, 382 }, 383 } 384 385 for _, tc := range testCases { 386 t.Run(tc.name, func(t *testing.T) { 387 got, err := validator.Validate(t.Context(), tc.key) 388 389 if err != nil { 390 t.Errorf("Validate() unexpected error for %s: %v", tc.name, err) 391 } 392 if got != tc.expected { 393 t.Errorf("Validate() = %v, want %v for %s", got, tc.expected, tc.name) 394 } 395 }) 396 } 397 }) 398 399 // Test with App Config Refresh Token validator 400 t.Run("app_config_refresh_token", func(t *testing.T) { 401 validator := slacktoken.NewAppConfigRefreshTokenValidator() 402 validator.HTTPC = server.Client() 403 validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint 404 testCases := []struct { 405 name string 406 key slacktoken.SlackAppConfigRefreshToken 407 expected veles.ValidationStatus 408 }{ 409 { 410 name: "empty_key", 411 key: slacktoken.SlackAppConfigRefreshToken{Token: ""}, 412 expected: veles.ValidationInvalid, 413 }, 414 { 415 name: "invalid_key_format", 416 key: slacktoken.SlackAppConfigRefreshToken{Token: "invalid-key-format"}, 417 expected: veles.ValidationInvalid, 418 }, 419 } 420 421 for _, tc := range testCases { 422 t.Run(tc.name, func(t *testing.T) { 423 got, err := validator.Validate(t.Context(), tc.key) 424 425 if err != nil { 426 t.Errorf("Validate() unexpected error for %s: %v", tc.name, err) 427 } 428 if got != tc.expected { 429 t.Errorf("Validate() = %v, want %v for %s", got, tc.expected, tc.name) 430 } 431 }) 432 } 433 }) 434 }