package authz import ( "encoding/hex" "fmt" "os" "sync" "testing" "golang.org/x/crypto/bcrypt" ) func TestGenerateNewToken(t *testing.T) { token, hash, err := GenerateNewToken() if err != nil { t.Fatalf("GenerateNewToken failed: %v", err) } // Verify token length (32 bytes = 64 hex chars) if len(token) != TokenSize*2 { t.Errorf("Expected token length %d, got %d", TokenSize*2, len(token)) } // Verify hash is valid bcrypt if len(hash) < 60 { t.Errorf("Hash length too short for bcrypt: %d", len(hash)) } // Decode token back to bytes and verify hash matches tokenBytes, err := hex.DecodeString(token) if err != nil { t.Fatalf("Failed to decode token hex: %v", err) } err = bcrypt.CompareHashAndPassword([]byte(hash), tokenBytes) if err != nil { t.Errorf("Hash does not match token: %v", err) } } func TestTokenMap(t *testing.T) { // Original TestTokenMap content remains... } func TestSafeTokenMap(t *testing.T) { // Test creation stm := NewSafeTokenMap() if stm == nil { t.Fatal("NewSafeTokenMap returned nil") } // Test Set and Get id := AccessID("test123") hash := "testhash" stm.Set(id, hash) got, exists := stm.Get(id) if !exists { t.Error("Get returned false for existing key") } if got != hash { t.Errorf("Get returned wrong hash. Want %s, got %s", hash, got) } // Test non-existent key _, exists = stm.Get("nonexistent") if exists { t.Error("Get returned true for non-existent key") } // Test concurrent access to verify thread safety t.Run("Concurrent access", func(t *testing.T) { stm := NewSafeTokenMap() const goroutines = 100 var wg sync.WaitGroup wg.Add(goroutines * 2) // for both readers and writers // Launch writers for i := 0; i < goroutines; i++ { go func(i int) { defer wg.Done() id := AccessID(fmt.Sprintf("test%d", i)) hash := fmt.Sprintf("hash%d", i) stm.Set(id, hash) }(i) } // Launch readers for i := 0; i < goroutines; i++ { go func(i int) { defer wg.Done() id := AccessID(fmt.Sprintf("test%d", i)) // Keep trying to read until we get the value or timeout for j := 0; j < 1000; j++ { if hash, exists := stm.Get(id); exists { expected := fmt.Sprintf("hash%d", i) if hash != expected { t.Errorf("Got wrong hash for id %s. Want %s, got %s", id, expected, hash) } break } } }(i) } wg.Wait() }) // Test LoadFromFile t.Run("LoadFromFile", func(t *testing.T) { // Create a temporary CSV file for testing tmpfile, err := os.CreateTemp("", "tokens*.csv") if err != nil { t.Fatalf("Failed to create temp file: %v", err) } defer os.Remove(tmpfile.Name()) // Write test data testData := "access123,tester,testhash\naccess456,bot:deploy,hash2\n" if _, err := tmpfile.Write([]byte(testData)); err != nil { t.Fatalf("Failed to write test data: %v", err) } tmpfile.Close() stm := NewSafeTokenMap() err = stm.LoadFromFile(tmpfile.Name()) if err != nil { t.Fatalf("LoadFromFile failed: %v", err) } // Verify loaded data hash, exists := stm.Get(AccessID("access123")) if !exists || hash != "testhash" { t.Errorf("Expected hash 'testhash' for access123, got %v", hash) } // Test loading non-existent file err = stm.LoadFromFile("nonexistent.csv") if err == nil { t.Error("Expected error when loading non-existent file") } }) } func TestIdentityMapThreadSafety(t *testing.T) { im := NewIdentityMap() const goroutines = 100 var wg sync.WaitGroup wg.Add(goroutines * 2) // for both registration and lookups // Launch registrations for i := 0; i < goroutines; i++ { go func(i int) { defer wg.Done() id := AccessID(fmt.Sprintf("test%d", i)) name := FriendlyName(fmt.Sprintf("user%d", i)) im.Register(id, name) }(i) } // Launch lookups for i := 0; i < goroutines; i++ { go func(i int) { defer wg.Done() id := AccessID(fmt.Sprintf("test%d", i)) // Keep trying to read until we get the value or timeout for j := 0; j < 1000; j++ { // Use thread-safe access method instead of direct map access im.mu.RLock() name, exists := im.IDToName[id] im.mu.RUnlock() if exists { expected := FriendlyName(fmt.Sprintf("user%d", i)) if name != expected { t.Errorf("Got wrong name for id %s. Want %s, got %s", id, expected, name) } break } } }(i) } wg.Wait() }