244 lines
6.3 KiB
Go
244 lines
6.3 KiB
Go
package tlsfingerprint
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|
)
|
|
|
|
func TestNewRegistry(t *testing.T) {
|
|
r := NewRegistry()
|
|
|
|
// Should have exactly one profile (the default)
|
|
if r.ProfileCount() != 1 {
|
|
t.Errorf("expected 1 profile, got %d", r.ProfileCount())
|
|
}
|
|
|
|
// Should have the default profile
|
|
profile := r.GetDefaultProfile()
|
|
if profile == nil {
|
|
t.Error("expected default profile to exist")
|
|
}
|
|
|
|
// Default profile name should be in the list
|
|
names := r.ProfileNames()
|
|
if len(names) != 1 || names[0] != DefaultProfileName {
|
|
t.Errorf("expected profile names to be [%s], got %v", DefaultProfileName, names)
|
|
}
|
|
}
|
|
|
|
func TestRegisterProfile(t *testing.T) {
|
|
r := NewRegistry()
|
|
|
|
// Register a new profile
|
|
customProfile := &Profile{
|
|
Name: "Custom Profile",
|
|
EnableGREASE: true,
|
|
}
|
|
r.RegisterProfile("custom", customProfile)
|
|
|
|
// Should now have 2 profiles
|
|
if r.ProfileCount() != 2 {
|
|
t.Errorf("expected 2 profiles, got %d", r.ProfileCount())
|
|
}
|
|
|
|
// Should be able to retrieve the custom profile
|
|
retrieved := r.GetProfile("custom")
|
|
if retrieved == nil {
|
|
t.Fatal("expected custom profile to exist")
|
|
}
|
|
if retrieved.Name != "Custom Profile" {
|
|
t.Errorf("expected profile name 'Custom Profile', got '%s'", retrieved.Name)
|
|
}
|
|
if !retrieved.EnableGREASE {
|
|
t.Error("expected EnableGREASE to be true")
|
|
}
|
|
}
|
|
|
|
func TestGetProfile(t *testing.T) {
|
|
r := NewRegistry()
|
|
|
|
// Get existing profile
|
|
profile := r.GetProfile(DefaultProfileName)
|
|
if profile == nil {
|
|
t.Error("expected default profile to exist")
|
|
}
|
|
|
|
// Get non-existing profile
|
|
nonExistent := r.GetProfile("nonexistent")
|
|
if nonExistent != nil {
|
|
t.Error("expected nil for non-existent profile")
|
|
}
|
|
}
|
|
|
|
func TestGetProfileByAccountID(t *testing.T) {
|
|
r := NewRegistry()
|
|
|
|
// With only default profile, all account IDs should return the same profile
|
|
for i := int64(0); i < 10; i++ {
|
|
profile := r.GetProfileByAccountID(i)
|
|
if profile == nil {
|
|
t.Errorf("expected profile for account %d, got nil", i)
|
|
}
|
|
}
|
|
|
|
// Add more profiles
|
|
r.RegisterProfile("profile_a", &Profile{Name: "Profile A"})
|
|
r.RegisterProfile("profile_b", &Profile{Name: "Profile B"})
|
|
|
|
// Now we have 3 profiles: claude_cli_v2, profile_a, profile_b
|
|
// Names are sorted, so order is: claude_cli_v2, profile_a, profile_b
|
|
expectedOrder := []string{DefaultProfileName, "profile_a", "profile_b"}
|
|
names := r.ProfileNames()
|
|
for i, name := range expectedOrder {
|
|
if names[i] != name {
|
|
t.Errorf("expected name at index %d to be %s, got %s", i, name, names[i])
|
|
}
|
|
}
|
|
|
|
// Test modulo selection
|
|
// Account ID 0 % 3 = 0 -> claude_cli_v2
|
|
// Account ID 1 % 3 = 1 -> profile_a
|
|
// Account ID 2 % 3 = 2 -> profile_b
|
|
// Account ID 3 % 3 = 0 -> claude_cli_v2
|
|
testCases := []struct {
|
|
accountID int64
|
|
expectedName string
|
|
}{
|
|
{0, "Claude CLI 2.x (Node.js 20.x + OpenSSL 3.x)"},
|
|
{1, "Profile A"},
|
|
{2, "Profile B"},
|
|
{3, "Claude CLI 2.x (Node.js 20.x + OpenSSL 3.x)"},
|
|
{4, "Profile A"},
|
|
{5, "Profile B"},
|
|
{100, "Profile A"}, // 100 % 3 = 1
|
|
{-1, "Profile A"}, // |-1| % 3 = 1
|
|
{-3, "Claude CLI 2.x (Node.js 20.x + OpenSSL 3.x)"}, // |-3| % 3 = 0
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
profile := r.GetProfileByAccountID(tc.accountID)
|
|
if profile == nil {
|
|
t.Errorf("expected profile for account %d, got nil", tc.accountID)
|
|
continue
|
|
}
|
|
if profile.Name != tc.expectedName {
|
|
t.Errorf("account %d: expected profile name '%s', got '%s'", tc.accountID, tc.expectedName, profile.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewRegistryFromConfig(t *testing.T) {
|
|
// Test with nil config
|
|
r := NewRegistryFromConfig(nil)
|
|
if r.ProfileCount() != 1 {
|
|
t.Errorf("expected 1 profile with nil config, got %d", r.ProfileCount())
|
|
}
|
|
|
|
// Test with disabled config
|
|
disabledCfg := &config.TLSFingerprintConfig{
|
|
Enabled: false,
|
|
}
|
|
r = NewRegistryFromConfig(disabledCfg)
|
|
if r.ProfileCount() != 1 {
|
|
t.Errorf("expected 1 profile with disabled config, got %d", r.ProfileCount())
|
|
}
|
|
|
|
// Test with enabled config and custom profiles
|
|
enabledCfg := &config.TLSFingerprintConfig{
|
|
Enabled: true,
|
|
Profiles: map[string]config.TLSProfileConfig{
|
|
"custom1": {
|
|
Name: "Custom Profile 1",
|
|
EnableGREASE: true,
|
|
},
|
|
"custom2": {
|
|
Name: "Custom Profile 2",
|
|
EnableGREASE: false,
|
|
},
|
|
},
|
|
}
|
|
r = NewRegistryFromConfig(enabledCfg)
|
|
|
|
// Should have 3 profiles: default + 2 custom
|
|
if r.ProfileCount() != 3 {
|
|
t.Errorf("expected 3 profiles, got %d", r.ProfileCount())
|
|
}
|
|
|
|
// Check custom profiles exist
|
|
custom1 := r.GetProfile("custom1")
|
|
if custom1 == nil || custom1.Name != "Custom Profile 1" {
|
|
t.Error("expected custom1 profile to exist with correct name")
|
|
}
|
|
custom2 := r.GetProfile("custom2")
|
|
if custom2 == nil || custom2.Name != "Custom Profile 2" {
|
|
t.Error("expected custom2 profile to exist with correct name")
|
|
}
|
|
}
|
|
|
|
func TestProfileNames(t *testing.T) {
|
|
r := NewRegistry()
|
|
|
|
// Add profiles in non-alphabetical order
|
|
r.RegisterProfile("zebra", &Profile{Name: "Zebra"})
|
|
r.RegisterProfile("alpha", &Profile{Name: "Alpha"})
|
|
r.RegisterProfile("beta", &Profile{Name: "Beta"})
|
|
|
|
names := r.ProfileNames()
|
|
|
|
// Should be sorted alphabetically
|
|
expected := []string{"alpha", "beta", DefaultProfileName, "zebra"}
|
|
if len(names) != len(expected) {
|
|
t.Errorf("expected %d names, got %d", len(expected), len(names))
|
|
}
|
|
for i, name := range expected {
|
|
if names[i] != name {
|
|
t.Errorf("expected name at index %d to be %s, got %s", i, name, names[i])
|
|
}
|
|
}
|
|
|
|
// Test that returned slice is a copy (modifying it shouldn't affect registry)
|
|
names[0] = "modified"
|
|
originalNames := r.ProfileNames()
|
|
if originalNames[0] == "modified" {
|
|
t.Error("modifying returned slice should not affect registry")
|
|
}
|
|
}
|
|
|
|
func TestConcurrentAccess(t *testing.T) {
|
|
r := NewRegistry()
|
|
|
|
// Run concurrent reads and writes
|
|
done := make(chan bool)
|
|
|
|
// Writers
|
|
for i := 0; i < 10; i++ {
|
|
go func(id int) {
|
|
for j := 0; j < 100; j++ {
|
|
r.RegisterProfile("concurrent"+string(rune('0'+id)), &Profile{Name: "Concurrent"})
|
|
}
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
// Readers
|
|
for i := 0; i < 10; i++ {
|
|
go func(id int) {
|
|
for j := 0; j < 100; j++ {
|
|
_ = r.ProfileCount()
|
|
_ = r.ProfileNames()
|
|
_ = r.GetProfileByAccountID(int64(id * j))
|
|
_ = r.GetProfile(DefaultProfileName)
|
|
}
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
// Wait for all goroutines
|
|
for i := 0; i < 20; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Test should pass without data races (run with -race flag)
|
|
}
|