github.com/google/osv-scalibr@v0.4.1/veles/secrets/anthropicapikey/model_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 anthropicapikey_test
    16  
    17  import (
    18  	"net/http"
    19  	"net/http/httptest"
    20  	"testing"
    21  
    22  	"github.com/google/osv-scalibr/veles"
    23  	"github.com/google/osv-scalibr/veles/secrets/anthropicapikey"
    24  )
    25  
    26  const (
    27  	modelValidatorTestKey = "sk-ant-api03-test123456789012345678901234567890123456789012345678"
    28  )
    29  
    30  // mockAnthropicModelServer creates a mock Anthropic API server for testing model keys
    31  func mockAnthropicModelServer(t *testing.T, expectedKey string, statusCode int, responseBody string) *httptest.Server {
    32  	t.Helper()
    33  
    34  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    35  		modelsEndpoint := anthropicapikey.AnthropicModelsEndpoint
    36  		// Check if it's a GET request to the models endpoint
    37  		if r.Method != http.MethodGet || r.URL.Path != modelsEndpoint {
    38  			t.Errorf("unexpected request: %s %s, expected: GET %s", r.Method, r.URL.Path, modelsEndpoint)
    39  			http.Error(w, "not found", http.StatusNotFound)
    40  			return
    41  		}
    42  
    43  		// Check headers
    44  		if r.Header.Get("X-Api-Key") != expectedKey {
    45  			t.Errorf("expected X-Api-Key: %s, got: %s", expectedKey, r.Header.Get("X-Api-Key"))
    46  		}
    47  		if r.Header.Get("Anthropic-Version") != "2023-06-01" {
    48  			t.Errorf("expected Anthropic-Version: 2023-06-01, got: %s", r.Header.Get("Anthropic-Version"))
    49  		}
    50  
    51  		// Set response
    52  		w.Header().Set("Content-Type", "application/json")
    53  		w.WriteHeader(statusCode)
    54  		if responseBody != "" {
    55  			if _, err := w.Write([]byte(responseBody)); err != nil {
    56  				t.Errorf("unable to write response: %v", err)
    57  			}
    58  		}
    59  	}))
    60  
    61  	return server
    62  }
    63  
    64  func TestModelValidator(t *testing.T) {
    65  	cases := []struct {
    66  		name         string
    67  		statusCode   int
    68  		responseBody string
    69  		want         veles.ValidationStatus
    70  		expectError  bool
    71  	}{
    72  		{
    73  			name:       "valid_key",
    74  			statusCode: http.StatusOK,
    75  			responseBody: `{
    76  				"data": [
    77  					{
    78  						"id": "claude-3-opus-20240229",
    79  						"object": "model",
    80  						"created": 1709251200,
    81  						"owned_by": "anthropic"
    82  					}
    83  				]
    84  			}`,
    85  			want: veles.ValidationValid,
    86  		},
    87  		{
    88  			name:       "invalid_key_unauthorized",
    89  			statusCode: http.StatusUnauthorized,
    90  			responseBody: `{
    91  				"error": {
    92  					"type": "authentication_error",
    93  					"message": "Invalid API key"
    94  				}
    95  			}`,
    96  			want: veles.ValidationInvalid,
    97  		},
    98  		{
    99  			name:       "forbidden_but_likely_valid",
   100  			statusCode: http.StatusForbidden,
   101  			responseBody: `{
   102  				"error": {
   103  					"type": "permission_error",
   104  					"message": "Your account does not have permission to perform this action"
   105  				}
   106  			}`,
   107  			want:        veles.ValidationFailed,
   108  			expectError: true,
   109  		},
   110  		{
   111  			name:       "rate_limited_but_likely_valid",
   112  			statusCode: http.StatusTooManyRequests,
   113  			responseBody: `{
   114  				"error": {
   115  					"type": "rate_limit_error",
   116  					"message": "Rate limit exceeded"
   117  				}
   118  			}`,
   119  			want: veles.ValidationValid,
   120  		},
   121  		{
   122  			name:       "server_error",
   123  			statusCode: http.StatusInternalServerError,
   124  			responseBody: `{
   125  				"error": {
   126  					"type": "server_error",
   127  					"message": "Internal server error"
   128  				}
   129  			}`,
   130  			want:        veles.ValidationFailed,
   131  			expectError: true,
   132  		},
   133  	}
   134  
   135  	for _, tc := range cases {
   136  		t.Run(tc.name, func(t *testing.T) {
   137  			// Create mock server
   138  			server := mockAnthropicModelServer(t, modelValidatorTestKey, tc.statusCode, tc.responseBody)
   139  			defer server.Close()
   140  
   141  			// Create validator with mock client and server URL
   142  			validator := anthropicapikey.NewModelValidator()
   143  			validator.HTTPC = server.Client()
   144  			validator.Endpoint = server.URL + anthropicapikey.AnthropicModelsEndpoint
   145  
   146  			// Create test key
   147  			key := anthropicapikey.ModelAPIKey{Key: modelValidatorTestKey}
   148  
   149  			// Test validation
   150  			got, err := validator.Validate(t.Context(), key)
   151  
   152  			// Check error expectation
   153  			if tc.expectError {
   154  				if err == nil {
   155  					t.Errorf("Validate() expected error, got nil")
   156  				}
   157  			} else {
   158  				if err != nil {
   159  					t.Errorf("Validate() unexpected error: %v", err)
   160  				}
   161  			}
   162  
   163  			// Check validation status
   164  			if got != tc.want {
   165  				t.Errorf("Validate() = %v, want %v", got, tc.want)
   166  			}
   167  		})
   168  	}
   169  }