github.com/google/osv-scalibr@v0.4.1/veles/secrets/cratesioapitoken/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 cratesioapitoken_test 16 17 import ( 18 "context" 19 "net/http" 20 "net/http/httptest" 21 "net/url" 22 "strings" 23 "testing" 24 25 "github.com/google/osv-scalibr/veles" 26 "github.com/google/osv-scalibr/veles/secrets/cratesioapitoken" 27 ) 28 29 const validatorValidTestKey = "cioAbCdEfGhIjKlMnOpQrStUvWxYz123456" 30 31 // mockTransport redirects requests to the test server 32 type mockTransport struct { 33 testServer *httptest.Server 34 } 35 36 func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) { 37 // Replace the original URL with our test server URL 38 if req.URL.Host == "crates.io" { 39 testURL, _ := url.Parse(m.testServer.URL) 40 req.URL.Scheme = testURL.Scheme 41 req.URL.Host = testURL.Host 42 } 43 return http.DefaultTransport.RoundTrip(req) 44 } 45 46 // mockCratesioServer creates a mock Crates.io API server for testing 47 func mockCratesioServer(t *testing.T, expectedKey string) *httptest.Server { 48 t.Helper() 49 50 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 51 // Check if it's a PUT request to the expected crates endpoint 52 expectedPath := "/api/v1/crates/osvscalibr" 53 if r.Method != http.MethodPut || !strings.HasPrefix(r.URL.Path, expectedPath) { 54 t.Errorf("unexpected request: %s %s, expected: PUT %s", r.Method, r.URL.Path, expectedPath) 55 http.Error(w, "not found", http.StatusNotFound) 56 return 57 } 58 59 // Check Authorization header format 60 authHeader := r.Header.Get("Authorization") 61 if !strings.HasPrefix(authHeader, "Bearer ") { 62 w.WriteHeader(http.StatusInternalServerError) 63 return 64 } 65 66 w.Header().Set("Content-Type", "application/json") 67 // Check Authorization 68 if authHeader == "Bearer "+expectedKey { 69 // Valid token 70 w.WriteHeader(http.StatusNotFound) 71 _, _ = w.Write([]byte(`{"errors":[{"detail":"crate velesvalidationtestcrate does not exist"}]}`)) 72 } else { 73 w.WriteHeader(http.StatusForbidden) 74 _, _ = w.Write([]byte(`{"errors":[{"detail":"authentication failed"}]}`)) 75 return 76 } 77 })) 78 } 79 80 func TestValidator(t *testing.T) { 81 cases := []struct { 82 name string 83 key string 84 serverExpectedKey string 85 want veles.ValidationStatus 86 expectError bool 87 }{ 88 { 89 name: "valid_key", 90 key: validatorValidTestKey, 91 serverExpectedKey: validatorValidTestKey, 92 want: veles.ValidationValid, 93 }, 94 { 95 name: "invalid_key_unauthorized", 96 key: "random_string", 97 serverExpectedKey: validatorValidTestKey[3:], 98 want: veles.ValidationInvalid, 99 }, 100 } 101 102 for _, tc := range cases { 103 t.Run(tc.name, func(t *testing.T) { 104 // Create a mock server 105 server := mockCratesioServer(t, tc.serverExpectedKey) 106 defer server.Close() 107 108 // Create a client with custom transport 109 client := &http.Client{ 110 Transport: &mockTransport{testServer: server}, 111 } 112 113 // Create a validator with a mock client 114 validator := cratesioapitoken.NewValidator() 115 validator.HTTPC = client 116 117 // Create a test key 118 key := cratesioapitoken.CratesIOAPItoken{Token: tc.key} 119 120 // Test validation 121 got, err := validator.Validate(t.Context(), key) 122 123 // Check error expectation 124 if tc.expectError { 125 if err == nil { 126 t.Errorf("Validate() expected error, got nil") 127 } 128 } else { 129 if err != nil { 130 t.Errorf("Validate() unexpected error: %v", err) 131 } 132 } 133 134 // Check validation status 135 if got != tc.want { 136 t.Errorf("Validate() = %v, want %v", got, tc.want) 137 } 138 }) 139 } 140 } 141 142 func TestValidator_ContextCancellation(t *testing.T) { 143 // Create a server that delays response 144 server := mockCratesioServer(t, "Bearer "+validatorValidTestKey) 145 defer server.Close() 146 147 // Create a client with custom transport 148 client := &http.Client{ 149 Transport: &mockTransport{testServer: server}, 150 } 151 152 validator := cratesioapitoken.NewValidator() 153 validator.HTTPC = client 154 155 key := cratesioapitoken.CratesIOAPItoken{Token: validatorValidTestKey} 156 157 // Create a cancelled context 158 ctx, cancel := context.WithCancel(t.Context()) 159 cancel() 160 161 // Test validation with cancelled context 162 got, err := validator.Validate(ctx, key) 163 164 if err == nil { 165 t.Errorf("Validate() expected error due to context cancellation, got nil") 166 } 167 if got != veles.ValidationFailed { 168 t.Errorf("Validate() = %v, want %v", got, veles.ValidationFailed) 169 } 170 } 171 172 func TestValidator_InvalidRequest(t *testing.T) { 173 // Create a mock server that returns 403 Forbidden for invalid keys 174 server := mockCratesioServer(t, "invalid-key") 175 defer server.Close() 176 177 // Create a client with custom transport 178 client := &http.Client{ 179 Transport: &mockTransport{testServer: server}, 180 } 181 182 validator := cratesioapitoken.NewValidator() 183 validator.HTTPC = client 184 185 testCases := []struct { 186 name string 187 key string 188 expected veles.ValidationStatus 189 wantErr bool 190 }{ 191 { 192 name: "empty_key", 193 key: "", 194 expected: veles.ValidationFailed, 195 wantErr: true, 196 }, 197 { 198 name: "invalid_key_format", 199 key: "invalid-key-format", 200 expected: veles.ValidationInvalid, 201 }, 202 } 203 204 for _, tc := range testCases { 205 t.Run(tc.name, func(t *testing.T) { 206 key := cratesioapitoken.CratesIOAPItoken{Token: tc.key} 207 208 got, err := validator.Validate(t.Context(), key) 209 if tc.wantErr { 210 if err == nil { 211 t.Errorf("Validate() expected error, got nil") 212 } 213 } else { 214 if err != nil { 215 t.Errorf("Validate() unexpected error for %s: %v", tc.name, err) 216 } 217 } 218 219 if got != tc.expected { 220 t.Errorf("Validate() = %v, want %v for %s", got, tc.expected, tc.name) 221 } 222 }) 223 } 224 }