github.com/google/osv-scalibr@v0.4.1/veles/secrets/pypiapitoken/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 pypiapitoken_test 16 17 import ( 18 "context" 19 "net/http" 20 "net/http/httptest" 21 "strings" 22 "testing" 23 24 "github.com/google/osv-scalibr/veles" 25 "github.com/google/osv-scalibr/veles/secrets/pypiapitoken" 26 ) 27 28 const validatorTestKey = `pypi-AgEIc433aS5vcmcffDgyZDA0MzFkLWMzZjEtNDlhNy1iOWQwLfflMjE5NmNkMjhjNQACKlszLCI22UBiYzQ2Yi05YjNhhTQ5NmItYWIxMHYhMGI3MmEyOWI5MzYiXQAABiCJBI80LFFz0JvS6UIj2LzgV9N-BQnBAD2123Dyu9xs33` 29 30 // mockPyPIServer creates a mock PyPI API server for testing 31 func mockPyPIServer(t *testing.T, expectedKey string, serverResponseCode int) *httptest.Server { 32 t.Helper() 33 34 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 35 // Check if it's a GET request to the expected endpoint 36 if r.Method != http.MethodPost || r.URL.Path != "/legacy/" { 37 t.Errorf("unexpected request: %s %s, expected: POST /legacy/", r.Method, r.URL.Path) 38 http.Error(w, "not found", http.StatusNotFound) 39 return 40 } 41 42 // Check Authorization header 43 authHeader := r.Header.Get("Authorization") 44 if !strings.Contains(authHeader, expectedKey) { 45 w.Header().Set("Content-Type", "application/json") 46 w.WriteHeader(http.StatusForbidden) 47 return 48 } 49 50 // Set response 51 w.Header().Set("Content-Type", "application/json") 52 w.WriteHeader(serverResponseCode) 53 })) 54 } 55 56 func TestValidator(t *testing.T) { 57 cases := []struct { 58 name string 59 key string 60 serverExpectedKey string 61 serverResponseCode int 62 want veles.ValidationStatus 63 expectError bool 64 }{ 65 { 66 name: "valid_key", 67 key: validatorTestKey, 68 serverExpectedKey: validatorTestKey, 69 serverResponseCode: http.StatusBadRequest, 70 want: veles.ValidationValid, 71 }, 72 { 73 name: "invalid_key_unauthorized", 74 key: "random_string", 75 serverExpectedKey: validatorTestKey, 76 serverResponseCode: http.StatusUnauthorized, 77 want: veles.ValidationInvalid, 78 }, 79 { 80 name: "server_error", 81 serverResponseCode: http.StatusInternalServerError, 82 want: veles.ValidationFailed, 83 expectError: true, 84 }, 85 { 86 name: "bad_gateway", 87 serverResponseCode: http.StatusBadGateway, 88 want: veles.ValidationFailed, 89 expectError: true, 90 }, 91 } 92 93 for _, tc := range cases { 94 t.Run(tc.name, func(t *testing.T) { 95 // Create a mock server 96 server := mockPyPIServer(t, tc.serverExpectedKey, tc.serverResponseCode) 97 defer server.Close() 98 99 validator := pypiapitoken.NewValidator() 100 validator.HTTPC = server.Client() 101 validator.Endpoint = server.URL + "/legacy/" 102 103 // Create a test key 104 key := pypiapitoken.PyPIAPIToken{Token: tc.key} 105 106 // Test validation 107 got, err := validator.Validate(context.Background(), key) 108 109 // Check error expectation 110 if tc.expectError { 111 if err == nil { 112 t.Errorf("Validate() expected error, got nil") 113 } 114 } else { 115 if err != nil { 116 t.Errorf("Validate() unexpected error: %v", err) 117 } 118 } 119 120 // Check validation status 121 if got != tc.want { 122 t.Errorf("Validate() = %v, want %v", got, tc.want) 123 } 124 }) 125 } 126 } 127 128 func TestValidator_ContextCancellation(t *testing.T) { 129 // Create a server that delays response 130 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 131 w.WriteHeader(http.StatusOK) 132 })) 133 defer server.Close() 134 135 validator := pypiapitoken.NewValidator() 136 validator.HTTPC = server.Client() 137 validator.Endpoint = server.URL + "/legacy/" 138 139 key := pypiapitoken.PyPIAPIToken{Token: validatorTestKey} 140 141 ctx, cancel := context.WithCancel(t.Context()) 142 cancel() 143 144 // Test validation with cancelled context 145 got, err := validator.Validate(ctx, key) 146 147 if err == nil { 148 t.Errorf("Validate() expected error due to context cancellation, got nil") 149 } 150 if got != veles.ValidationFailed { 151 t.Errorf("Validate() = %v, want %v", got, veles.ValidationFailed) 152 } 153 } 154 155 func TestValidator_InvalidRequest(t *testing.T) { 156 // Create a mock server that returns 401 Unauthorized 157 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 158 w.WriteHeader(http.StatusUnauthorized) 159 })) 160 defer server.Close() 161 162 validator := pypiapitoken.NewValidator() 163 validator.HTTPC = server.Client() 164 validator.Endpoint = server.URL + "/legacy/" 165 166 testCases := []struct { 167 name string 168 key string 169 expected veles.ValidationStatus 170 expectError bool 171 }{ 172 { 173 name: "empty_key", 174 key: "", 175 expected: veles.ValidationFailed, 176 expectError: true, 177 }, 178 { 179 name: "invalid_key_format", 180 key: "invalid-key-format", 181 expected: veles.ValidationFailed, 182 expectError: true, 183 }, 184 } 185 186 for _, tc := range testCases { 187 t.Run(tc.name, func(t *testing.T) { 188 key := pypiapitoken.PyPIAPIToken{Token: tc.key} 189 190 got, err := validator.Validate(context.Background(), key) 191 192 if tc.expectError { 193 if err == nil { 194 t.Errorf("Validate() expected error for %s, got nil", tc.name) 195 } 196 } else { 197 if err != nil { 198 t.Errorf("Validate() unexpected error for %s: %v", tc.name, err) 199 } 200 } 201 if got != tc.expected { 202 t.Errorf("Validate() = %v, want %v for %s", got, tc.expected, tc.name) 203 } 204 }) 205 } 206 }