github.com/google/osv-scalibr@v0.4.1/veles/secrets/anthropicapikey/workspace_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  	workspaceValidatorTestKey = "sk-ant-admin01-test123456789012345678901234567890123456789012345678"
    28  )
    29  
    30  // mockAnthropicWorkspaceServer creates a mock Anthropic API server for testing workspace keys
    31  func mockAnthropicWorkspaceServer(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  		workspacesEndpoint := anthropicapikey.AnthropicWorkspacesEndpoint
    36  		// Check if it's a GET request to the workspaces endpoint
    37  		if r.Method != http.MethodGet || r.URL.Path != workspacesEndpoint {
    38  			t.Errorf("unexpected request: %s %s, expected: GET %s", r.Method, r.URL.Path, workspacesEndpoint)
    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 TestWorkspaceValidator(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_workspace_key",
    74  			statusCode: http.StatusOK,
    75  			responseBody: `{
    76  				"data": [
    77  					{
    78  						"type": "organization",
    79  						"id": "org_123456789012345678901234",
    80  						"name": "Example Organization"
    81  					}
    82  				]
    83  			}`,
    84  			want: veles.ValidationValid,
    85  		},
    86  		{
    87  			name:       "invalid_workspace_key_unauthorized",
    88  			statusCode: http.StatusUnauthorized,
    89  			responseBody: `{
    90  				"error": {
    91  					"type": "authentication_error",
    92  					"message": "Invalid API key"
    93  				}
    94  			}`,
    95  			want: veles.ValidationInvalid,
    96  		},
    97  		{
    98  			name:       "workspace_forbidden_but_likely_valid",
    99  			statusCode: http.StatusForbidden,
   100  			responseBody: `{
   101  				"error": {
   102  					"type": "permission_error",
   103  					"message": "Your account does not have permission to perform this action"
   104  				}
   105  			}`,
   106  			want:        veles.ValidationFailed,
   107  			expectError: true,
   108  		},
   109  		{
   110  			name:       "workspace_rate_limited_but_likely_valid",
   111  			statusCode: http.StatusTooManyRequests,
   112  			responseBody: `{
   113  				"error": {
   114  					"type": "rate_limit_error",
   115  					"message": "Rate limit exceeded"
   116  				}
   117  			}`,
   118  			want: veles.ValidationValid,
   119  		},
   120  		{
   121  			name:       "workspace_server_error",
   122  			statusCode: http.StatusInternalServerError,
   123  			responseBody: `{
   124  				"error": {
   125  					"type": "server_error",
   126  					"message": "Internal server error"
   127  				}
   128  			}`,
   129  			want:        veles.ValidationFailed,
   130  			expectError: true,
   131  		},
   132  	}
   133  
   134  	for _, tc := range cases {
   135  		t.Run(tc.name, func(t *testing.T) {
   136  			// Create mock server
   137  			server := mockAnthropicWorkspaceServer(t, workspaceValidatorTestKey, tc.statusCode, tc.responseBody)
   138  			defer server.Close()
   139  
   140  			// Create validator with mock client and server URL
   141  			validator := anthropicapikey.NewWorkspaceValidator()
   142  			validator.HTTPC = server.Client()
   143  			validator.Endpoint = server.URL + anthropicapikey.AnthropicWorkspacesEndpoint
   144  
   145  			// Create test key
   146  			key := anthropicapikey.WorkspaceAPIKey{Key: workspaceValidatorTestKey}
   147  
   148  			// Test validation
   149  			got, err := validator.Validate(t.Context(), key)
   150  
   151  			// Check error expectation
   152  			if tc.expectError {
   153  				if err == nil {
   154  					t.Errorf("Validate() expected error, got nil")
   155  				}
   156  			} else {
   157  				if err != nil {
   158  					t.Errorf("Validate() unexpected error: %v", err)
   159  				}
   160  			}
   161  
   162  			// Check validation status
   163  			if got != tc.want {
   164  				t.Errorf("Validate() = %v, want %v", got, tc.want)
   165  			}
   166  		})
   167  	}
   168  }