480 lines
13 KiB
Go
480 lines
13 KiB
Go
package appearances
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
|
|
"eq2emu/internal/database"
|
|
)
|
|
|
|
func TestNew(t *testing.T) {
|
|
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Test creating a new appearance
|
|
appearance := New(db)
|
|
if appearance == nil {
|
|
t.Fatal("New returned nil")
|
|
}
|
|
|
|
if !appearance.IsNew() {
|
|
t.Error("New appearance should be marked as new")
|
|
}
|
|
|
|
// Test setting values
|
|
appearance.ID = 1001
|
|
appearance.Name = "Test Appearance"
|
|
appearance.MinClient = 1096
|
|
|
|
if appearance.GetID() != 1001 {
|
|
t.Errorf("Expected GetID() to return 1001, got %d", appearance.GetID())
|
|
}
|
|
|
|
if appearance.GetName() != "Test Appearance" {
|
|
t.Errorf("Expected GetName() to return 'Test Appearance', got %s", appearance.GetName())
|
|
}
|
|
|
|
if appearance.GetMinClientVersion() != 1096 {
|
|
t.Errorf("Expected GetMinClientVersion() to return 1096, got %d", appearance.GetMinClientVersion())
|
|
}
|
|
}
|
|
|
|
func TestNewWithData(t *testing.T) {
|
|
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
appearance := NewWithData(100, "Human Male", 1096, db)
|
|
if appearance == nil {
|
|
t.Fatal("NewWithData returned nil")
|
|
}
|
|
|
|
if appearance.GetID() != 100 {
|
|
t.Errorf("Expected ID 100, got %d", appearance.GetID())
|
|
}
|
|
|
|
if appearance.GetName() != "Human Male" {
|
|
t.Errorf("Expected name 'Human Male', got '%s'", appearance.GetName())
|
|
}
|
|
|
|
if appearance.GetMinClientVersion() != 1096 {
|
|
t.Errorf("Expected min client 1096, got %d", appearance.GetMinClientVersion())
|
|
}
|
|
|
|
if !appearance.IsNew() {
|
|
t.Error("NewWithData should create new appearance")
|
|
}
|
|
}
|
|
|
|
func TestAppearanceGetters(t *testing.T) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
app := NewWithData(123, "Test Appearance", 1096, db)
|
|
|
|
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) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
app := NewWithData(100, "Original", 1000, db)
|
|
|
|
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) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
app := NewWithData(100, "Test", 1096, db)
|
|
|
|
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) {
|
|
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
defer db.Close()
|
|
|
|
original := NewWithData(500, "Original Appearance", 1200, db)
|
|
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())
|
|
}
|
|
|
|
if !clone.IsNew() {
|
|
t.Error("Clone should always be marked as new")
|
|
}
|
|
|
|
// Verify modification independence
|
|
clone.SetName("Modified Clone")
|
|
if original.GetName() == "Modified Clone" {
|
|
t.Error("Modifying clone affected original")
|
|
}
|
|
}
|
|
|
|
// Test appearance type functions
|
|
func TestGetAppearanceType(t *testing.T) {
|
|
tests := []struct {
|
|
typeName string
|
|
expected int8
|
|
}{
|
|
{"hair_color1", AppearanceHairColor1},
|
|
{"soga_hair_color1", AppearanceSOGAHairColor1},
|
|
{"skin_color", AppearanceSkinColor},
|
|
{"eye_color", AppearanceEyeColor},
|
|
{"unknown_type", -1},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.typeName, func(t *testing.T) {
|
|
result := GetAppearanceType(tt.typeName)
|
|
if result != tt.expected {
|
|
t.Errorf("GetAppearanceType(%q) = %d, want %d", tt.typeName, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetAppearanceTypeName(t *testing.T) {
|
|
tests := []struct {
|
|
typeConst int8
|
|
expected string
|
|
}{
|
|
{AppearanceHairColor1, "hair_color1"},
|
|
{AppearanceSOGAHairColor1, "soga_hair_color1"},
|
|
{AppearanceSkinColor, "skin_color"},
|
|
{AppearanceEyeColor, "eye_color"},
|
|
{-1, "unknown"},
|
|
{100, "unknown"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run("", func(t *testing.T) {
|
|
result := GetAppearanceTypeName(tt.typeConst)
|
|
if result != tt.expected {
|
|
t.Errorf("GetAppearanceTypeName(%d) = %q, want %q", tt.typeConst, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestMasterList tests the bespoke master list implementation
|
|
func TestMasterList(t *testing.T) {
|
|
masterList := NewMasterList()
|
|
|
|
if masterList == nil {
|
|
t.Fatal("NewMasterList returned nil")
|
|
}
|
|
|
|
if masterList.Size() != 0 {
|
|
t.Errorf("Expected size 0, got %d", masterList.Size())
|
|
}
|
|
|
|
// Create test database
|
|
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Create appearances for testing
|
|
app1 := NewWithData(1001, "Human Male", 1096, db)
|
|
app2 := NewWithData(1002, "Elf Female", 1200, db)
|
|
app3 := NewWithData(1003, "Dwarf Warrior", 1096, db)
|
|
|
|
// Test adding
|
|
if !masterList.AddAppearance(app1) {
|
|
t.Error("Should successfully add app1")
|
|
}
|
|
|
|
if !masterList.AddAppearance(app2) {
|
|
t.Error("Should successfully add app2")
|
|
}
|
|
|
|
if !masterList.AddAppearance(app3) {
|
|
t.Error("Should successfully add app3")
|
|
}
|
|
|
|
if masterList.Size() != 3 {
|
|
t.Errorf("Expected size 3, got %d", masterList.Size())
|
|
}
|
|
|
|
// Test duplicate add (should fail)
|
|
if masterList.AddAppearance(app1) {
|
|
t.Error("Should not add duplicate appearance")
|
|
}
|
|
|
|
// Test retrieving
|
|
retrieved := masterList.GetAppearance(1001)
|
|
if retrieved == nil {
|
|
t.Error("Should retrieve added appearance")
|
|
}
|
|
|
|
if retrieved.Name != "Human Male" {
|
|
t.Errorf("Expected name 'Human Male', got '%s'", retrieved.Name)
|
|
}
|
|
|
|
// Test safe retrieval
|
|
retrievedSafe, exists := masterList.GetAppearanceSafe(1001)
|
|
if !exists {
|
|
t.Error("Should find existing appearance")
|
|
}
|
|
if retrievedSafe.Name != "Human Male" {
|
|
t.Errorf("Expected safe name 'Human Male', got '%s'", retrievedSafe.Name)
|
|
}
|
|
|
|
_, notExists := masterList.GetAppearanceSafe(9999)
|
|
if notExists {
|
|
t.Error("Should not find non-existent appearance")
|
|
}
|
|
|
|
// Test client version filtering
|
|
version1096 := masterList.FindAppearancesByMinClient(1096)
|
|
if len(version1096) != 2 {
|
|
t.Errorf("Expected 2 appearances with min client 1096, got %d", len(version1096))
|
|
}
|
|
|
|
version1200 := masterList.FindAppearancesByMinClient(1200)
|
|
if len(version1200) != 1 {
|
|
t.Errorf("Expected 1 appearance with min client 1200, got %d", len(version1200))
|
|
}
|
|
|
|
// Test compatible appearances
|
|
compatible1200 := masterList.GetCompatibleAppearances(1200)
|
|
if len(compatible1200) != 3 {
|
|
t.Errorf("Expected 3 appearances compatible with client 1200, got %d", len(compatible1200))
|
|
}
|
|
|
|
compatible1100 := masterList.GetCompatibleAppearances(1100)
|
|
if len(compatible1100) != 2 {
|
|
t.Errorf("Expected 2 appearances compatible with client 1100, got %d", len(compatible1100))
|
|
}
|
|
|
|
// Test name searching (case insensitive)
|
|
// Names: "Human Male", "Elf Female", "Dwarf Warrior"
|
|
humanApps := masterList.FindAppearancesByName("human")
|
|
if len(humanApps) != 1 {
|
|
t.Errorf("Expected 1 appearance with 'human' in name, got %d", len(humanApps))
|
|
}
|
|
|
|
maleApps := masterList.FindAppearancesByName("male")
|
|
if len(maleApps) != 1 { // Only "Human Male" contains "male"
|
|
t.Errorf("Expected 1 appearance with 'male' in name, got %d", len(maleApps))
|
|
}
|
|
|
|
// Test exact name match (indexed lookup)
|
|
humanMaleApps := masterList.FindAppearancesByName("human male")
|
|
if len(humanMaleApps) != 1 {
|
|
t.Errorf("Expected 1 appearance with exact name 'human male', got %d", len(humanMaleApps))
|
|
}
|
|
|
|
// Test ID range filtering
|
|
rangeApps := masterList.GetAppearancesByIDRange(1001, 1002)
|
|
if len(rangeApps) != 2 {
|
|
t.Errorf("Expected 2 appearances in range 1001-1002, got %d", len(rangeApps))
|
|
}
|
|
|
|
// Test client version range filtering
|
|
clientRangeApps := masterList.GetAppearancesByClientRange(1096, 1096)
|
|
if len(clientRangeApps) != 2 {
|
|
t.Errorf("Expected 2 appearances in client range 1096-1096, got %d", len(clientRangeApps))
|
|
}
|
|
|
|
// Test metadata caching
|
|
clientVersions := masterList.GetClientVersions()
|
|
if len(clientVersions) != 2 {
|
|
t.Errorf("Expected 2 unique client versions, got %d", len(clientVersions))
|
|
}
|
|
|
|
// Test clone
|
|
clone := masterList.GetAppearanceClone(1001)
|
|
if clone == nil {
|
|
t.Error("Should return cloned appearance")
|
|
}
|
|
|
|
if clone.Name != "Human Male" {
|
|
t.Errorf("Expected cloned name 'Human Male', got '%s'", clone.Name)
|
|
}
|
|
|
|
// Test GetAllAppearances
|
|
allApps := masterList.GetAllAppearances()
|
|
if len(allApps) != 3 {
|
|
t.Errorf("Expected 3 appearances in GetAll, got %d", len(allApps))
|
|
}
|
|
|
|
// Test GetAllAppearancesList
|
|
allAppsList := masterList.GetAllAppearancesList()
|
|
if len(allAppsList) != 3 {
|
|
t.Errorf("Expected 3 appearances in GetAllList, got %d", len(allAppsList))
|
|
}
|
|
|
|
// Test update
|
|
updatedApp := NewWithData(1001, "Updated Human", 1500, db)
|
|
if err := masterList.UpdateAppearance(updatedApp); err != nil {
|
|
t.Errorf("Update should succeed: %v", err)
|
|
}
|
|
|
|
// Verify update worked
|
|
retrievedUpdated := masterList.GetAppearance(1001)
|
|
if retrievedUpdated.Name != "Updated Human" {
|
|
t.Errorf("Expected updated name 'Updated Human', got '%s'", retrievedUpdated.Name)
|
|
}
|
|
|
|
// Verify client version index updated
|
|
version1500 := masterList.FindAppearancesByMinClient(1500)
|
|
if len(version1500) != 1 {
|
|
t.Errorf("Expected 1 appearance with min client 1500, got %d", len(version1500))
|
|
}
|
|
|
|
// Test removal
|
|
if !masterList.RemoveAppearance(1001) {
|
|
t.Error("Should successfully remove appearance")
|
|
}
|
|
|
|
if masterList.Size() != 2 {
|
|
t.Errorf("Expected size 2 after removal, got %d", masterList.Size())
|
|
}
|
|
|
|
// Test validation
|
|
issues := masterList.ValidateAppearances()
|
|
if len(issues) != 0 {
|
|
t.Errorf("Expected no validation issues, got %d", len(issues))
|
|
}
|
|
|
|
// Test statistics
|
|
stats := masterList.GetStatistics()
|
|
if stats["total_appearances"] != 2 {
|
|
t.Errorf("Expected statistics total 2, got %v", stats["total_appearances"])
|
|
}
|
|
|
|
// Test clear
|
|
masterList.Clear()
|
|
if masterList.Size() != 0 {
|
|
t.Errorf("Expected size 0 after clear, got %d", masterList.Size())
|
|
}
|
|
}
|
|
|
|
// TestMasterListConcurrency tests thread safety of the master list
|
|
func TestMasterListConcurrency(t *testing.T) {
|
|
masterList := NewMasterList()
|
|
|
|
// Create test database
|
|
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
const numWorkers = 10
|
|
const appsPerWorker = 100
|
|
var wg sync.WaitGroup
|
|
|
|
// Concurrently add appearances
|
|
wg.Add(numWorkers)
|
|
for i := 0; i < numWorkers; i++ {
|
|
go func(workerID int) {
|
|
defer wg.Done()
|
|
for j := 0; j < appsPerWorker; j++ {
|
|
app := NewWithData(
|
|
int32(workerID*appsPerWorker+j+1),
|
|
"Concurrent Test",
|
|
int16(1096+(workerID%3)*100),
|
|
db,
|
|
)
|
|
masterList.AddAppearance(app)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
// Concurrently read appearances
|
|
wg.Add(numWorkers)
|
|
for i := 0; i < numWorkers; i++ {
|
|
go func() {
|
|
defer wg.Done()
|
|
for j := 0; j < appsPerWorker; j++ {
|
|
// Random reads
|
|
_ = masterList.GetAppearance(int32(j + 1))
|
|
_ = masterList.FindAppearancesByMinClient(1096)
|
|
_ = masterList.GetCompatibleAppearances(1200)
|
|
_ = masterList.Size()
|
|
}
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Verify final state
|
|
expectedSize := numWorkers * appsPerWorker
|
|
if masterList.Size() != expectedSize {
|
|
t.Errorf("Expected size %d, got %d", expectedSize, masterList.Size())
|
|
}
|
|
|
|
clientVersions := masterList.GetClientVersions()
|
|
if len(clientVersions) != 3 {
|
|
t.Errorf("Expected 3 client versions, got %d", len(clientVersions))
|
|
}
|
|
}
|