Files
nofx/mcp/config_usage_test.go
T
tinkle-community 8e294a5eed refactor: restructure project directories for better modularity
- Delete llm/ dead code (3 files, zero references)
- Split mcp/ into sub-packages: mcp/provider/ (8 providers) and
  mcp/payment/ (4 payment clients) with registry pattern
- Export Client internal fields and ClientHooks interface for
  sub-package access
- Split api/server.go (3892 lines) into 8 domain-specific handler files
- Split trader/auto_trader.go (2296 lines) into 5 focused files
- Reorganize web/src/components/ flat files into auth/, charts/,
  trader/, common/, modals/, backtest/ subdirectories
- Update all consumer imports to use registry-based provider creation
2026-03-11 23:58:13 +08:00

263 lines
7.0 KiB
Go

package mcp
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"testing"
"time"
)
// ============================================================
// Test Config Fields Are Actually Used (Verify Issue 2 Fix)
// ============================================================
func TestConfig_MaxRetries_IsUsed(t *testing.T) {
mockHTTP := NewMockHTTPClient()
mockLogger := NewMockLogger()
// Set HTTP client to return error
callCount := 0
mockHTTP.ResponseFunc = func(req *http.Request) (*http.Response, error) {
callCount++
return nil, errors.New("connection reset")
}
// Create client and set custom retry count to 5
client := NewClient(
WithHTTPClient(mockHTTP.ToHTTPClient()),
WithLogger(mockLogger),
WithAPIKey("sk-test-key"),
WithMaxRetries(5), // Set to retry 5 times
)
// Call API (should fail)
_, err := client.CallWithMessages("system", "user")
if err == nil {
t.Error("should error")
}
// Verify indeed retried 5 times (not the default 3 times)
if callCount != 5 {
t.Errorf("expected 5 retry attempts (from WithMaxRetries(5)), got %d", callCount)
}
// Verify logs show correct retry count
logs := mockLogger.GetLogsByLevel("WARN")
expectedWarningCount := 4 // Warnings will be printed on 2nd, 3rd, 4th, 5th retry
actualWarningCount := 0
for _, log := range logs {
if log.Message == "⚠️ AI API call failed, retrying (2/5)..." ||
log.Message == "⚠️ AI API call failed, retrying (3/5)..." ||
log.Message == "⚠️ AI API call failed, retrying (4/5)..." ||
log.Message == "⚠️ AI API call failed, retrying (5/5)..." {
actualWarningCount++
}
}
if actualWarningCount != expectedWarningCount {
t.Errorf("expected %d warning logs, got %d", expectedWarningCount, actualWarningCount)
for _, log := range logs {
t.Logf(" WARN: %s", log.Message)
}
}
}
func TestConfig_Temperature_IsUsed(t *testing.T) {
mockHTTP := NewMockHTTPClient()
mockHTTP.SetSuccessResponse("AI response")
mockLogger := NewMockLogger()
customTemperature := 0.8
// Create client and set custom temperature
client := NewClient(
WithHTTPClient(mockHTTP.ToHTTPClient()),
WithLogger(mockLogger),
WithAPIKey("sk-test-key"),
WithTemperature(customTemperature), // Set custom temperature
)
c := client.(*Client)
// Build request body
requestBody := c.BuildMCPRequestBody("system", "user")
// Verify temperature field
temp, ok := requestBody["temperature"].(float64)
if !ok {
t.Fatal("temperature should be float64")
}
if temp != customTemperature {
t.Errorf("expected temperature %f (from WithTemperature), got %f", customTemperature, temp)
}
// Can also verify through actual HTTP request
_, err := client.CallWithMessages("system", "user")
if err != nil {
t.Fatalf("should not error: %v", err)
}
// Check sent request body
requests := mockHTTP.GetRequests()
if len(requests) != 1 {
t.Fatalf("expected 1 request, got %d", len(requests))
}
// Parse request body
var body map[string]interface{}
decoder := json.NewDecoder(requests[0].Body)
if err := decoder.Decode(&body); err != nil {
t.Fatalf("failed to decode request body: %v", err)
}
// Verify temperature
if body["temperature"] != customTemperature {
t.Errorf("expected temperature %f in HTTP request, got %v", customTemperature, body["temperature"])
}
}
func TestConfig_RetryWaitBase_IsUsed(t *testing.T) {
mockHTTP := NewMockHTTPClient()
mockLogger := NewMockLogger()
// Set success response (before ResponseFunc)
mockHTTP.SetSuccessResponse("AI response")
// Set HTTP client to return error first 2 times, success on 3rd time
callCount := 0
successResponse := mockHTTP.Response // Save success response string
mockHTTP.ResponseFunc = func(req *http.Request) (*http.Response, error) {
callCount++
if callCount <= 2 {
return nil, errors.New("timeout exceeded")
}
// 3rd time return success response
return &http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(successResponse)),
Header: make(http.Header),
}, nil
}
// Set custom retry wait base to 1 second (instead of default 2 seconds)
customWaitBase := 1 * time.Second
client := NewClient(
WithHTTPClient(mockHTTP.ToHTTPClient()),
WithLogger(mockLogger),
WithAPIKey("sk-test-key"),
WithRetryWaitBase(customWaitBase), // Set custom wait time
WithMaxRetries(3),
)
// Record start time
start := time.Now()
// Call API
_, err := client.CallWithMessages("system", "user")
// Record end time
elapsed := time.Since(start)
// 3rd time succeeds, but failed 2 times before
if err != nil {
t.Fatalf("should succeed on 3rd attempt, got error: %v", err)
}
if callCount != 3 {
t.Errorf("expected 3 attempts, got %d", callCount)
}
// Verify wait time
// After 1st failure wait 1s (customWaitBase * 1)
// After 2nd failure wait 2s (customWaitBase * 2)
// Total wait time should be about 3s (allow some error)
expectedWait := 3 * time.Second
tolerance := 200 * time.Millisecond
if elapsed < expectedWait-tolerance || elapsed > expectedWait+tolerance {
t.Errorf("expected total time ~%v (with RetryWaitBase=%v), got %v", expectedWait, customWaitBase, elapsed)
}
}
func TestConfig_RetryableErrors_IsUsed(t *testing.T) {
mockHTTP := NewMockHTTPClient()
mockLogger := NewMockLogger()
// Custom retryable error list (only contains "custom error")
customRetryableErrors := []string{"custom error"}
client := NewClient(
WithHTTPClient(mockHTTP.ToHTTPClient()),
WithLogger(mockLogger),
WithAPIKey("sk-test-key"),
)
c := client.(*Client)
// Modify config's RetryableErrors (no WithRetryableErrors option yet)
c.Cfg.RetryableErrors = customRetryableErrors
tests := []struct {
name string
err error
retryable bool
}{
{
name: "custom error should be retryable",
err: errors.New("custom error occurred"),
retryable: true,
},
{
name: "EOF should NOT be retryable (not in custom list)",
err: errors.New("unexpected EOF"),
retryable: false,
},
{
name: "timeout should NOT be retryable (not in custom list)",
err: errors.New("timeout exceeded"),
retryable: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := c.IsRetryableError(tt.err)
if result != tt.retryable {
t.Errorf("expected isRetryableError(%v) = %v, got %v", tt.err, tt.retryable, result)
}
})
}
}
// ============================================================
// Test Default Values
// ============================================================
func TestConfig_DefaultValues(t *testing.T) {
client := NewClient()
c := client.(*Client)
// Verify default values
if c.Cfg.MaxRetries != 3 {
t.Errorf("default MaxRetries should be 3, got %d", c.Cfg.MaxRetries)
}
if c.Cfg.Temperature != 0.5 {
t.Errorf("default Temperature should be 0.5, got %f", c.Cfg.Temperature)
}
if c.Cfg.RetryWaitBase != 2*time.Second {
t.Errorf("default RetryWaitBase should be 2s, got %v", c.Cfg.RetryWaitBase)
}
if len(c.Cfg.RetryableErrors) == 0 {
t.Error("default RetryableErrors should not be empty")
}
}