eq2go/internal/collections/collections_test.go

1242 lines
31 KiB
Go

package collections
import (
"context"
"fmt"
"sync"
"testing"
"eq2emu/internal/database"
)
// Mock implementations for testing
// MockCollectionDatabase implements CollectionDatabase interface for testing
type MockCollectionDatabase struct {
mu sync.RWMutex
collections []CollectionData
collectionItems map[int32][]CollectionItem
collectionRewards map[int32][]CollectionRewardData
playerCollections map[int32][]PlayerCollectionData
playerCollectionItems map[string][]int32 // key: "charID-collectionID"
failLoad bool
failSave bool
}
func NewMockCollectionDatabase() *MockCollectionDatabase {
return &MockCollectionDatabase{
collections: make([]CollectionData, 0),
collectionItems: make(map[int32][]CollectionItem),
collectionRewards: make(map[int32][]CollectionRewardData),
playerCollections: make(map[int32][]PlayerCollectionData),
playerCollectionItems: make(map[string][]int32),
}
}
func (m *MockCollectionDatabase) LoadCollections(ctx context.Context) ([]CollectionData, error) {
m.mu.RLock()
defer m.mu.RUnlock()
if m.failLoad {
return nil, fmt.Errorf("mock load error")
}
return m.collections, nil
}
func (m *MockCollectionDatabase) LoadCollectionItems(ctx context.Context, collectionID int32) ([]CollectionItem, error) {
m.mu.RLock()
defer m.mu.RUnlock()
if m.failLoad {
return nil, fmt.Errorf("mock load error")
}
items, exists := m.collectionItems[collectionID]
if !exists {
return []CollectionItem{}, nil
}
return items, nil
}
func (m *MockCollectionDatabase) LoadCollectionRewards(ctx context.Context, collectionID int32) ([]CollectionRewardData, error) {
m.mu.RLock()
defer m.mu.RUnlock()
if m.failLoad {
return nil, fmt.Errorf("mock load error")
}
rewards, exists := m.collectionRewards[collectionID]
if !exists {
return []CollectionRewardData{}, nil
}
return rewards, nil
}
func (m *MockCollectionDatabase) LoadPlayerCollections(ctx context.Context, characterID int32) ([]PlayerCollectionData, error) {
m.mu.RLock()
defer m.mu.RUnlock()
if m.failLoad {
return nil, fmt.Errorf("mock load error")
}
collections, exists := m.playerCollections[characterID]
if !exists {
return []PlayerCollectionData{}, nil
}
return collections, nil
}
func (m *MockCollectionDatabase) LoadPlayerCollectionItems(ctx context.Context, characterID, collectionID int32) ([]int32, error) {
m.mu.RLock()
defer m.mu.RUnlock()
if m.failLoad {
return nil, fmt.Errorf("mock load error")
}
key := fmt.Sprintf("%d-%d", characterID, collectionID)
items, exists := m.playerCollectionItems[key]
if !exists {
return []int32{}, nil
}
return items, nil
}
func (m *MockCollectionDatabase) SavePlayerCollection(ctx context.Context, characterID, collectionID int32, completed bool) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.failSave {
return fmt.Errorf("mock save error")
}
// Update or add player collection
found := false
collections := m.playerCollections[characterID]
for i := range collections {
if collections[i].CollectionID == collectionID {
collections[i].Completed = completed
found = true
break
}
}
if !found {
m.playerCollections[characterID] = append(collections, PlayerCollectionData{
CharacterID: characterID,
CollectionID: collectionID,
Completed: completed,
})
}
return nil
}
func (m *MockCollectionDatabase) SavePlayerCollectionItem(ctx context.Context, characterID, collectionID, itemID int32) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.failSave {
return fmt.Errorf("mock save error")
}
key := fmt.Sprintf("%d-%d", characterID, collectionID)
items := m.playerCollectionItems[key]
// Check if already exists
for _, existingItem := range items {
if existingItem == itemID {
return nil
}
}
m.playerCollectionItems[key] = append(items, itemID)
return nil
}
func (m *MockCollectionDatabase) SavePlayerCollections(ctx context.Context, characterID int32, collections []*Collection) error {
if m.failSave {
return fmt.Errorf("mock save error")
}
for _, collection := range collections {
if collection.GetSaveNeeded() {
err := m.SavePlayerCollection(ctx, characterID, collection.GetID(), collection.GetCompleted())
if err != nil {
return err
}
// Save items
items := collection.GetCollectionItems()
for _, item := range items {
if item.Found == ItemFound {
err := m.SavePlayerCollectionItem(ctx, characterID, collection.GetID(), item.ItemID)
if err != nil {
return err
}
}
}
}
}
return nil
}
// MockItemLookup implements ItemLookup interface for testing
type MockItemLookup struct {
items map[int32]ItemInfo
}
func NewMockItemLookup() *MockItemLookup {
return &MockItemLookup{
items: make(map[int32]ItemInfo),
}
}
func (m *MockItemLookup) GetItem(itemID int32) (ItemInfo, error) {
if item, exists := m.items[itemID]; exists {
return item, nil
}
return ItemInfo{}, fmt.Errorf("item not found")
}
func (m *MockItemLookup) ItemExists(itemID int32) bool {
_, exists := m.items[itemID]
return exists
}
func (m *MockItemLookup) GetItemName(itemID int32) string {
if item, exists := m.items[itemID]; exists {
return item.Name
}
return "Unknown Item"
}
// MockPlayerManager implements PlayerManager interface for testing
type MockPlayerManager struct {
players map[int32]PlayerInfo
}
func NewMockPlayerManager() *MockPlayerManager {
return &MockPlayerManager{
players: make(map[int32]PlayerInfo),
}
}
func (m *MockPlayerManager) GetPlayerInfo(characterID int32) (PlayerInfo, error) {
if player, exists := m.players[characterID]; exists {
return player, nil
}
return PlayerInfo{}, fmt.Errorf("player not found")
}
func (m *MockPlayerManager) IsPlayerOnline(characterID int32) bool {
if player, exists := m.players[characterID]; exists {
return player.IsOnline
}
return false
}
func (m *MockPlayerManager) GetPlayerLevel(characterID int32) int8 {
if player, exists := m.players[characterID]; exists {
return player.Level
}
return 0
}
// MockClientManager implements ClientManager interface for testing
type MockClientManager struct {
mu sync.Mutex
sentUpdates []int32
sentCompletions []int32
sentLists []int32
sentProgress []int32
failSend bool
}
func NewMockClientManager() *MockClientManager {
return &MockClientManager{
sentUpdates: make([]int32, 0),
sentCompletions: make([]int32, 0),
sentLists: make([]int32, 0),
sentProgress: make([]int32, 0),
}
}
func (m *MockClientManager) SendCollectionUpdate(characterID int32, collection *Collection) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.failSend {
return fmt.Errorf("mock send error")
}
m.sentUpdates = append(m.sentUpdates, characterID)
return nil
}
func (m *MockClientManager) SendCollectionComplete(characterID int32, collection *Collection) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.failSend {
return fmt.Errorf("mock send error")
}
m.sentCompletions = append(m.sentCompletions, characterID)
return nil
}
func (m *MockClientManager) SendCollectionList(characterID int32, collections []CollectionInfo) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.failSend {
return fmt.Errorf("mock send error")
}
m.sentLists = append(m.sentLists, characterID)
return nil
}
func (m *MockClientManager) SendCollectionProgress(characterID int32, progress []CollectionProgress) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.failSend {
return fmt.Errorf("mock send error")
}
m.sentProgress = append(m.sentProgress, characterID)
return nil
}
// MockRewardProvider implements RewardProvider interface for testing
type MockRewardProvider struct {
mu sync.Mutex
givenItems []struct {
CharacterID int32
ItemID int32
Quantity int8
}
givenCoin []struct {
CharacterID int32
Amount int64
}
givenXP []struct {
CharacterID int32
Amount int64
}
failGive bool
failValidate bool
}
func NewMockRewardProvider() *MockRewardProvider {
return &MockRewardProvider{}
}
func (m *MockRewardProvider) GiveItem(characterID int32, itemID int32, quantity int8) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.failGive {
return fmt.Errorf("mock give error")
}
m.givenItems = append(m.givenItems, struct {
CharacterID int32
ItemID int32
Quantity int8
}{characterID, itemID, quantity})
return nil
}
func (m *MockRewardProvider) GiveCoin(characterID int32, amount int64) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.failGive {
return fmt.Errorf("mock give error")
}
m.givenCoin = append(m.givenCoin, struct {
CharacterID int32
Amount int64
}{characterID, amount})
return nil
}
func (m *MockRewardProvider) GiveXP(characterID int32, amount int64) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.failGive {
return fmt.Errorf("mock give error")
}
m.givenXP = append(m.givenXP, struct {
CharacterID int32
Amount int64
}{characterID, amount})
return nil
}
func (m *MockRewardProvider) ValidateRewards(characterID int32, rewards []CollectionRewardItem, coin, xp int64) error {
if m.failValidate {
return fmt.Errorf("mock validate error")
}
return nil
}
// Tests for Collection
func TestNewCollection(t *testing.T) {
c := NewCollection()
c.SetID(1)
c.SetName("Test Collection")
c.SetCategory("Test Category")
c.SetLevel(10)
if c.GetID() != 1 {
t.Errorf("Expected ID 1, got %d", c.GetID())
}
if c.GetName() != "Test Collection" {
t.Errorf("Expected name 'Test Collection', got %s", c.GetName())
}
if c.GetCategory() != "Test Category" {
t.Errorf("Expected category 'Test Category', got %s", c.GetCategory())
}
if c.GetLevel() != 10 {
t.Errorf("Expected level 10, got %d", c.GetLevel())
}
if c.GetCompleted() {
t.Error("Expected collection to be incomplete")
}
if c.GetSaveNeeded() {
t.Error("Expected saveNeeded to be false")
}
}
func TestCollectionCopy(t *testing.T) {
c1 := NewCollection()
c1.SetID(1)
c1.SetName("Original")
c1.SetCategory("Category")
c1.SetLevel(5)
c1.SetRewardCoin(1000)
c1.SetRewardXP(5000)
c1.AddCollectionItem(CollectionItem{ItemID: 100, Index: 0, Found: ItemNotFound})
c2 := NewCollectionFromData(c1)
// Verify copy has same values
if c2.GetID() != c1.GetID() {
t.Error("Copy has different ID")
}
if c2.GetName() != c1.GetName() {
t.Error("Copy has different name")
}
if c2.GetRewardCoin() != c1.GetRewardCoin() {
t.Error("Copy has different reward coin")
}
// Verify deep copy (modifying copy doesn't affect original)
c2.SetRewardCoin(2000)
if c1.GetRewardCoin() == c2.GetRewardCoin() {
t.Error("Modifying copy affected original")
}
}
func TestCollectionItems(t *testing.T) {
c := NewCollection()
c.SetID(1)
c.SetName("Test")
c.SetCategory("Category")
c.SetLevel(1)
// Add items
c.AddCollectionItem(CollectionItem{ItemID: 100, Index: 0, Found: ItemNotFound})
c.AddCollectionItem(CollectionItem{ItemID: 101, Index: 1, Found: ItemNotFound})
c.AddCollectionItem(CollectionItem{ItemID: 102, Index: 2, Found: ItemNotFound})
items := c.GetCollectionItems()
if len(items) != 3 {
t.Errorf("Expected 3 items, got %d", len(items))
}
// Test finding item
found := c.MarkItemFound(101)
if !found {
t.Error("Expected to find item 101")
}
if !c.GetSaveNeeded() {
t.Error("Expected saveNeeded to be true after finding item")
}
// Test finding non-existent item
found = c.MarkItemFound(999)
if found {
t.Error("Should not find non-existent item")
}
// Check if item is needed
if !c.NeedsItem(100) {
t.Error("Expected NeedsItem to return true for unfound item")
}
if c.NeedsItem(101) {
t.Error("Expected NeedsItem to return false for found item")
}
// Check ready status
if c.GetIsReadyToTurnIn() {
t.Error("Collection should not be ready with unfound items")
}
// Find remaining items
c.MarkItemFound(100)
c.MarkItemFound(102)
if !c.GetIsReadyToTurnIn() {
t.Error("Collection should be ready when all items found")
}
}
func TestCollectionRewards(t *testing.T) {
c := NewCollection()
c.SetID(1)
c.SetName("Test")
c.SetCategory("Category")
c.SetLevel(1)
// Set rewards
c.SetRewardCoin(1000)
c.SetRewardXP(5000)
// Add reward items
c.AddRewardItem(CollectionRewardItem{ItemID: 200, Quantity: 1})
c.AddRewardItem(CollectionRewardItem{ItemID: 201, Quantity: 5})
// Add selectable rewards
c.AddSelectableRewardItem(CollectionRewardItem{ItemID: 300, Quantity: 1})
c.AddSelectableRewardItem(CollectionRewardItem{ItemID: 301, Quantity: 1})
if c.GetRewardCoin() != 1000 {
t.Errorf("Expected reward coin 1000, got %d", c.GetRewardCoin())
}
if c.GetRewardXP() != 5000 {
t.Errorf("Expected reward XP 5000, got %d", c.GetRewardXP())
}
rewards := c.GetRewardItems()
if len(rewards) != 2 {
t.Errorf("Expected 2 reward items, got %d", len(rewards))
}
selectable := c.GetSelectableRewardItems()
if len(selectable) != 2 {
t.Errorf("Expected 2 selectable rewards, got %d", len(selectable))
}
}
func TestCollectionCompletion(t *testing.T) {
c := NewCollection()
c.SetID(1)
c.SetName("Test")
c.SetCategory("Category")
c.SetLevel(1)
c.AddCollectionItem(CollectionItem{ItemID: 100, Index: 0, Found: ItemNotFound})
// Check not ready to complete without all items
if c.GetIsReadyToTurnIn() {
t.Error("Should not be ready to complete without all items")
}
// Find item
c.MarkItemFound(100)
if !c.GetIsReadyToTurnIn() {
t.Error("Should be ready to complete when all items found")
}
// Mark as completed
c.SetCompleted(true)
if !c.GetCompleted() {
t.Error("Expected collection to be completed")
}
}
func TestCollectionProgress(t *testing.T) {
c := NewCollection()
c.SetID(1)
c.SetName("Test")
c.SetCategory("Category")
c.SetLevel(1)
c.AddCollectionItem(CollectionItem{ItemID: 100, Index: 0, Found: ItemNotFound})
c.AddCollectionItem(CollectionItem{ItemID: 101, Index: 1, Found: ItemNotFound})
c.AddCollectionItem(CollectionItem{ItemID: 102, Index: 2, Found: ItemNotFound})
c.AddCollectionItem(CollectionItem{ItemID: 103, Index: 3, Found: ItemNotFound})
// Initial progress
progress := c.GetProgress()
if progress != 0.0 {
t.Errorf("Expected 0%% progress, got %.2f%%", progress)
}
// Find one item
c.MarkItemFound(100)
progress = c.GetProgress()
if progress != 25.0 {
t.Errorf("Expected 25%% progress, got %.2f%%", progress)
}
// Find more items
c.MarkItemFound(101)
c.MarkItemFound(102)
progress = c.GetProgress()
if progress != 75.0 {
t.Errorf("Expected 75%% progress, got %.2f%%", progress)
}
// Complete collection
c.MarkItemFound(103)
c.SetCompleted(true)
progress = c.GetProgress()
if progress != 100.0 {
t.Errorf("Expected 100%% progress, got %.2f%%", progress)
}
}
// Tests for MasterCollectionList
func TestNewMasterCollectionList(t *testing.T) {
db := NewMockCollectionDatabase()
ml := NewMasterCollectionList(db)
if ml == nil {
t.Fatal("Failed to create MasterCollectionList")
}
}
func TestMasterCollectionListLoad(t *testing.T) {
db := NewMockCollectionDatabase()
// Add test data
db.collections = []CollectionData{
{ID: 1, Name: "Collection 1", Category: "Category A", Level: 10},
{ID: 2, Name: "Collection 2", Category: "Category B", Level: 20},
}
db.collectionItems[1] = []CollectionItem{
{ItemID: 100, Index: 0, Found: ItemNotFound},
{ItemID: 101, Index: 1, Found: ItemNotFound},
}
db.collectionItems[2] = []CollectionItem{
{ItemID: 200, Index: 0, Found: ItemNotFound},
}
db.collectionRewards[1] = []CollectionRewardData{
{CollectionID: 1, RewardType: RewardTypeCoin, RewardValue: "1000", Quantity: 1},
{CollectionID: 1, RewardType: RewardTypeXP, RewardValue: "5000", Quantity: 1},
}
// Set up item lookup
itemLookup := NewMockItemLookup()
itemLookup.items[100] = ItemInfo{ID: 100, Name: "Test Item 1"}
itemLookup.items[101] = ItemInfo{ID: 101, Name: "Test Item 2"}
itemLookup.items[200] = ItemInfo{ID: 200, Name: "Test Item 3"}
ml := NewMasterCollectionList(db)
err := ml.Initialize(context.Background(), itemLookup)
if err != nil {
t.Fatalf("Failed to load collections: %v", err)
}
// Verify collections loaded
collection := ml.GetCollection(1)
if collection == nil {
t.Fatal("Collection 1 not found")
}
if collection.GetName() != "Collection 1" {
t.Errorf("Expected name 'Collection 1', got %s", collection.GetName())
}
// Verify items loaded
items := collection.GetCollectionItems()
if len(items) != 2 {
t.Errorf("Expected 2 items, got %d", len(items))
}
// Verify rewards loaded
if collection.GetRewardCoin() != 1000 {
t.Errorf("Expected reward coin 1000, got %d", collection.GetRewardCoin())
}
}
func TestMasterCollectionListSearch(t *testing.T) {
db := NewMockCollectionDatabase()
db.collections = []CollectionData{
{ID: 1, Name: "Ancient Artifacts", Category: "Archaeology", Level: 10},
{ID: 2, Name: "Ancient Weapons", Category: "Combat", Level: 20},
{ID: 3, Name: "Modern Art", Category: "Art", Level: 15},
}
// Add required items for each collection
db.collectionItems[1] = []CollectionItem{
{ItemID: 100, Index: 0, Found: ItemNotFound},
}
db.collectionItems[2] = []CollectionItem{
{ItemID: 200, Index: 0, Found: ItemNotFound},
}
db.collectionItems[3] = []CollectionItem{
{ItemID: 300, Index: 0, Found: ItemNotFound},
}
// Set up item lookup
itemLookup := NewMockItemLookup()
itemLookup.items[100] = ItemInfo{ID: 100, Name: "Test Item 1"}
itemLookup.items[200] = ItemInfo{ID: 200, Name: "Test Item 2"}
itemLookup.items[300] = ItemInfo{ID: 300, Name: "Test Item 3"}
ml := NewMasterCollectionList(db)
ml.Initialize(context.Background(), itemLookup)
// Search by name
results := ml.FindCollectionsByName("Ancient")
if len(results) != 2 {
t.Errorf("Expected 2 results for 'Ancient', got %d", len(results))
}
// Search by category
results = ml.GetCollectionsByCategory("Art")
if len(results) != 1 {
t.Errorf("Expected 1 result for category 'Art', got %d", len(results))
}
// Search by level range
results = ml.GetCollectionsByLevel(15, 25)
if len(results) != 2 {
t.Errorf("Expected 2 results for level range 15-25, got %d", len(results))
}
}
// Tests for PlayerCollectionList
func TestNewPlayerCollectionList(t *testing.T) {
db := NewMockCollectionDatabase()
pl := NewPlayerCollectionList(1001, db)
if pl == nil {
t.Fatal("Failed to create PlayerCollectionList")
}
if pl.GetCharacterID() != 1001 {
t.Errorf("Expected character ID 1001, got %d", pl.GetCharacterID())
}
}
func TestPlayerCollectionListLoad(t *testing.T) {
db := NewMockCollectionDatabase()
masterList := NewMasterCollectionList(db)
// Set up master collections
db.collections = []CollectionData{
{ID: 1, Name: "Collection 1", Category: "Category A", Level: 10},
{ID: 2, Name: "Collection 2", Category: "Category B", Level: 20},
}
db.collectionItems[1] = []CollectionItem{
{ItemID: 100, Index: 0, Found: ItemNotFound},
{ItemID: 101, Index: 1, Found: ItemNotFound},
}
db.collectionItems[2] = []CollectionItem{
{ItemID: 200, Index: 0, Found: ItemNotFound},
}
// Set up item lookup
itemLookup := NewMockItemLookup()
itemLookup.items[100] = ItemInfo{ID: 100, Name: "Test Item 1"}
itemLookup.items[101] = ItemInfo{ID: 101, Name: "Test Item 2"}
itemLookup.items[200] = ItemInfo{ID: 200, Name: "Test Item 3"}
masterList.Initialize(context.Background(), itemLookup)
// Set up player data
db.playerCollections[1001] = []PlayerCollectionData{
{CharacterID: 1001, CollectionID: 1, Completed: false},
{CharacterID: 1001, CollectionID: 2, Completed: true},
}
db.playerCollectionItems["1001-1"] = []int32{100} // Found item 100 in collection 1
pl := NewPlayerCollectionList(1001, db)
err := pl.Initialize(context.Background(), masterList)
if err != nil {
t.Fatalf("Failed to load player collections: %v", err)
}
// Check loaded collections
collection := pl.GetCollection(1)
if collection == nil {
t.Fatal("Collection 1 not found")
}
if collection.GetCompleted() {
t.Error("Collection 1 should not be completed")
}
// Check found items
if collection.NeedsItem(100) {
t.Error("Item 100 should be found")
}
if !collection.NeedsItem(101) {
t.Error("Item 101 should not be found")
}
// Check completed collection
collection2 := pl.GetCollection(2)
if collection2 == nil {
t.Fatal("Collection 2 not found")
}
if !collection2.GetCompleted() {
t.Error("Collection 2 should be completed")
}
}
func TestPlayerCollectionListAddItem(t *testing.T) {
db := NewMockCollectionDatabase()
masterList := NewMasterCollectionList(db)
// Set up collections
db.collections = []CollectionData{
{ID: 1, Name: "Collection 1", Category: "Category A", Level: 10},
}
db.collectionItems[1] = []CollectionItem{
{ItemID: 100, Index: 0, Found: ItemNotFound},
{ItemID: 101, Index: 1, Found: ItemNotFound},
}
// Set up item lookup
itemLookup := NewMockItemLookup()
itemLookup.items[100] = ItemInfo{ID: 100, Name: "Test Item 1"}
itemLookup.items[101] = ItemInfo{ID: 101, Name: "Test Item 2"}
masterList.Initialize(context.Background(), itemLookup)
pl := NewPlayerCollectionList(1001, db)
// Try to add collection item directly - this will depend on the actual API
// For now, just test basic functionality
if pl.GetCharacterID() != 1001 {
t.Errorf("Expected character ID 1001, got %d", pl.GetCharacterID())
}
// Load collections first
err := pl.Initialize(context.Background(), masterList)
if err != nil {
t.Fatalf("Failed to load player collections: %v", err)
}
}
// Tests for CollectionManager
func TestNewCollectionManager(t *testing.T) {
db := NewMockCollectionDatabase()
itemLookup := NewMockItemLookup()
cm := NewCollectionManager(db, itemLookup)
if cm == nil {
t.Fatal("Failed to create CollectionManager")
}
}
func TestCollectionManagerInitialize(t *testing.T) {
db := NewMockCollectionDatabase()
itemLookup := NewMockItemLookup()
// Add test data
db.collections = []CollectionData{
{ID: 1, Name: "Collection 1", Category: "Category A", Level: 10},
}
db.collectionItems[1] = []CollectionItem{
{ItemID: 100, Index: 0, Found: ItemNotFound},
}
// Set up item lookup
itemLookup.items[100] = ItemInfo{ID: 100, Name: "Test Item 1"}
cm := NewCollectionManager(db, itemLookup)
err := cm.Initialize(context.Background())
if err != nil {
t.Fatalf("Failed to initialize: %v", err)
}
// Verify master list loaded
stats := cm.GetSystemStatistics()
if stats.TotalCollections != 1 {
t.Errorf("Expected 1 collection, got %d", stats.TotalCollections)
}
}
func TestCollectionManagerPlayerOperations(t *testing.T) {
db := NewMockCollectionDatabase()
itemLookup := NewMockItemLookup()
// Add test data
db.collections = []CollectionData{
{ID: 1, Name: "Collection 1", Category: "Category A", Level: 10},
}
db.collectionItems[1] = []CollectionItem{
{ItemID: 100, Index: 0, Found: ItemNotFound},
{ItemID: 101, Index: 1, Found: ItemNotFound},
}
// Set up item lookup
itemLookup.items[100] = ItemInfo{ID: 100, Name: "Test Item 1"}
itemLookup.items[101] = ItemInfo{ID: 101, Name: "Test Item 2"}
cm := NewCollectionManager(db, itemLookup)
err := cm.Initialize(context.Background())
if err != nil {
t.Fatalf("Failed to initialize: %v", err)
}
// Verify statistics
stats := cm.GetSystemStatistics()
if stats.TotalCollections != 1 {
t.Errorf("Expected 1 collection, got %d", stats.TotalCollections)
}
// Get collection copy
collection := cm.GetCollectionCopy(1)
if collection == nil {
t.Fatal("Failed to get collection copy")
}
if collection.GetName() != "Collection 1" {
t.Errorf("Expected name 'Collection 1', got %s", collection.GetName())
}
}
// Tests for DatabaseCollectionManager
func TestDatabaseCollectionManager(t *testing.T) {
// Create in-memory database
db, err := database.Open(":memory:")
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer db.Close()
dcm := NewDatabaseCollectionManager(db)
// Ensure tables exist
err = dcm.EnsureCollectionTables(context.Background())
if err != nil {
t.Fatalf("Failed to create tables: %v", err)
}
// Test saving and loading collections
ctx := context.Background()
// Insert test collection
err = db.Exec(`INSERT INTO collections (id, collection_name, collection_category, level)
VALUES (?, ?, ?, ?)`, 1, "Test Collection", "Test Category", 10)
if err != nil {
t.Fatalf("Failed to insert collection: %v", err)
}
// Load collections
collections, err := dcm.LoadCollections(ctx)
if err != nil {
t.Fatalf("Failed to load collections: %v", err)
}
if len(collections) != 1 {
t.Errorf("Expected 1 collection, got %d", len(collections))
}
if collections[0].Name != "Test Collection" {
t.Errorf("Expected name 'Test Collection', got %s", collections[0].Name)
}
// Test player collection operations
err = dcm.SavePlayerCollection(ctx, 1001, 1, false)
if err != nil {
t.Fatalf("Failed to save player collection: %v", err)
}
playerCollections, err := dcm.LoadPlayerCollections(ctx, 1001)
if err != nil {
t.Fatalf("Failed to load player collections: %v", err)
}
if len(playerCollections) != 1 {
t.Errorf("Expected 1 player collection, got %d", len(playerCollections))
}
// Test statistics
stats, err := dcm.GetCollectionStatistics(ctx)
if err != nil {
t.Fatalf("Failed to get statistics: %v", err)
}
if stats.TotalCollections != 1 {
t.Errorf("Expected 1 total collection, got %d", stats.TotalCollections)
}
}
// Tests for EntityCollectionAdapter
// Mock entity for testing
type mockEntity struct {
id int32
}
func (m *mockEntity) GetID() int32 {
return m.id
}
func TestEntityCollectionAdapter(t *testing.T) {
entity := &mockEntity{id: 1001}
// Mock player manager
pm := NewMockPlayerManager()
pm.players[1001] = PlayerInfo{
CharacterID: 1001,
Level: 50,
}
adapter := &EntityCollectionAdapter{
entity: entity,
playerManager: pm,
}
if adapter.GetCharacterID() != 1001 {
t.Errorf("Expected character ID 1001, got %d", adapter.GetCharacterID())
}
if adapter.GetLevel() != 50 {
t.Errorf("Expected level 50, got %d", adapter.GetLevel())
}
// Test HasItem (should return false as placeholder)
if adapter.HasItem(100) {
t.Error("HasItem should return false")
}
}
// Concurrency tests
func TestCollectionConcurrency(t *testing.T) {
c := NewCollection()
c.SetID(1)
c.SetName("Test")
c.SetCategory("Category")
c.SetLevel(1)
// Add items
for i := 0; i < 100; i++ {
c.AddCollectionItem(CollectionItem{ItemID: int32(i), Index: int8(i), Found: ItemNotFound})
}
// Concurrent reads and writes
var wg sync.WaitGroup
errors := make(chan error, 100)
// Writers
for i := 0; i < 50; i++ {
wg.Add(1)
go func(itemID int32) {
defer wg.Done()
c.MarkItemFound(itemID)
}(int32(i))
}
// Readers
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_ = c.GetProgress()
_ = c.GetCollectionItems()
_ = c.GetIsReadyToTurnIn()
}()
}
wg.Wait()
close(errors)
// Check for errors
for err := range errors {
if err != nil {
t.Errorf("Concurrent operation error: %v", err)
}
}
}
func TestMasterCollectionListConcurrency(t *testing.T) {
db := NewMockCollectionDatabase()
// Add test data
for i := 1; i <= 100; i++ {
db.collections = append(db.collections, CollectionData{
ID: int32(i),
Name: fmt.Sprintf("Collection %d", i),
Category: fmt.Sprintf("Category %d", i%10),
Level: int8(i % 50),
})
// Add at least one item per collection
db.collectionItems[int32(i)] = []CollectionItem{
{ItemID: int32(i * 100), Index: 0, Found: ItemNotFound},
}
}
// Set up item lookup for all items
itemLookup := NewMockItemLookup()
for i := 1; i <= 100; i++ {
itemLookup.items[int32(i*100)] = ItemInfo{ID: int32(i * 100), Name: fmt.Sprintf("Item %d", i)}
}
ml := NewMasterCollectionList(db)
ml.Initialize(context.Background(), itemLookup)
var wg sync.WaitGroup
// Concurrent reads
for i := 0; i < 100; i++ {
wg.Add(1)
go func(id int32) {
defer wg.Done()
_ = ml.GetCollection(id)
_ = ml.FindCollectionsByName("Collection")
_ = ml.GetCollectionsByCategory("Category 1")
_ = ml.GetAllCollections()
}(int32(i + 1))
}
wg.Wait()
}
// Benchmarks
func BenchmarkCollectionMarkItemFound(b *testing.B) {
c := NewCollection()
c.SetID(1)
c.SetName("Test")
c.SetCategory("Category")
c.SetLevel(1)
// Add items
for i := 0; i < 100; i++ {
c.AddCollectionItem(CollectionItem{ItemID: int32(i), Index: int8(i), Found: ItemNotFound})
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
c.MarkItemFound(int32(i % 100))
}
}
func BenchmarkMasterCollectionListSearch(b *testing.B) {
db := NewMockCollectionDatabase()
// Add test data
for i := 1; i <= 1000; i++ {
db.collections = append(db.collections, CollectionData{
ID: int32(i),
Name: fmt.Sprintf("Collection %d", i),
Category: fmt.Sprintf("Category %d", i%10),
Level: int8(i % 50),
})
// Add at least one item per collection
db.collectionItems[int32(i)] = []CollectionItem{
{ItemID: int32(i * 100), Index: 0, Found: ItemNotFound},
}
}
// Set up item lookup for all items
itemLookup := NewMockItemLookup()
for i := 1; i <= 1000; i++ {
itemLookup.items[int32(i*100)] = ItemInfo{ID: int32(i * 100), Name: fmt.Sprintf("Item %d", i)}
}
ml := NewMasterCollectionList(db)
ml.Initialize(context.Background(), itemLookup)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ml.FindCollectionsByName("Collection")
}
}
func BenchmarkPlayerCollectionListLoad(b *testing.B) {
db := NewMockCollectionDatabase()
masterList := NewMasterCollectionList(db)
// Set up collections
for i := 1; i <= 100; i++ {
db.collections = append(db.collections, CollectionData{
ID: int32(i),
Name: fmt.Sprintf("Collection %d", i),
Category: "Category",
Level: 10,
})
items := make([]CollectionItem, 10)
for j := 0; j < 10; j++ {
items[j] = CollectionItem{
ItemID: int32(i*100 + j),
Index: int8(j),
Found: ItemNotFound,
}
}
db.collectionItems[int32(i)] = items
}
// Set up item lookup for all items
itemLookup := NewMockItemLookup()
for i := 1; i <= 100; i++ {
for j := 0; j < 10; j++ {
itemID := int32(i*100 + j)
itemLookup.items[itemID] = ItemInfo{ID: itemID, Name: fmt.Sprintf("Item %d", itemID)}
}
}
masterList.Initialize(context.Background(), itemLookup)
b.ResetTimer()
for i := 0; i < b.N; i++ {
pl := NewPlayerCollectionList(1001, db)
pl.Initialize(context.Background(), masterList)
}
}
// Test constants
func TestConstants(t *testing.T) {
// Test reward types
if RewardTypeItem != "Item" {
t.Errorf("Expected RewardTypeItem to be 'Item', got %s", RewardTypeItem)
}
if RewardTypeSelectable != "Selectable" {
t.Errorf("Expected RewardTypeSelectable to be 'Selectable', got %s", RewardTypeSelectable)
}
if RewardTypeCoin != "Coin" {
t.Errorf("Expected RewardTypeCoin to be 'Coin', got %s", RewardTypeCoin)
}
if RewardTypeXP != "XP" {
t.Errorf("Expected RewardTypeXP to be 'XP', got %s", RewardTypeXP)
}
// Test item states
if ItemNotFound != 0 {
t.Errorf("Expected ItemNotFound to be 0, got %d", ItemNotFound)
}
if ItemFound != 1 {
t.Errorf("Expected ItemFound to be 1, got %d", ItemFound)
}
// Test collection states
if CollectionIncomplete != false {
t.Error("Expected CollectionIncomplete to be false")
}
if CollectionCompleted != true {
t.Error("Expected CollectionCompleted to be true")
}
}