mirror of
https://github.com/cexll/myclaude.git
synced 2026-02-05 02:30:26 +08:00
- CI: fetch tags for version detection - Makefile: inject version via ldflags - Add CODEAGENT_TMPDIR support for macOS permission issues - Inject ANTHROPIC_BASE_URL/API_KEY for claude backend Generated with SWE-Agent.ai Co-Authored-By: SWE-Agent.ai <noreply@swe-agent.ai>
334 lines
8.2 KiB
Go
334 lines
8.2 KiB
Go
package executor
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
backend "codeagent-wrapper/internal/backend"
|
|
)
|
|
|
|
func TestMaskSensitiveValue(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
key string
|
|
value string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "API_KEY with long value",
|
|
key: "ANTHROPIC_API_KEY",
|
|
value: "sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
expected: "sk-a****xxxx",
|
|
},
|
|
{
|
|
name: "api_key lowercase",
|
|
key: "api_key",
|
|
value: "abcdefghijklmnop",
|
|
expected: "abcd****mnop",
|
|
},
|
|
{
|
|
name: "AUTH_TOKEN",
|
|
key: "AUTH_TOKEN",
|
|
value: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9",
|
|
expected: "eyJh****VCJ9",
|
|
},
|
|
{
|
|
name: "SECRET",
|
|
key: "MY_SECRET",
|
|
value: "super-secret-value-12345",
|
|
expected: "supe****2345",
|
|
},
|
|
{
|
|
name: "short key value (8 chars)",
|
|
key: "API_KEY",
|
|
value: "12345678",
|
|
expected: "****",
|
|
},
|
|
{
|
|
name: "very short key value",
|
|
key: "API_KEY",
|
|
value: "abc",
|
|
expected: "****",
|
|
},
|
|
{
|
|
name: "empty key value",
|
|
key: "API_KEY",
|
|
value: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "non-sensitive BASE_URL",
|
|
key: "ANTHROPIC_BASE_URL",
|
|
value: "https://api.anthropic.com",
|
|
expected: "https://api.anthropic.com",
|
|
},
|
|
{
|
|
name: "non-sensitive MODEL",
|
|
key: "MODEL",
|
|
value: "claude-3-opus",
|
|
expected: "claude-3-opus",
|
|
},
|
|
{
|
|
name: "case insensitive - Key",
|
|
key: "My_Key",
|
|
value: "1234567890abcdef",
|
|
expected: "1234****cdef",
|
|
},
|
|
{
|
|
name: "case insensitive - TOKEN",
|
|
key: "ACCESS_TOKEN",
|
|
value: "access123456789",
|
|
expected: "acce****6789",
|
|
},
|
|
{
|
|
name: "partial match - apikey",
|
|
key: "MYAPIKEY",
|
|
value: "1234567890",
|
|
expected: "1234****7890",
|
|
},
|
|
{
|
|
name: "partial match - secretvalue",
|
|
key: "SECRETVALUE",
|
|
value: "abcdefghij",
|
|
expected: "abcd****ghij",
|
|
},
|
|
{
|
|
name: "9 char value (just above threshold)",
|
|
key: "API_KEY",
|
|
value: "123456789",
|
|
expected: "1234****6789",
|
|
},
|
|
{
|
|
name: "exactly 8 char value (at threshold)",
|
|
key: "API_KEY",
|
|
value: "12345678",
|
|
expected: "****",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := maskSensitiveValue(tt.key, tt.value)
|
|
if result != tt.expected {
|
|
t.Errorf("maskSensitiveValue(%q, %q) = %q, want %q", tt.key, tt.value, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMaskSensitiveValue_NoLeakage(t *testing.T) {
|
|
// Ensure sensitive values are never fully exposed
|
|
sensitiveKeys := []string{"API_KEY", "api_key", "AUTH_TOKEN", "SECRET", "access_token", "MYAPIKEY"}
|
|
longValue := "this-is-a-very-long-secret-value-that-should-be-masked"
|
|
|
|
for _, key := range sensitiveKeys {
|
|
t.Run(key, func(t *testing.T) {
|
|
masked := maskSensitiveValue(key, longValue)
|
|
// Should not contain the full value
|
|
if masked == longValue {
|
|
t.Errorf("key %q: value was not masked", key)
|
|
}
|
|
// Should contain mask marker
|
|
if !strings.Contains(masked, "****") {
|
|
t.Errorf("key %q: masked value %q does not contain ****", key, masked)
|
|
}
|
|
// First 4 chars should be visible
|
|
if !strings.HasPrefix(masked, longValue[:4]) {
|
|
t.Errorf("key %q: masked value should start with first 4 chars", key)
|
|
}
|
|
// Last 4 chars should be visible
|
|
if !strings.HasSuffix(masked, longValue[len(longValue)-4:]) {
|
|
t.Errorf("key %q: masked value should end with last 4 chars", key)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMaskSensitiveValue_NonSensitivePassthrough(t *testing.T) {
|
|
// Non-sensitive keys should pass through unchanged
|
|
nonSensitiveKeys := []string{
|
|
"ANTHROPIC_BASE_URL",
|
|
"BASE_URL",
|
|
"MODEL",
|
|
"BACKEND",
|
|
"WORKDIR",
|
|
"HOME",
|
|
"PATH",
|
|
}
|
|
value := "any-value-here-12345"
|
|
|
|
for _, key := range nonSensitiveKeys {
|
|
t.Run(key, func(t *testing.T) {
|
|
result := maskSensitiveValue(key, value)
|
|
if result != value {
|
|
t.Errorf("key %q: expected passthrough but got %q", key, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestClaudeBackendEnv tests that ClaudeBackend.Env returns correct env vars
|
|
func TestClaudeBackendEnv(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
baseURL string
|
|
apiKey string
|
|
expectKeys []string
|
|
expectNil bool
|
|
}{
|
|
{
|
|
name: "both base_url and api_key",
|
|
baseURL: "https://api.custom.com",
|
|
apiKey: "sk-test-key-12345",
|
|
expectKeys: []string{"ANTHROPIC_BASE_URL", "ANTHROPIC_API_KEY"},
|
|
},
|
|
{
|
|
name: "only base_url",
|
|
baseURL: "https://api.custom.com",
|
|
apiKey: "",
|
|
expectKeys: []string{"ANTHROPIC_BASE_URL"},
|
|
},
|
|
{
|
|
name: "only api_key",
|
|
baseURL: "",
|
|
apiKey: "sk-test-key-12345",
|
|
expectKeys: []string{"ANTHROPIC_API_KEY"},
|
|
},
|
|
{
|
|
name: "both empty",
|
|
baseURL: "",
|
|
apiKey: "",
|
|
expectNil: true,
|
|
},
|
|
{
|
|
name: "whitespace only",
|
|
baseURL: " ",
|
|
apiKey: " ",
|
|
expectNil: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
b := backend.ClaudeBackend{}
|
|
env := b.Env(tt.baseURL, tt.apiKey)
|
|
|
|
if tt.expectNil {
|
|
if env != nil {
|
|
t.Errorf("expected nil env, got %v", env)
|
|
}
|
|
return
|
|
}
|
|
|
|
if env == nil {
|
|
t.Fatal("expected non-nil env")
|
|
}
|
|
|
|
for _, key := range tt.expectKeys {
|
|
if _, ok := env[key]; !ok {
|
|
t.Errorf("expected key %q in env", key)
|
|
}
|
|
}
|
|
|
|
// Verify values are correct
|
|
if tt.baseURL != "" && strings.TrimSpace(tt.baseURL) != "" {
|
|
if env["ANTHROPIC_BASE_URL"] != strings.TrimSpace(tt.baseURL) {
|
|
t.Errorf("ANTHROPIC_BASE_URL = %q, want %q", env["ANTHROPIC_BASE_URL"], strings.TrimSpace(tt.baseURL))
|
|
}
|
|
}
|
|
if tt.apiKey != "" && strings.TrimSpace(tt.apiKey) != "" {
|
|
if env["ANTHROPIC_API_KEY"] != strings.TrimSpace(tt.apiKey) {
|
|
t.Errorf("ANTHROPIC_API_KEY = %q, want %q", env["ANTHROPIC_API_KEY"], strings.TrimSpace(tt.apiKey))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestEnvLoggingIntegration tests that env vars are properly masked in logs
|
|
func TestEnvLoggingIntegration(t *testing.T) {
|
|
b := backend.ClaudeBackend{}
|
|
baseURL := "https://api.minimaxi.com/anthropic"
|
|
apiKey := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.longjwttoken"
|
|
|
|
env := b.Env(baseURL, apiKey)
|
|
if env == nil {
|
|
t.Fatal("expected non-nil env")
|
|
}
|
|
|
|
// Verify that when we log these values, sensitive ones are masked
|
|
for k, v := range env {
|
|
masked := maskSensitiveValue(k, v)
|
|
|
|
if k == "ANTHROPIC_BASE_URL" {
|
|
// URL should not be masked
|
|
if masked != v {
|
|
t.Errorf("BASE_URL should not be masked: got %q, want %q", masked, v)
|
|
}
|
|
}
|
|
|
|
if k == "ANTHROPIC_API_KEY" {
|
|
// API key should be masked
|
|
if masked == v {
|
|
t.Errorf("API_KEY should be masked, but got original value")
|
|
}
|
|
if !strings.Contains(masked, "****") {
|
|
t.Errorf("masked API_KEY should contain ****: got %q", masked)
|
|
}
|
|
// Should still show first 4 and last 4 chars
|
|
if !strings.HasPrefix(masked, v[:4]) {
|
|
t.Errorf("masked value should start with first 4 chars of original")
|
|
}
|
|
if !strings.HasSuffix(masked, v[len(v)-4:]) {
|
|
t.Errorf("masked value should end with last 4 chars of original")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestGeminiBackendEnv tests GeminiBackend.Env for comparison
|
|
func TestGeminiBackendEnv(t *testing.T) {
|
|
b := backend.GeminiBackend{}
|
|
env := b.Env("https://custom.api", "gemini-api-key-12345")
|
|
|
|
if env == nil {
|
|
t.Fatal("expected non-nil env")
|
|
}
|
|
|
|
// Check that GEMINI env vars are set
|
|
if _, ok := env["GOOGLE_GEMINI_BASE_URL"]; !ok {
|
|
t.Error("expected GOOGLE_GEMINI_BASE_URL in env")
|
|
}
|
|
if _, ok := env["GEMINI_API_KEY"]; !ok {
|
|
t.Error("expected GEMINI_API_KEY in env")
|
|
}
|
|
|
|
// Verify masking works for Gemini keys too
|
|
for k, v := range env {
|
|
masked := maskSensitiveValue(k, v)
|
|
if strings.Contains(strings.ToLower(k), "key") {
|
|
if masked == v && len(v) > 0 {
|
|
t.Errorf("key %q should be masked", k)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCodexBackendEnv tests CodexBackend.Env
|
|
func TestCodexBackendEnv(t *testing.T) {
|
|
b := backend.CodexBackend{}
|
|
env := b.Env("https://custom.api", "codex-api-key-12345")
|
|
|
|
if env == nil {
|
|
t.Fatal("expected non-nil env for codex")
|
|
}
|
|
|
|
// Check for OPENAI env vars
|
|
if _, ok := env["OPENAI_BASE_URL"]; !ok {
|
|
t.Error("expected OPENAI_BASE_URL in env")
|
|
}
|
|
if _, ok := env["OPENAI_API_KEY"]; !ok {
|
|
t.Error("expected OPENAI_API_KEY in env")
|
|
}
|
|
}
|