github.com/google/osv-scalibr@v0.4.1/veles/secrets/dockerhubpat/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 dockerhubpat_test 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "net/http" 22 "net/http/httptest" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/google/go-cmp/cmp/cmpopts" 27 "github.com/google/osv-scalibr/veles" 28 "github.com/google/osv-scalibr/veles/secrets/dockerhubpat" 29 ) 30 31 const validatorTestPat = "dckr_oat_7awgM4jG5SQvxcvmNzhKj8PQjxo" 32 const validatorTestUsername = "User123" 33 34 // mockDockerHubServer creates a mock Docker Hub API server for testing 35 func mockDockerHubServer(t *testing.T, expectedKey string, expectedUser string) *httptest.Server { 36 t.Helper() 37 38 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 39 // Check if it's a POST request to the expected endpoint 40 if r.Method != http.MethodPost || 41 r.URL.Path != "/v2/auth/token/" || 42 r.Header.Get("Content-Type") != "application/json" { 43 t.Errorf("unexpected request: %s %s, expected: POST /v2/auth/token/ with content type application/json", r.Method, r.URL.Path) 44 http.Error(w, "not found", http.StatusNotFound) 45 return 46 } 47 48 // Check Body 49 bodyBytes, err := io.ReadAll(r.Body) 50 if err != nil { 51 t.Errorf("failed to read request body: %v", err) 52 http.Error(w, "internal server error", http.StatusInternalServerError) 53 return 54 } 55 defer r.Body.Close() 56 bodyString := string(bodyBytes) 57 expectedBody := fmt.Sprintf("{\"identifier\": \"%s\",\"secret\": \"%s\"}", expectedUser, expectedKey) 58 if expectedBody != bodyString { 59 w.WriteHeader(http.StatusUnauthorized) 60 return 61 } 62 w.WriteHeader(http.StatusOK) 63 })) 64 } 65 66 func TestValidator(t *testing.T) { 67 cases := []struct { 68 name string 69 Username string 70 Pat string 71 want veles.ValidationStatus 72 }{ 73 { 74 name: "invalid key and username", 75 Pat: "dckr_oat_random", 76 Username: "User2", 77 want: veles.ValidationInvalid, 78 }, 79 { 80 name: "valid key and username", 81 Pat: validatorTestPat, 82 Username: validatorTestUsername, 83 want: veles.ValidationValid, 84 }, 85 } 86 87 for _, tc := range cases { 88 t.Run(tc.name, func(t *testing.T) { 89 // Create a mock server 90 server := mockDockerHubServer(t, validatorTestPat, validatorTestUsername) 91 defer server.Close() 92 93 // Create a validator with a mock client 94 validator := dockerhubpat.NewValidator() 95 validator.HTTPC = server.Client() 96 validator.Endpoint = server.URL + "/v2/auth/token/" 97 98 // Create a test username and pat 99 usernamePat := dockerhubpat.DockerHubPAT{Pat: tc.Pat, Username: tc.Username} 100 101 // Test validation 102 got, err := validator.Validate(t.Context(), usernamePat) 103 104 if !cmp.Equal(err, nil, cmpopts.EquateErrors()) { 105 t.Fatalf("plugin.Validate(%v) got error: %v\n", usernamePat, err) 106 } 107 108 // Check validation status 109 if got != tc.want { 110 t.Errorf("Validate() = %v, want %v", got, tc.want) 111 } 112 }) 113 } 114 } 115 func TestValidator_ContextCancellation(t *testing.T) { 116 // Create a server that delays response 117 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 118 w.WriteHeader(http.StatusOK) 119 })) 120 defer server.Close() 121 122 validator := dockerhubpat.NewValidator() 123 validator.HTTPC = server.Client() 124 validator.Endpoint = server.URL + "/v2/auth/token/" 125 126 // Create a test username and pat 127 usernamePat := dockerhubpat.DockerHubPAT{Pat: validatorTestPat, Username: validatorTestUsername} 128 129 // Create a cancelled context 130 ctx, cancel := context.WithCancel(t.Context()) 131 cancel() 132 133 // Test validation with cancelled context 134 got, err := validator.Validate(ctx, usernamePat) 135 136 if err == nil { 137 t.Errorf("Validate() expected error due to context cancellation, got nil") 138 } 139 if got != veles.ValidationFailed { 140 t.Errorf("Validate() = %v, want %v", got, veles.ValidationFailed) 141 } 142 } 143 144 func TestValidator_InvalidRequest(t *testing.T) { 145 // Create a mock server that returns 401 Unauthorized 146 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 147 w.WriteHeader(http.StatusUnauthorized) 148 })) 149 defer server.Close() 150 151 validator := dockerhubpat.NewValidator() 152 validator.HTTPC = server.Client() 153 validator.Endpoint = server.URL + "/v2/auth/token/" 154 155 testCases := []struct { 156 name string 157 Pat string 158 Username string 159 expected veles.ValidationStatus 160 wantErr bool 161 }{ 162 { 163 name: "empty_key", 164 Pat: "", 165 Username: validatorTestUsername, 166 expected: veles.ValidationInvalid, 167 }, 168 { 169 name: "invalid_key_format", 170 Pat: "invalid-key-format", 171 Username: validatorTestUsername, 172 expected: veles.ValidationInvalid, 173 }, 174 { 175 name: "empty_username", 176 Pat: validatorTestPat, 177 Username: "", 178 expected: veles.ValidationInvalid, 179 wantErr: true, 180 }, 181 } 182 183 for _, tc := range testCases { 184 t.Run(tc.name, func(t *testing.T) { 185 usernamePat := dockerhubpat.DockerHubPAT{Pat: tc.Pat, Username: tc.Username} 186 187 got, err := validator.Validate(t.Context(), usernamePat) 188 189 if tc.wantErr { 190 if err == nil { 191 t.Errorf("Validate() expected error for %s, got nil", tc.name) 192 } 193 } else { 194 if err != nil { 195 t.Errorf("Validate() unexpected error for %s: %v", tc.name, err) 196 } 197 if got != tc.expected { 198 t.Errorf("Validate() = %v, want %v for %s", got, tc.expected, tc.name) 199 } 200 } 201 }) 202 } 203 }