package appearances import ( "fmt" "sync" "testing" ) func TestNewAppearance(t *testing.T) { tests := []struct { name string id int32 appearanceName string minClientVersion int16 wantNil bool }{ { name: "valid appearance", id: 100, appearanceName: "Test Appearance", minClientVersion: 1096, wantNil: false, }, { name: "empty name returns nil", id: 200, appearanceName: "", minClientVersion: 1096, wantNil: true, }, { name: "negative id allowed", id: -50, appearanceName: "Negative ID", minClientVersion: 0, wantNil: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { app := NewAppearance(tt.id, tt.appearanceName, tt.minClientVersion) if tt.wantNil { if app != nil { t.Errorf("expected nil appearance, got %v", app) } return } if app == nil { t.Fatal("expected non-nil appearance, got nil") } if app.GetID() != tt.id { t.Errorf("ID = %v, want %v", app.GetID(), tt.id) } if app.GetName() != tt.appearanceName { t.Errorf("Name = %v, want %v", app.GetName(), tt.appearanceName) } if app.GetMinClientVersion() != tt.minClientVersion { t.Errorf("MinClientVersion = %v, want %v", app.GetMinClientVersion(), tt.minClientVersion) } }) } } func TestAppearanceGetters(t *testing.T) { app := NewAppearance(123, "Test Appearance", 1096) if id := app.GetID(); id != 123 { t.Errorf("GetID() = %v, want 123", id) } if name := app.GetName(); name != "Test Appearance" { t.Errorf("GetName() = %v, want Test Appearance", name) } if nameStr := app.GetNameString(); nameStr != "Test Appearance" { t.Errorf("GetNameString() = %v, want Test Appearance", nameStr) } if minVer := app.GetMinClientVersion(); minVer != 1096 { t.Errorf("GetMinClientVersion() = %v, want 1096", minVer) } } func TestAppearanceSetters(t *testing.T) { app := NewAppearance(100, "Original", 1000) app.SetName("Modified Name") if app.GetName() != "Modified Name" { t.Errorf("SetName failed: got %v, want Modified Name", app.GetName()) } app.SetMinClientVersion(2000) if app.GetMinClientVersion() != 2000 { t.Errorf("SetMinClientVersion failed: got %v, want 2000", app.GetMinClientVersion()) } } func TestIsCompatibleWithClient(t *testing.T) { app := NewAppearance(100, "Test", 1096) tests := []struct { clientVersion int16 want bool }{ {1095, false}, // Below minimum {1096, true}, // Exact minimum {1097, true}, // Above minimum {2000, true}, // Well above minimum {0, false}, // Zero version } for _, tt := range tests { t.Run("", func(t *testing.T) { if got := app.IsCompatibleWithClient(tt.clientVersion); got != tt.want { t.Errorf("IsCompatibleWithClient(%v) = %v, want %v", tt.clientVersion, got, tt.want) } }) } } func TestAppearanceClone(t *testing.T) { original := NewAppearance(500, "Original Appearance", 1200) clone := original.Clone() if clone == nil { t.Fatal("Clone returned nil") } if clone == original { t.Error("Clone returned same pointer as original") } if clone.GetID() != original.GetID() { t.Errorf("Clone ID = %v, want %v", clone.GetID(), original.GetID()) } if clone.GetName() != original.GetName() { t.Errorf("Clone Name = %v, want %v", clone.GetName(), original.GetName()) } if clone.GetMinClientVersion() != original.GetMinClientVersion() { t.Errorf("Clone MinClientVersion = %v, want %v", clone.GetMinClientVersion(), original.GetMinClientVersion()) } // Verify modification independence clone.SetName("Modified Clone") if original.GetName() == "Modified Clone" { t.Error("Modifying clone affected original") } } func TestNewAppearances(t *testing.T) { apps := NewAppearances() if apps == nil { t.Fatal("NewAppearances returned nil") } if count := apps.GetAppearanceCount(); count != 0 { t.Errorf("New appearances collection should be empty, got count %v", count) } } func TestAppearancesInsertAndFind(t *testing.T) { apps := NewAppearances() // Test nil insertion err := apps.InsertAppearance(nil) if err == nil { t.Error("InsertAppearance(nil) should return error") } // Insert valid appearances app1 := NewAppearance(100, "Appearance 1", 1000) app2 := NewAppearance(200, "Appearance 2", 1100) if err := apps.InsertAppearance(app1); err != nil { t.Errorf("InsertAppearance failed: %v", err) } if err := apps.InsertAppearance(app2); err != nil { t.Errorf("InsertAppearance failed: %v", err) } // Test finding by ID found := apps.FindAppearanceByID(100) if found == nil { t.Error("FindAppearanceByID(100) returned nil") } else if found.GetName() != "Appearance 1" { t.Errorf("FindAppearanceByID(100) returned wrong appearance: %v", found.GetName()) } // Test finding non-existent ID notFound := apps.FindAppearanceByID(999) if notFound != nil { t.Errorf("FindAppearanceByID(999) should return nil, got %v", notFound) } } func TestAppearancesHasAppearance(t *testing.T) { apps := NewAppearances() app := NewAppearance(300, "Test", 1000) apps.InsertAppearance(app) if !apps.HasAppearance(300) { t.Error("HasAppearance(300) should return true") } if apps.HasAppearance(999) { t.Error("HasAppearance(999) should return false") } } func TestAppearancesGetAllAndCount(t *testing.T) { apps := NewAppearances() // Add multiple appearances for i := int32(1); i <= 5; i++ { app := NewAppearance(i*100, "Appearance", 1000) apps.InsertAppearance(app) } if count := apps.GetAppearanceCount(); count != 5 { t.Errorf("GetAppearanceCount() = %v, want 5", count) } all := apps.GetAllAppearances() if len(all) != 5 { t.Errorf("GetAllAppearances() returned %v items, want 5", len(all)) } // Verify it's a copy by modifying returned map delete(all, 100) if apps.GetAppearanceCount() != 5 { t.Error("Modifying returned map affected internal state") } } func TestAppearancesGetIDs(t *testing.T) { apps := NewAppearances() expectedIDs := []int32{100, 200, 300} for _, id := range expectedIDs { app := NewAppearance(id, "Test", 1000) apps.InsertAppearance(app) } ids := apps.GetAppearanceIDs() if len(ids) != len(expectedIDs) { t.Errorf("GetAppearanceIDs() returned %v IDs, want %v", len(ids), len(expectedIDs)) } // Check all expected IDs are present idMap := make(map[int32]bool) for _, id := range ids { idMap[id] = true } for _, expected := range expectedIDs { if !idMap[expected] { t.Errorf("Expected ID %v not found in returned IDs", expected) } } } func TestAppearancesFindByName(t *testing.T) { apps := NewAppearances() apps.InsertAppearance(NewAppearance(1, "Human Male", 1000)) apps.InsertAppearance(NewAppearance(2, "Human Female", 1000)) apps.InsertAppearance(NewAppearance(3, "Elf Male", 1000)) apps.InsertAppearance(NewAppearance(4, "Dwarf Female", 1000)) // Test partial matching humanApps := apps.FindAppearancesByName("Human") if len(humanApps) != 2 { t.Errorf("FindAppearancesByName('Human') returned %v results, want 2", len(humanApps)) } // Test case sensitivity maleApps := apps.FindAppearancesByName("Male") if len(maleApps) != 2 { t.Errorf("FindAppearancesByName('Male') returned %v results, want 2", len(maleApps)) } // Test empty substring allApps := apps.FindAppearancesByName("") if len(allApps) != 4 { t.Errorf("FindAppearancesByName('') returned %v results, want 4", len(allApps)) } // Test no matches noMatches := apps.FindAppearancesByName("Orc") if len(noMatches) != 0 { t.Errorf("FindAppearancesByName('Orc') returned %v results, want 0", len(noMatches)) } } func TestAppearancesFindByMinClient(t *testing.T) { apps := NewAppearances() apps.InsertAppearance(NewAppearance(1, "Old", 1000)) apps.InsertAppearance(NewAppearance(2, "Medium1", 1096)) apps.InsertAppearance(NewAppearance(3, "Medium2", 1096)) apps.InsertAppearance(NewAppearance(4, "New", 1200)) results := apps.FindAppearancesByMinClient(1096) if len(results) != 2 { t.Errorf("FindAppearancesByMinClient(1096) returned %v results, want 2", len(results)) } results = apps.FindAppearancesByMinClient(999) if len(results) != 0 { t.Errorf("FindAppearancesByMinClient(999) returned %v results, want 0", len(results)) } } func TestAppearancesGetCompatible(t *testing.T) { apps := NewAppearances() apps.InsertAppearance(NewAppearance(1, "Old", 1000)) apps.InsertAppearance(NewAppearance(2, "Medium", 1096)) apps.InsertAppearance(NewAppearance(3, "New", 1200)) apps.InsertAppearance(NewAppearance(4, "Newer", 1300)) // Client version 1100 should get Old and Medium compatible := apps.GetCompatibleAppearances(1100) if len(compatible) != 2 { t.Errorf("GetCompatibleAppearances(1100) returned %v results, want 2", len(compatible)) } // Client version 1500 should get all compatible = apps.GetCompatibleAppearances(1500) if len(compatible) != 4 { t.Errorf("GetCompatibleAppearances(1500) returned %v results, want 4", len(compatible)) } // Client version 500 should get none compatible = apps.GetCompatibleAppearances(500) if len(compatible) != 0 { t.Errorf("GetCompatibleAppearances(500) returned %v results, want 0", len(compatible)) } } func TestAppearancesRemove(t *testing.T) { apps := NewAppearances() app := NewAppearance(100, "Test", 1000) apps.InsertAppearance(app) // Remove existing if !apps.RemoveAppearance(100) { t.Error("RemoveAppearance(100) should return true") } if apps.HasAppearance(100) { t.Error("Appearance 100 should have been removed") } // Remove non-existent if apps.RemoveAppearance(100) { t.Error("RemoveAppearance(100) should return false for non-existent ID") } } func TestAppearancesUpdate(t *testing.T) { apps := NewAppearances() // Test updating nil err := apps.UpdateAppearance(nil) if err == nil { t.Error("UpdateAppearance(nil) should return error") } // Insert and update original := NewAppearance(100, "Original", 1000) apps.InsertAppearance(original) updated := NewAppearance(100, "Updated", 1100) err = apps.UpdateAppearance(updated) if err != nil { t.Errorf("UpdateAppearance failed: %v", err) } found := apps.FindAppearanceByID(100) if found.GetName() != "Updated" { t.Errorf("Updated appearance name = %v, want Updated", found.GetName()) } // Update non-existent (should insert) new := NewAppearance(200, "New", 1200) err = apps.UpdateAppearance(new) if err != nil { t.Errorf("UpdateAppearance failed: %v", err) } if !apps.HasAppearance(200) { t.Error("UpdateAppearance should insert non-existent appearance") } } func TestAppearancesGetByIDRange(t *testing.T) { apps := NewAppearances() // Insert appearances with various IDs for _, id := range []int32{5, 10, 15, 20, 25, 30} { apps.InsertAppearance(NewAppearance(id, "Test", 1000)) } results := apps.GetAppearancesByIDRange(10, 20) if len(results) != 3 { // Should get 10, 15, 20 t.Errorf("GetAppearancesByIDRange(10, 20) returned %v results, want 3", len(results)) } // Verify correct IDs idMap := make(map[int32]bool) for _, app := range results { idMap[app.GetID()] = true } for _, expectedID := range []int32{10, 15, 20} { if !idMap[expectedID] { t.Errorf("Expected ID %v not found in range results", expectedID) } } // Test empty range results = apps.GetAppearancesByIDRange(100, 200) if len(results) != 0 { t.Errorf("GetAppearancesByIDRange(100, 200) returned %v results, want 0", len(results)) } } func TestAppearancesValidate(t *testing.T) { apps := NewAppearances() // Valid appearances apps.InsertAppearance(NewAppearance(100, "Valid", 1000)) issues := apps.ValidateAppearances() if len(issues) != 0 { t.Errorf("ValidateAppearances() returned issues for valid data: %v", issues) } if !apps.IsValid() { t.Error("IsValid() should return true for valid data") } // Force invalid state by directly modifying map apps.mutex.Lock() apps.appearanceMap[200] = nil apps.appearanceMap[300] = NewAppearance(301, "", 1000) // ID mismatch and empty name apps.appearanceMap[400] = NewAppearance(400, "Negative", -100) apps.mutex.Unlock() issues = apps.ValidateAppearances() if len(issues) < 3 { t.Errorf("ValidateAppearances() should return at least 3 issues, got %v", len(issues)) } if apps.IsValid() { t.Error("IsValid() should return false for invalid data") } } func TestAppearancesStatistics(t *testing.T) { apps := NewAppearances() // Add appearances with different client versions apps.InsertAppearance(NewAppearance(10, "A", 1000)) apps.InsertAppearance(NewAppearance(20, "B", 1000)) apps.InsertAppearance(NewAppearance(30, "C", 1096)) apps.InsertAppearance(NewAppearance(40, "D", 1096)) apps.InsertAppearance(NewAppearance(50, "E", 1096)) stats := apps.GetStatistics() if total, ok := stats["total_appearances"].(int); !ok || total != 5 { t.Errorf("total_appearances = %v, want 5", stats["total_appearances"]) } if minID, ok := stats["min_id"].(int32); !ok || minID != 10 { t.Errorf("min_id = %v, want 10", stats["min_id"]) } if maxID, ok := stats["max_id"].(int32); !ok || maxID != 50 { t.Errorf("max_id = %v, want 50", stats["max_id"]) } if idRange, ok := stats["id_range"].(int32); !ok || idRange != 40 { t.Errorf("id_range = %v, want 40", stats["id_range"]) } if versionCounts, ok := stats["appearances_by_min_client"].(map[int16]int); ok { if versionCounts[1000] != 2 { t.Errorf("appearances with min client 1000 = %v, want 2", versionCounts[1000]) } if versionCounts[1096] != 3 { t.Errorf("appearances with min client 1096 = %v, want 3", versionCounts[1096]) } } else { t.Error("appearances_by_min_client not found in statistics") } } func TestAppearancesClearAndReset(t *testing.T) { apps := NewAppearances() // Add some appearances for i := int32(1); i <= 3; i++ { apps.InsertAppearance(NewAppearance(i*100, "Test", 1000)) } if apps.GetAppearanceCount() != 3 { t.Error("Setup failed: should have 3 appearances") } // Test ClearAppearances apps.ClearAppearances() if apps.GetAppearanceCount() != 0 { t.Errorf("ClearAppearances() failed: count = %v, want 0", apps.GetAppearanceCount()) } // Add again and test Reset for i := int32(1); i <= 3; i++ { apps.InsertAppearance(NewAppearance(i*100, "Test", 1000)) } apps.Reset() if apps.GetAppearanceCount() != 0 { t.Errorf("Reset() failed: count = %v, want 0", apps.GetAppearanceCount()) } } func TestAppearancesConcurrency(t *testing.T) { apps := NewAppearances() var wg sync.WaitGroup // Concurrent insertions for i := 0; i < 100; i++ { wg.Add(1) go func(id int32) { defer wg.Done() app := NewAppearance(id, "Concurrent", 1000) apps.InsertAppearance(app) }(int32(i)) } // Concurrent reads for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() _ = apps.GetAppearanceCount() _ = apps.GetAllAppearances() _ = apps.FindAppearanceByID(25) }() } // Concurrent searches for i := 0; i < 20; i++ { wg.Add(1) go func() { defer wg.Done() _ = apps.FindAppearancesByName("Concurrent") _ = apps.GetCompatibleAppearances(1100) }() } wg.Wait() // Verify all insertions succeeded if count := apps.GetAppearanceCount(); count != 100 { t.Errorf("After concurrent operations, count = %v, want 100", count) } } func TestContainsFunction(t *testing.T) { tests := []struct { str string substr string want bool }{ {"hello world", "world", true}, {"hello world", "World", false}, // Case sensitive {"hello", "hello world", false}, {"hello", "", true}, {"", "hello", false}, {"", "", true}, {"abcdef", "cde", true}, {"abcdef", "xyz", false}, } for _, tt := range tests { t.Run("", func(t *testing.T) { if got := contains(tt.str, tt.substr); got != tt.want { t.Errorf("contains(%q, %q) = %v, want %v", tt.str, tt.substr, got, tt.want) } }) } } // Benchmarks func BenchmarkAppearanceInsert(b *testing.B) { apps := NewAppearances() b.ResetTimer() for i := 0; i < b.N; i++ { app := NewAppearance(int32(i), "Benchmark", 1000) apps.InsertAppearance(app) } } func BenchmarkAppearanceFindByID(b *testing.B) { apps := NewAppearances() // Pre-populate for i := 0; i < 10000; i++ { app := NewAppearance(int32(i), "Benchmark", 1000) apps.InsertAppearance(app) } b.ResetTimer() for i := 0; i < b.N; i++ { apps.FindAppearanceByID(int32(i % 10000)) } } func BenchmarkAppearanceFindByName(b *testing.B) { apps := NewAppearances() // Pre-populate with varied names names := []string{"Human Male", "Human Female", "Elf Male", "Elf Female", "Dwarf Male"} for i := 0; i < 1000; i++ { app := NewAppearance(int32(i), names[i%len(names)], 1000) apps.InsertAppearance(app) } b.ResetTimer() for i := 0; i < b.N; i++ { apps.FindAppearancesByName("Male") } } // Mock implementations type MockDatabase struct { appearances []*Appearance saveError error loadError error deleteError error } func (m *MockDatabase) LoadAllAppearances() ([]*Appearance, error) { if m.loadError != nil { return nil, m.loadError } return m.appearances, nil } func (m *MockDatabase) SaveAppearance(appearance *Appearance) error { return m.saveError } func (m *MockDatabase) DeleteAppearance(id int32) error { return m.deleteError } func (m *MockDatabase) LoadAppearancesByClientVersion(minClientVersion int16) ([]*Appearance, error) { var results []*Appearance for _, app := range m.appearances { if app.GetMinClientVersion() == minClientVersion { results = append(results, app) } } return results, nil } type MockLogger struct { logs []string } func (m *MockLogger) LogInfo(message string, args ...interface{}) { m.logs = append(m.logs, fmt.Sprintf("INFO: "+message, args...)) } func (m *MockLogger) LogError(message string, args ...interface{}) { m.logs = append(m.logs, fmt.Sprintf("ERROR: "+message, args...)) } func (m *MockLogger) LogDebug(message string, args ...interface{}) { m.logs = append(m.logs, fmt.Sprintf("DEBUG: "+message, args...)) } func (m *MockLogger) LogWarning(message string, args ...interface{}) { m.logs = append(m.logs, fmt.Sprintf("WARNING: "+message, args...)) } type MockEntity struct { id int32 name string databaseID int32 } func (m *MockEntity) GetID() int32 { return m.id } func (m *MockEntity) GetName() string { return m.name } func (m *MockEntity) GetDatabaseID() int32 { return m.databaseID } type MockClient struct { version int16 sendError error } func (m *MockClient) GetVersion() int16 { return m.version } func (m *MockClient) SendAppearanceUpdate(appearanceID int32) error { return m.sendError } // Manager tests func TestNewManager(t *testing.T) { db := &MockDatabase{} logger := &MockLogger{} manager := NewManager(db, logger) if manager == nil { t.Fatal("NewManager returned nil") } if manager.database != db { t.Error("Manager database not set correctly") } if manager.logger != logger { t.Error("Manager logger not set correctly") } if manager.appearances == nil { t.Error("Manager appearances not initialized") } } func TestManagerInitialize(t *testing.T) { tests := []struct { name string database *MockDatabase wantError bool wantCount int wantLogInfo bool }{ { name: "successful initialization", database: &MockDatabase{ appearances: []*Appearance{ NewAppearance(1, "Test1", 1000), NewAppearance(2, "Test2", 1096), }, }, wantError: false, wantCount: 2, wantLogInfo: true, }, { name: "nil database", database: nil, wantError: false, wantCount: 0, wantLogInfo: true, }, { name: "database load error", database: &MockDatabase{ loadError: fmt.Errorf("database error"), }, wantError: true, wantCount: 0, wantLogInfo: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { logger := &MockLogger{} var manager *Manager if tt.database != nil { manager = NewManager(tt.database, logger) } else { manager = &Manager{ appearances: NewAppearances(), database: nil, logger: logger, } } err := manager.Initialize() if (err != nil) != tt.wantError { t.Errorf("Initialize() error = %v, wantError %v", err, tt.wantError) } if count := manager.GetAppearanceCount(); count != tt.wantCount { t.Errorf("GetAppearanceCount() = %v, want %v", count, tt.wantCount) } if tt.wantLogInfo && len(logger.logs) == 0 { t.Error("Expected log messages, got none") } }) } } func TestManagerFindAppearanceByID(t *testing.T) { db := &MockDatabase{ appearances: []*Appearance{ NewAppearance(100, "Test", 1000), }, } logger := &MockLogger{} manager := NewManager(db, logger) manager.Initialize() // Test successful lookup appearance := manager.FindAppearanceByID(100) if appearance == nil { t.Error("FindAppearanceByID(100) returned nil") } // Test failed lookup appearance = manager.FindAppearanceByID(999) if appearance != nil { t.Error("FindAppearanceByID(999) should return nil") } // Check statistics stats := manager.GetStatistics() if totalLookups, ok := stats["total_lookups"].(int64); !ok || totalLookups != 2 { t.Errorf("total_lookups = %v, want 2", stats["total_lookups"]) } if successfulLookups, ok := stats["successful_lookups"].(int64); !ok || successfulLookups != 1 { t.Errorf("successful_lookups = %v, want 1", stats["successful_lookups"]) } if failedLookups, ok := stats["failed_lookups"].(int64); !ok || failedLookups != 1 { t.Errorf("failed_lookups = %v, want 1", stats["failed_lookups"]) } } func TestManagerAddAppearance(t *testing.T) { tests := []struct { name string appearance *Appearance saveError error existingID int32 wantError bool errorContains string }{ { name: "successful add", appearance: NewAppearance(100, "Test", 1000), wantError: false, }, { name: "nil appearance", appearance: nil, wantError: true, errorContains: "cannot be nil", }, { name: "empty name", appearance: &Appearance{id: 100, name: "", minClient: 1000}, wantError: true, errorContains: "name cannot be empty", }, { name: "invalid ID", appearance: NewAppearance(0, "Test", 1000), wantError: true, errorContains: "must be positive", }, { name: "duplicate ID", appearance: NewAppearance(100, "Test", 1000), existingID: 100, wantError: true, errorContains: "already exists", }, { name: "database save error", appearance: NewAppearance(200, "Test", 1000), saveError: fmt.Errorf("save failed"), wantError: true, errorContains: "database", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { db := &MockDatabase{saveError: tt.saveError} logger := &MockLogger{} manager := NewManager(db, logger) if tt.existingID > 0 { manager.appearances.InsertAppearance(NewAppearance(tt.existingID, "Existing", 1000)) } err := manager.AddAppearance(tt.appearance) if (err != nil) != tt.wantError { t.Errorf("AddAppearance() error = %v, wantError %v", err, tt.wantError) } if err != nil && tt.errorContains != "" && !contains(err.Error(), tt.errorContains) { t.Errorf("Error message %q doesn't contain %q", err.Error(), tt.errorContains) } // Verify appearance was added/not added if !tt.wantError && tt.appearance != nil { if !manager.appearances.HasAppearance(tt.appearance.GetID()) { t.Error("Appearance was not added to collection") } } }) } } func TestManagerUpdateAppearance(t *testing.T) { db := &MockDatabase{} logger := &MockLogger{} manager := NewManager(db, logger) // Add initial appearance original := NewAppearance(100, "Original", 1000) manager.AddAppearance(original) // Test successful update updated := NewAppearance(100, "Updated", 1100) err := manager.UpdateAppearance(updated) if err != nil { t.Errorf("UpdateAppearance failed: %v", err) } found := manager.FindAppearanceByID(100) if found.GetName() != "Updated" { t.Error("Appearance was not updated") } // Test update non-existent notExist := NewAppearance(999, "NotExist", 1000) err = manager.UpdateAppearance(notExist) if err == nil { t.Error("UpdateAppearance should fail for non-existent appearance") } // Test nil appearance err = manager.UpdateAppearance(nil) if err == nil { t.Error("UpdateAppearance should fail for nil appearance") } } func TestManagerRemoveAppearance(t *testing.T) { db := &MockDatabase{} logger := &MockLogger{} manager := NewManager(db, logger) // Add appearance app := NewAppearance(100, "Test", 1000) manager.AddAppearance(app) // Test successful removal err := manager.RemoveAppearance(100) if err != nil { t.Errorf("RemoveAppearance failed: %v", err) } if manager.appearances.HasAppearance(100) { t.Error("Appearance was not removed") } // Test removing non-existent err = manager.RemoveAppearance(999) if err == nil { t.Error("RemoveAppearance should fail for non-existent appearance") } // Test database delete error db.deleteError = fmt.Errorf("delete failed") manager.AddAppearance(NewAppearance(200, "Test2", 1000)) err = manager.RemoveAppearance(200) if err == nil { t.Error("RemoveAppearance should fail when database delete fails") } } func TestManagerCommands(t *testing.T) { db := &MockDatabase{ appearances: []*Appearance{ NewAppearance(100, "Human Male", 1000), NewAppearance(200, "Human Female", 1096), }, } logger := &MockLogger{} manager := NewManager(db, logger) manager.Initialize() // Test stats command result, err := manager.ProcessCommand("stats", nil) if err != nil { t.Errorf("ProcessCommand(stats) failed: %v", err) } if !contains(result, "Appearance System Statistics") { t.Error("Stats command output incorrect") } // Test validate command result, err = manager.ProcessCommand("validate", nil) if err != nil { t.Errorf("ProcessCommand(validate) failed: %v", err) } if !contains(result, "valid") { t.Error("Validate command output incorrect") } // Test search command result, err = manager.ProcessCommand("search", []string{"Human"}) if err != nil { t.Errorf("ProcessCommand(search) failed: %v", err) } if !contains(result, "Found 2 appearances") { t.Error("Search command output incorrect") } // Test search without args _, err = manager.ProcessCommand("search", nil) if err == nil { t.Error("Search command should fail without arguments") } // Test info command result, err = manager.ProcessCommand("info", []string{"100"}) if err != nil { t.Errorf("ProcessCommand(info) failed: %v", err) } if !contains(result, "Human Male") { t.Error("Info command output incorrect") } // Test info without args _, err = manager.ProcessCommand("info", nil) if err == nil { t.Error("Info command should fail without arguments") } // Test unknown command _, err = manager.ProcessCommand("unknown", nil) if err == nil { t.Error("Unknown command should return error") } } func TestManagerResetStatistics(t *testing.T) { manager := NewManager(nil, nil) // Perform some lookups manager.FindAppearanceByID(100) manager.FindAppearanceByID(200) stats := manager.GetStatistics() if totalLookups, ok := stats["total_lookups"].(int64); !ok || totalLookups != 2 { t.Error("Statistics not tracked correctly") } // Reset statistics manager.ResetStatistics() stats = manager.GetStatistics() if totalLookups, ok := stats["total_lookups"].(int64); !ok || totalLookups != 0 { t.Error("Statistics not reset correctly") } } func TestManagerShutdown(t *testing.T) { logger := &MockLogger{} manager := NewManager(nil, logger) manager.appearances.InsertAppearance(NewAppearance(100, "Test", 1000)) manager.Shutdown() if manager.GetAppearanceCount() != 0 { t.Error("Shutdown did not clear appearances") } // Check for shutdown log found := false for _, log := range logger.logs { if contains(log, "Shutting down") { found = true break } } if !found { t.Error("Shutdown did not log message") } } // EntityAppearanceAdapter tests func TestEntityAppearanceAdapter(t *testing.T) { entity := &MockEntity{id: 1, name: "TestEntity", databaseID: 100} logger := &MockLogger{} manager := NewManager(nil, logger) // Add test appearance app := NewAppearance(500, "TestAppearance", 1000) manager.AddAppearance(app) adapter := NewEntityAppearanceAdapter(entity, manager, logger) // Test initial state if adapter.GetAppearanceID() != 0 { t.Error("Initial appearance ID should be 0") } if adapter.GetAppearance() != nil { t.Error("Initial appearance should be nil") } // Test setting appearance ID adapter.SetAppearanceID(500) if adapter.GetAppearanceID() != 500 { t.Error("SetAppearanceID failed") } // Test getting appearance appearance := adapter.GetAppearance() if appearance == nil || appearance.GetID() != 500 { t.Error("GetAppearance failed") } // Test appearance name if name := adapter.GetAppearanceName(); name != "TestAppearance" { t.Errorf("GetAppearanceName() = %v, want TestAppearance", name) } // Test validation if err := adapter.ValidateAppearance(); err != nil { t.Errorf("ValidateAppearance() failed: %v", err) } // Test invalid appearance adapter.SetAppearanceID(999) if err := adapter.ValidateAppearance(); err == nil { t.Error("ValidateAppearance() should fail for invalid ID") } } func TestEntityAppearanceAdapterClientCompatibility(t *testing.T) { entity := &MockEntity{id: 1, name: "TestEntity"} manager := NewManager(nil, nil) // Add appearance with version requirement app := NewAppearance(100, "Test", 1096) manager.AddAppearance(app) adapter := NewEntityAppearanceAdapter(entity, manager, nil) adapter.SetAppearanceID(100) // Test compatible client if !adapter.IsCompatibleWithClient(1100) { t.Error("Should be compatible with client version 1100") } // Test incompatible client if adapter.IsCompatibleWithClient(1000) { t.Error("Should not be compatible with client version 1000") } // Test no appearance (always compatible) adapter.SetAppearanceID(0) if !adapter.IsCompatibleWithClient(500) { t.Error("No appearance should be compatible with all clients") } } func TestEntityAppearanceAdapterSendToClient(t *testing.T) { entity := &MockEntity{id: 1, name: "TestEntity"} logger := &MockLogger{} manager := NewManager(nil, logger) app := NewAppearance(100, "Test", 1096) manager.AddAppearance(app) adapter := NewEntityAppearanceAdapter(entity, manager, logger) adapter.SetAppearanceID(100) // Test successful send client := &MockClient{version: 1100} err := adapter.SendAppearanceToClient(client) if err != nil { t.Errorf("SendAppearanceToClient failed: %v", err) } // Test incompatible client lowClient := &MockClient{version: 1000} err = adapter.SendAppearanceToClient(lowClient) if err == nil { t.Error("SendAppearanceToClient should fail for incompatible client") } // Test client send error errorClient := &MockClient{version: 1100, sendError: fmt.Errorf("send failed")} err = adapter.SendAppearanceToClient(errorClient) if err == nil { t.Error("SendAppearanceToClient should propagate client error") } // Test nil client err = adapter.SendAppearanceToClient(nil) if err == nil { t.Error("SendAppearanceToClient should fail for nil client") } } // Cache tests func TestSimpleAppearanceCache(t *testing.T) { cache := NewSimpleAppearanceCache() app1 := NewAppearance(100, "Test1", 1000) app2 := NewAppearance(200, "Test2", 1096) // Test Set and Get cache.Set(100, app1) cache.Set(200, app2) if got := cache.Get(100); got != app1 { t.Error("Cache Get(100) failed") } if got := cache.Get(999); got != nil { t.Error("Cache Get(999) should return nil") } // Test GetSize if size := cache.GetSize(); size != 2 { t.Errorf("GetSize() = %v, want 2", size) } // Test Remove cache.Remove(100) if cache.Get(100) != nil { t.Error("Remove(100) failed") } if size := cache.GetSize(); size != 1 { t.Errorf("GetSize() after remove = %v, want 1", size) } // Test Clear cache.Clear() if size := cache.GetSize(); size != 0 { t.Errorf("GetSize() after clear = %v, want 0", size) } } func TestCachedAppearanceManager(t *testing.T) { db := &MockDatabase{} logger := &MockLogger{} baseManager := NewManager(db, logger) // Add test appearances app1 := NewAppearance(100, "Test1", 1000) app2 := NewAppearance(200, "Test2", 1096) baseManager.AddAppearance(app1) baseManager.AddAppearance(app2) cache := NewSimpleAppearanceCache() cachedManager := NewCachedAppearanceManager(baseManager, cache) // Test FindAppearanceByID with caching found := cachedManager.FindAppearanceByID(100) if found == nil || found.GetID() != 100 { t.Error("FindAppearanceByID(100) failed") } // Verify it was cached if cache.GetSize() != 1 { t.Error("Appearance was not cached") } // Test cache hit found2 := cachedManager.FindAppearanceByID(100) if found2 != found { t.Error("Should have returned cached appearance") } // Test AddAppearance updates cache app3 := NewAppearance(300, "Test3", 1200) err := cachedManager.AddAppearance(app3) if err != nil { t.Errorf("AddAppearance failed: %v", err) } if cache.Get(300) == nil { t.Error("Added appearance was not cached") } // Test UpdateAppearance updates cache updated := NewAppearance(100, "Updated", 1100) err = cachedManager.UpdateAppearance(updated) if err != nil { t.Errorf("UpdateAppearance failed: %v", err) } cached := cache.Get(100) if cached == nil || cached.GetName() != "Updated" { t.Error("Cache was not updated") } // Test RemoveAppearance updates cache err = cachedManager.RemoveAppearance(200) if err != nil { t.Errorf("RemoveAppearance failed: %v", err) } if cache.Get(200) != nil { t.Error("Removed appearance still in cache") } // Test ClearCache cachedManager.ClearCache() if cache.GetSize() != 0 { t.Error("ClearCache failed") } } func TestCacheConcurrency(t *testing.T) { cache := NewSimpleAppearanceCache() var wg sync.WaitGroup // Concurrent operations for i := 0; i < 100; i++ { wg.Add(3) // Writer go func(id int32) { defer wg.Done() app := NewAppearance(id, "Test", 1000) cache.Set(id, app) }(int32(i)) // Reader go func(id int32) { defer wg.Done() _ = cache.Get(id) }(int32(i)) // Size checker go func() { defer wg.Done() _ = cache.GetSize() }() } wg.Wait() // Final size should be predictable if size := cache.GetSize(); size > 100 { t.Errorf("Cache size %v is too large", size) } } // Additional tests for uncovered code paths func TestEntityAppearanceAdapterErrorCases(t *testing.T) { entity := &MockEntity{id: 1, name: "TestEntity"} logger := &MockLogger{} // Test with nil manager adapter := NewEntityAppearanceAdapter(entity, nil, logger) adapter.SetAppearanceID(100) // GetAppearance should return nil and log error appearance := adapter.GetAppearance() if appearance != nil { t.Error("GetAppearance should return nil with nil manager") } // Test UpdateAppearance with nil manager err := adapter.UpdateAppearance(100) if err == nil { t.Error("UpdateAppearance should fail with nil manager") } // Test UpdateAppearance with non-existent appearance manager := NewManager(nil, logger) adapter = NewEntityAppearanceAdapter(entity, manager, logger) err = adapter.UpdateAppearance(999) if err == nil { t.Error("UpdateAppearance should fail for non-existent appearance") } // Test SendAppearanceToClient with no appearance set client := &MockClient{version: 1100} adapter.SetAppearanceID(0) err = adapter.SendAppearanceToClient(client) if err != nil { t.Errorf("SendAppearanceToClient should succeed with no appearance: %v", err) } } func TestManagerGetAppearances(t *testing.T) { manager := NewManager(nil, nil) appearances := manager.GetAppearances() if appearances == nil { t.Error("GetAppearances should not return nil") } if appearances != manager.appearances { t.Error("GetAppearances should return internal appearances collection") } } func TestManagerCompatibleAndSearch(t *testing.T) { manager := NewManager(nil, nil) // Add test appearances app1 := NewAppearance(100, "Human Male", 1000) app2 := NewAppearance(200, "Human Female", 1096) manager.AddAppearance(app1) manager.AddAppearance(app2) // Test GetCompatibleAppearances compatible := manager.GetCompatibleAppearances(1050) if len(compatible) != 1 { t.Errorf("GetCompatibleAppearances(1050) returned %v results, want 1", len(compatible)) } // Test SearchAppearancesByName results := manager.SearchAppearancesByName("Human") if len(results) != 2 { t.Errorf("SearchAppearancesByName('Human') returned %v results, want 2", len(results)) } } func TestManagerReloadFromDatabase(t *testing.T) { // Test with nil database manager := NewManager(nil, nil) err := manager.ReloadFromDatabase() if err == nil { t.Error("ReloadFromDatabase should fail with nil database") } // Test successful reload db := &MockDatabase{ appearances: []*Appearance{ NewAppearance(100, "Test", 1000), }, } manager = NewManager(db, nil) // Add some appearances first manager.AddAppearance(NewAppearance(200, "Existing", 1000)) err = manager.ReloadFromDatabase() if err != nil { t.Errorf("ReloadFromDatabase failed: %v", err) } // Should only have the database appearance now if count := manager.GetAppearanceCount(); count != 1 { t.Errorf("After reload, count = %v, want 1", count) } if !manager.appearances.HasAppearance(100) { t.Error("Reloaded appearance not found") } if manager.appearances.HasAppearance(200) { t.Error("Previous appearance should be cleared") } } func TestManagerCommandEdgeCases(t *testing.T) { manager := NewManager(nil, nil) // Test reload command without database result, err := manager.ProcessCommand("reload", nil) if err == nil { t.Error("Reload command should fail without database") } // Test info command with invalid ID _, err = manager.ProcessCommand("info", []string{"invalid"}) if err == nil { t.Error("Info command should fail with invalid ID") } // Test info command with non-existent ID result, err = manager.ProcessCommand("info", []string{"999"}) if err != nil { t.Errorf("Info command failed: %v", err) } if !contains(result, "not found") { t.Error("Info command should indicate appearance not found") } // Test search with no results result, err = manager.ProcessCommand("search", []string{"nonexistent"}) if err != nil { t.Errorf("Search command failed: %v", err) } if !contains(result, "No appearances found") { t.Error("Search command should indicate no results") } } func TestManagerValidateAllAppearances(t *testing.T) { manager := NewManager(nil, nil) // Add valid appearance manager.AddAppearance(NewAppearance(100, "Valid", 1000)) issues := manager.ValidateAllAppearances() if len(issues) != 0 { t.Errorf("ValidateAllAppearances returned issues for valid data: %v", issues) } } func TestEntityAppearanceAdapterGetAppearanceName(t *testing.T) { entity := &MockEntity{id: 1, name: "TestEntity"} manager := NewManager(nil, nil) adapter := NewEntityAppearanceAdapter(entity, manager, nil) // Test with no appearance if name := adapter.GetAppearanceName(); name != "" { t.Errorf("GetAppearanceName() with no appearance = %v, want empty string", name) } // Test with appearance app := NewAppearance(100, "TestName", 1000) manager.AddAppearance(app) adapter.SetAppearanceID(100) if name := adapter.GetAppearanceName(); name != "TestName" { t.Errorf("GetAppearanceName() = %v, want TestName", name) } }