Files
nofx/api/traderid_test.go
0xYYBB | ZYY | Bobo 031cc19c99 fix(api): use UUID to ensure traderID uniqueness (#893) (#1008)
## Problem
When multiple users create traders with the same exchange + AI model
combination within the same second, they generate identical traderIDs,
causing data conflicts.

Old code (Line 496):
```go
traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix())
```

## Solution
Use UUID to guarantee 100% uniqueness while preserving prefix for debugging:
```go
traderID := fmt.Sprintf("%s_%s_%s", req.ExchangeID, req.AIModelID, uuid.New().String())
```

Example output: `binance_gpt-4_a1b2c3d4-e5f6-7890-abcd-ef1234567890`

## Changes
- `api/server.go:495-497`: Use UUID for traderID generation
- `api/traderid_test.go`: New test file with 3 comprehensive tests

## Tests
 All tests passed (0.189s)
 TestTraderIDUniqueness - 100 unique IDs generated
 TestTraderIDFormat - 3 exchange/model combinations validated
 TestTraderIDNoCollision - 1000 iterations without collision

Fixes #893

Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
2025-11-15 22:20:06 -05:00

124 lines
3.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package api
import (
"fmt"
"strings"
"testing"
"github.com/google/uuid"
)
// TestTraderIDUniqueness 测试 traderID 的唯一性(修复 Issue #893
// 验证即使在相同的 exchange 和 AI model 下,也能生成唯一的 traderID
func TestTraderIDUniqueness(t *testing.T) {
exchangeID := "binance"
aiModelID := "gpt-4"
// 模拟同时创建 100 个 trader相同参数
traderIDs := make(map[string]bool)
const numTraders = 100
for i := 0; i < numTraders; i++ {
// 模拟 api/server.go:497 的 traderID 生成逻辑
traderID := generateTraderID(exchangeID, aiModelID)
// ✅ 检查是否重复
if traderIDs[traderID] {
t.Errorf("Duplicate traderID detected: %s", traderID)
}
traderIDs[traderID] = true
// ✅ 验证格式:应该是 "exchange_model_uuid"
if !isValidTraderIDFormat(traderID, exchangeID, aiModelID) {
t.Errorf("Invalid traderID format: %s", traderID)
}
}
// ✅ 验证生成了预期数量的唯一 ID
if len(traderIDs) != numTraders {
t.Errorf("Expected %d unique traderIDs, got %d", numTraders, len(traderIDs))
}
}
// generateTraderID 辅助函数,模拟 api/server.go 中的 traderID 生成逻辑
func generateTraderID(exchangeID, aiModelID string) string {
return fmt.Sprintf("%s_%s_%s", exchangeID, aiModelID, uuid.New().String())
}
// isValidTraderIDFormat 验证 traderID 格式是否符合预期
func isValidTraderIDFormat(traderID, expectedExchange, expectedModel string) bool {
// 格式exchange_model_uuid
// 例如binance_gpt-4_a1b2c3d4-e5f6-7890-abcd-ef1234567890
parts := strings.Split(traderID, "_")
if len(parts) < 3 {
return false
}
// 验证前缀
if parts[0] != expectedExchange {
return false
}
// AI model 可能包含连字符(如 gpt-4所以需要重组
// 最后一部分应该是 UUID
uuidPart := parts[len(parts)-1]
// 验证 UUID 格式36 个字符,包含 4 个连字符)
_, err := uuid.Parse(uuidPart)
return err == nil
}
// TestTraderIDFormat 测试 traderID 格式的正确性
func TestTraderIDFormat(t *testing.T) {
tests := []struct {
name string
exchangeID string
aiModelID string
}{
{"Binance + GPT-4", "binance", "gpt-4"},
{"Hyperliquid + Claude", "hyperliquid", "claude-3"},
{"OKX + Qwen", "okx", "qwen-2.5"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
traderID := generateTraderID(tt.exchangeID, tt.aiModelID)
// ✅ 验证包含正确的前缀
if !strings.HasPrefix(traderID, tt.exchangeID+"_"+tt.aiModelID+"_") {
t.Errorf("traderID does not have correct prefix. Got: %s", traderID)
}
// ✅ 验证格式有效
if !isValidTraderIDFormat(traderID, tt.exchangeID, tt.aiModelID) {
t.Errorf("Invalid traderID format: %s", traderID)
}
// ✅ 验证长度合理(至少应该有 exchange + model + "_" + UUID(36) 的长度)
minLength := len(tt.exchangeID) + len(tt.aiModelID) + 2 + 36 // 2个下划线 + 36字符UUID
if len(traderID) < minLength {
t.Errorf("traderID too short: expected at least %d chars, got %d", minLength, len(traderID))
}
})
}
}
// TestTraderIDNoCollision 测试在高并发场景下不会产生碰撞
func TestTraderIDNoCollision(t *testing.T) {
const iterations = 1000
uniqueIDs := make(map[string]bool, iterations)
// 模拟高并发场景
for i := 0; i < iterations; i++ {
id := generateTraderID("binance", "gpt-4")
if uniqueIDs[id] {
t.Fatalf("Collision detected after %d iterations: %s", i+1, id)
}
uniqueIDs[id] = true
}
if len(uniqueIDs) != iterations {
t.Errorf("Expected %d unique IDs, got %d", iterations, len(uniqueIDs))
}
}