eq2go/internal/collections/master_test.go
2025-08-08 12:13:38 -05:00

573 lines
18 KiB
Go

package collections
import (
"fmt"
"testing"
"eq2emu/internal/database"
)
func TestNewMasterList(t *testing.T) {
masterList := NewMasterList()
if masterList == nil {
t.Fatal("NewMasterList returned nil")
}
if masterList.GetCollectionCount() != 0 {
t.Errorf("Expected count 0, got %d", masterList.GetCollectionCount())
}
}
func TestMasterListBasicOperations(t *testing.T) {
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
defer db.Close()
masterList := NewMasterList()
// Create test collections
collection1 := NewWithData(1001, "Heritage Collection", "Heritage", 20, db)
collection2 := NewWithData(1002, "Treasured Collection", "Treasured", 30, db)
// Test adding
if !masterList.AddCollection(collection1) {
t.Error("Should successfully add collection1")
}
if !masterList.AddCollection(collection2) {
t.Error("Should successfully add collection2")
}
// Test duplicate add (should fail)
if masterList.AddCollection(collection1) {
t.Error("Should not add duplicate collection")
}
if masterList.GetCollectionCount() != 2 {
t.Errorf("Expected count 2, got %d", masterList.GetCollectionCount())
}
// Test retrieving
retrieved := masterList.GetCollection(1001)
if retrieved == nil {
t.Error("Should retrieve added collection")
}
if retrieved.GetName() != "Heritage Collection" {
t.Errorf("Expected name 'Heritage Collection', got '%s'", retrieved.GetName())
}
// Test safe retrieval
retrieved, exists := masterList.GetCollectionSafe(1001)
if !exists || retrieved == nil {
t.Error("GetCollectionSafe should return collection and true")
}
_, exists = masterList.GetCollectionSafe(9999)
if exists {
t.Error("GetCollectionSafe should return false for non-existent ID")
}
// Test HasCollection
if !masterList.HasCollection(1001) {
t.Error("HasCollection should return true for existing ID")
}
if masterList.HasCollection(9999) {
t.Error("HasCollection should return false for non-existent ID")
}
// Test removing
if !masterList.RemoveCollection(1001) {
t.Error("Should successfully remove collection")
}
if masterList.GetCollectionCount() != 1 {
t.Errorf("Expected count 1, got %d", masterList.GetCollectionCount())
}
if masterList.HasCollection(1001) {
t.Error("Collection should be removed")
}
// Test clear
masterList.ClearCollections()
if masterList.GetCollectionCount() != 0 {
t.Errorf("Expected count 0 after clear, got %d", masterList.GetCollectionCount())
}
}
func TestMasterListItemNeeds(t *testing.T) {
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
defer db.Close()
masterList := NewMasterList()
// Create collections with items
collection1 := NewWithData(1001, "Heritage Collection", "Heritage", 20, db)
collection1.CollectionItems = append(collection1.CollectionItems, CollectionItem{
ItemID: 12345,
Index: 0,
Found: ItemNotFound,
})
collection1.CollectionItems = append(collection1.CollectionItems, CollectionItem{
ItemID: 12346,
Index: 1,
Found: ItemFound, // Already found
})
collection2 := NewWithData(1002, "Treasured Collection", "Treasured", 30, db)
collection2.CollectionItems = append(collection2.CollectionItems, CollectionItem{
ItemID: 12347,
Index: 0,
Found: ItemNotFound,
})
masterList.AddCollection(collection1)
masterList.AddCollection(collection2)
// Test NeedsItem
if !masterList.NeedsItem(12345) {
t.Error("MasterList should need item 12345")
}
if masterList.NeedsItem(12346) {
t.Error("MasterList should not need item 12346 (already found)")
}
if !masterList.NeedsItem(12347) {
t.Error("MasterList should need item 12347")
}
if masterList.NeedsItem(99999) {
t.Error("MasterList should not need item 99999")
}
// Test GetCollectionsNeedingItem
needingItem := masterList.GetCollectionsNeedingItem(12345)
if len(needingItem) != 1 {
t.Errorf("Expected 1 collection needing item 12345, got %d", len(needingItem))
}
needingNone := masterList.GetCollectionsNeedingItem(99999)
if len(needingNone) != 0 {
t.Errorf("Expected 0 collections needing item 99999, got %d", len(needingNone))
}
}
func TestMasterListFiltering(t *testing.T) {
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
defer db.Close()
masterList := NewMasterList()
// Add test collections
collections := []*Collection{
NewWithData(1, "Heritage 1", "Heritage", 10, db),
NewWithData(2, "Heritage 2", "Heritage", 20, db),
NewWithData(3, "Treasured 1", "Treasured", 15, db),
NewWithData(4, "Treasured 2", "Treasured", 25, db),
NewWithData(5, "Legendary 1", "Legendary", 30, db),
}
for _, collection := range collections {
masterList.AddCollection(collection)
}
// Test FindCollectionsByCategory
heritageCollections := masterList.FindCollectionsByCategory("Heritage")
if len(heritageCollections) != 2 {
t.Errorf("FindCollectionsByCategory('Heritage') returned %v results, want 2", len(heritageCollections))
}
treasuredCollections := masterList.FindCollectionsByCategory("Treasured")
if len(treasuredCollections) != 2 {
t.Errorf("FindCollectionsByCategory('Treasured') returned %v results, want 2", len(treasuredCollections))
}
// Test FindCollectionsByLevel
lowLevel := masterList.FindCollectionsByLevel(10, 15)
if len(lowLevel) != 2 {
t.Errorf("FindCollectionsByLevel(10, 15) returned %v results, want 2", len(lowLevel))
}
midLevel := masterList.FindCollectionsByLevel(20, 25)
if len(midLevel) != 2 {
t.Errorf("FindCollectionsByLevel(20, 25) returned %v results, want 2", len(midLevel))
}
highLevel := masterList.FindCollectionsByLevel(30, 40)
if len(highLevel) != 1 {
t.Errorf("FindCollectionsByLevel(30, 40) returned %v results, want 1", len(highLevel))
}
}
func TestMasterListCategories(t *testing.T) {
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
defer db.Close()
masterList := NewMasterList()
// Add collections with different categories
masterList.AddCollection(NewWithData(1, "Test1", "Heritage", 10, db))
masterList.AddCollection(NewWithData(2, "Test2", "Heritage", 20, db))
masterList.AddCollection(NewWithData(3, "Test3", "Treasured", 15, db))
masterList.AddCollection(NewWithData(4, "Test4", "Legendary", 30, db))
categories := masterList.GetCategories()
expectedCategories := []string{"Heritage", "Treasured", "Legendary"}
if len(categories) != len(expectedCategories) {
t.Errorf("Expected %d categories, got %d", len(expectedCategories), len(categories))
}
// Check that all expected categories are present
categoryMap := make(map[string]bool)
for _, category := range categories {
categoryMap[category] = true
}
for _, expected := range expectedCategories {
if !categoryMap[expected] {
t.Errorf("Expected category '%s' not found", expected)
}
}
}
func TestMasterListGetAll(t *testing.T) {
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
defer db.Close()
masterList := NewMasterList()
// Add test collections
for i := int32(1); i <= 3; i++ {
collection := NewWithData(i*100, "Test", "Heritage", 20, db)
masterList.AddCollection(collection)
}
// Test GetAllCollections (map)
allMap := masterList.GetAllCollections()
if len(allMap) != 3 {
t.Errorf("GetAllCollections() returned %v items, want 3", len(allMap))
}
// Verify it's a copy by modifying returned map
delete(allMap, 100)
if masterList.GetCollectionCount() != 3 {
t.Error("Modifying returned map affected internal state")
}
// Test GetAllCollectionsList (slice)
allList := masterList.GetAllCollectionsList()
if len(allList) != 3 {
t.Errorf("GetAllCollectionsList() returned %v items, want 3", len(allList))
}
}
func TestMasterListValidation(t *testing.T) {
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
defer db.Close()
masterList := NewMasterList()
// Add valid collection
collection1 := NewWithData(100, "Valid Collection", "Heritage", 20, db)
collection1.CollectionItems = append(collection1.CollectionItems, CollectionItem{
ItemID: 12345,
Index: 0,
Found: ItemNotFound,
})
masterList.AddCollection(collection1)
issues := masterList.ValidateCollections()
if len(issues) != 0 {
t.Errorf("ValidateCollections() returned issues for valid data: %v", issues)
}
if !masterList.IsValid() {
t.Error("IsValid() should return true for valid data")
}
// Add invalid collection (empty name)
collection2 := NewWithData(200, "", "Heritage", 20, db)
masterList.AddCollection(collection2)
issues = masterList.ValidateCollections()
if len(issues) == 0 {
t.Error("ValidateCollections() should return issues for invalid data")
}
if masterList.IsValid() {
t.Error("IsValid() should return false for invalid data")
}
}
func TestMasterListStatistics(t *testing.T) {
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
defer db.Close()
masterList := NewMasterList()
// Add collections with different categories and levels
collection1 := NewWithData(10, "Heritage1", "Heritage", 10, db)
collection1.CollectionItems = append(collection1.CollectionItems, CollectionItem{ItemID: 1, Index: 0, Found: 0})
collection1.CollectionItems = append(collection1.CollectionItems, CollectionItem{ItemID: 2, Index: 1, Found: 0})
collection1.RewardItems = append(collection1.RewardItems, CollectionRewardItem{ItemID: 1001, Quantity: 1})
collection2 := NewWithData(20, "Heritage2", "Heritage", 20, db)
collection2.CollectionItems = append(collection2.CollectionItems, CollectionItem{ItemID: 3, Index: 0, Found: 0})
collection2.RewardItems = append(collection2.RewardItems, CollectionRewardItem{ItemID: 1002, Quantity: 1})
collection2.SelectableRewardItems = append(collection2.SelectableRewardItems, CollectionRewardItem{ItemID: 1003, Quantity: 1})
collection3 := NewWithData(30, "Treasured1", "Treasured", 30, db)
collection3.CollectionItems = append(collection3.CollectionItems, CollectionItem{ItemID: 4, Index: 0, Found: 0})
masterList.AddCollection(collection1)
masterList.AddCollection(collection2)
masterList.AddCollection(collection3)
stats := masterList.GetStatistics()
if total, ok := stats["total_collections"].(int); !ok || total != 3 {
t.Errorf("total_collections = %v, want 3", stats["total_collections"])
}
if totalItems, ok := stats["total_collection_items"].(int); !ok || totalItems != 4 {
t.Errorf("total_collection_items = %v, want 4", stats["total_collection_items"])
}
if totalRewards, ok := stats["total_rewards"].(int); !ok || totalRewards != 3 {
t.Errorf("total_rewards = %v, want 3", stats["total_rewards"])
}
if minLevel, ok := stats["min_level"].(int8); !ok || minLevel != 10 {
t.Errorf("min_level = %v, want 10", stats["min_level"])
}
if maxLevel, ok := stats["max_level"].(int8); !ok || maxLevel != 30 {
t.Errorf("max_level = %v, want 30", stats["max_level"])
}
if categoryCounts, ok := stats["collections_by_category"].(map[string]int); ok {
if categoryCounts["Heritage"] != 2 {
t.Errorf("Heritage collections = %v, want 2", categoryCounts["Heritage"])
}
if categoryCounts["Treasured"] != 1 {
t.Errorf("Treasured collections = %v, want 1", categoryCounts["Treasured"])
}
} else {
t.Error("collections_by_category not found in statistics")
}
if avgItems, ok := stats["average_items_per_collection"].(float64); !ok || avgItems != float64(4)/3 {
t.Errorf("average_items_per_collection = %v, want %v", avgItems, float64(4)/3)
}
}
func TestMasterListBespokeFeatures(t *testing.T) {
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
defer db.Close()
masterList := NewMasterList()
// Create collections with different properties
col1 := NewWithData(101, "Heritage Quest", "Heritage", 10, db)
col1.CollectionItems = []CollectionItem{
{ItemID: 1001, Index: 0, Found: ItemNotFound},
{ItemID: 1002, Index: 1, Found: ItemFound},
}
col1.Completed = false
col2 := NewWithData(102, "Treasured Quest", "Treasured", 20, db)
col2.CollectionItems = []CollectionItem{
{ItemID: 1003, Index: 0, Found: ItemFound},
{ItemID: 1004, Index: 1, Found: ItemFound},
}
col2.Completed = true
col3 := NewWithData(103, "Legendary Quest", "Legendary", 10, db)
col3.CollectionItems = []CollectionItem{
{ItemID: 1001, Index: 0, Found: ItemNotFound}, // Same item as col1
}
col3.Completed = false
masterList.AddCollection(col1)
masterList.AddCollection(col2)
masterList.AddCollection(col3)
// Test GetCollectionsByExactLevel
level10Collections := masterList.GetCollectionsByExactLevel(10)
if len(level10Collections) != 2 {
t.Errorf("GetCollectionsByExactLevel(10) returned %v results, want 2", len(level10Collections))
}
level20Collections := masterList.GetCollectionsByExactLevel(20)
if len(level20Collections) != 1 {
t.Errorf("GetCollectionsByExactLevel(20) returned %v results, want 1", len(level20Collections))
}
// Test GetCollectionByName
found := masterList.GetCollectionByName("heritage quest")
if found == nil || found.ID != 101 {
t.Error("GetCollectionByName should find 'Heritage Quest' (case insensitive)")
}
found = masterList.GetCollectionByName("TREASURED QUEST")
if found == nil || found.ID != 102 {
t.Error("GetCollectionByName should find 'Treasured Quest' (uppercase)")
}
found = masterList.GetCollectionByName("NonExistent")
if found != nil {
t.Error("GetCollectionByName should return nil for non-existent collection")
}
// Test completion status filtering
completedCollections := masterList.GetCompletedCollections()
if len(completedCollections) != 1 {
t.Errorf("GetCompletedCollections() returned %v results, want 1", len(completedCollections))
}
incompleteCollections := masterList.GetIncompleteCollections()
if len(incompleteCollections) != 2 {
t.Errorf("GetIncompleteCollections() returned %v results, want 2", len(incompleteCollections))
}
// Test GetCollectionsNeedingItem (multiple collections need same item)
collectionsNeedingItem := masterList.GetCollectionsNeedingItem(1001)
if len(collectionsNeedingItem) != 2 {
t.Errorf("GetCollectionsNeedingItem(1001) returned %v results, want 2", len(collectionsNeedingItem))
}
// Test GetReadyToTurnInCollections
readyCollections := masterList.GetReadyToTurnInCollections()
if len(readyCollections) != 0 { // col1 has one item not found, col3 has one item not found
t.Errorf("GetReadyToTurnInCollections() returned %v results, want 0", len(readyCollections))
}
// Mark col1 as ready to turn in
col1.CollectionItems[0].Found = ItemFound
masterList.RefreshCollectionIndices(col1)
readyCollections = masterList.GetReadyToTurnInCollections()
if len(readyCollections) != 1 {
t.Errorf("GetReadyToTurnInCollections() returned %v results, want 1 after marking items found", len(readyCollections))
}
// Test UpdateCollection
updatedCol := &Collection{
ID: 101,
Name: "Updated Heritage Quest",
Category: "Updated",
Level: 25,
db: db,
isNew: false,
CollectionItems: []CollectionItem{
{ItemID: 2001, Index: 0, Found: ItemNotFound},
},
}
err := masterList.UpdateCollection(updatedCol)
if err != nil {
t.Errorf("UpdateCollection failed: %v", err)
}
// Verify the update worked
retrieved := masterList.GetCollection(101)
if retrieved.Name != "Updated Heritage Quest" {
t.Errorf("Expected updated name 'Updated Heritage Quest', got '%s'", retrieved.Name)
}
if retrieved.Category != "Updated" {
t.Errorf("Expected updated category 'Updated', got '%s'", retrieved.Category)
}
// Test updating non-existent collection
nonExistentCol := &Collection{ID: 9999, Name: "Non-existent", db: db}
err = masterList.UpdateCollection(nonExistentCol)
if err == nil {
t.Error("UpdateCollection should fail for non-existent collection")
}
// Test GetLevels and GetItemsNeeded
levels := masterList.GetLevels()
if len(levels) == 0 {
t.Error("GetLevels() should return levels")
}
itemsNeeded := masterList.GetItemsNeeded()
if len(itemsNeeded) == 0 {
t.Error("GetItemsNeeded() should return items needed")
}
// Test GetCollectionClone
cloned := masterList.GetCollectionClone(101)
if cloned == nil {
t.Error("GetCollectionClone should return a clone")
}
if cloned == retrieved {
t.Error("GetCollectionClone should return a different object")
}
}
func TestMasterListConcurrency(t *testing.T) {
db, _ := database.NewSQLite("file::memory:?mode=memory&cache=shared")
defer db.Close()
masterList := NewMasterList()
// Add initial collections
for i := 1; i <= 100; i++ {
col := NewWithData(int32(i), fmt.Sprintf("Collection%d", i), "Heritage", 10, db)
col.CollectionItems = []CollectionItem{
{ItemID: int32(i + 1000), Index: 0, Found: ItemNotFound},
}
masterList.AddCollection(col)
}
// Test concurrent access
done := make(chan bool, 10)
// Concurrent readers
for i := 0; i < 5; i++ {
go func() {
defer func() { done <- true }()
for j := 0; j < 100; j++ {
masterList.GetCollection(int32(j%100 + 1))
masterList.FindCollectionsByCategory("Heritage")
masterList.GetCollectionByName(fmt.Sprintf("collection%d", j%100+1))
masterList.NeedsItem(int32(j + 1000))
}
}()
}
// Concurrent writers
for i := 0; i < 5; i++ {
go func(workerID int) {
defer func() { done <- true }()
for j := 0; j < 10; j++ {
colID := int32(workerID*1000 + j + 1)
col := NewWithData(colID, fmt.Sprintf("Worker%d-Collection%d", workerID, j), "Treasured", 20, db)
col.CollectionItems = []CollectionItem{
{ItemID: colID + 10000, Index: 0, Found: ItemNotFound},
}
masterList.AddCollection(col) // Some may fail due to concurrent additions
}
}(i)
}
// Wait for all goroutines
for i := 0; i < 10; i++ {
<-done
}
// Verify final state - should have at least 100 initial collections
finalCount := masterList.GetCollectionCount()
if finalCount < 100 {
t.Errorf("Expected at least 100 collections after concurrent operations, got %d", finalCount)
}
if finalCount > 150 {
t.Errorf("Expected at most 150 collections after concurrent operations, got %d", finalCount)
}
}