eq2go/internal/collections/collections_test.go

653 lines
18 KiB
Go

package collections
import (
"testing"
)
// MockDatabase implements a simple in-memory database for testing
type MockDatabase struct {
collections map[int32]map[string]interface{}
collectionItems map[int32][]map[string]interface{}
collectionRewards map[int32][]map[string]interface{}
characterCollections map[int32][]map[string]interface{}
characterCollectionItems map[string][]int32
}
func NewMockDatabase() *MockDatabase {
return &MockDatabase{
collections: make(map[int32]map[string]interface{}),
collectionItems: make(map[int32][]map[string]interface{}),
collectionRewards: make(map[int32][]map[string]interface{}),
characterCollections: make(map[int32][]map[string]interface{}),
characterCollectionItems: make(map[string][]int32),
}
}
// Mock database methods (simplified implementations for testing)
func (db *MockDatabase) Query(query string, args ...interface{}) (interface{}, error) {
// This is a simplified mock - in real tests you'd want more sophisticated query parsing
return nil, nil
}
func (db *MockDatabase) QueryRow(query string, args ...interface{}) interface{} {
return nil
}
func (db *MockDatabase) Exec(query string, args ...interface{}) (interface{}, error) {
return nil, nil
}
func setupTestData(db *MockDatabase) {
// Collection 1: Shiny Objects
db.collections[1] = map[string]interface{}{
"id": 1,
"collection_name": "Shiny Objects",
"collection_category": "Common Collections",
"level": 10,
}
db.collectionItems[1] = []map[string]interface{}{
{"item_id": 1001, "item_index": 0, "found": 0},
{"item_id": 1002, "item_index": 1, "found": 0},
{"item_id": 1003, "item_index": 2, "found": 0},
}
db.collectionRewards[1] = []map[string]interface{}{
{"reward_type": "coin", "reward_value": "1000", "reward_quantity": 1},
{"reward_type": "xp", "reward_value": "500", "reward_quantity": 1},
}
// Collection 2: Rare Gems
db.collections[2] = map[string]interface{}{
"id": 2,
"collection_name": "Rare Gems",
"collection_category": "Valuable Collections",
"level": 25,
}
db.collectionItems[2] = []map[string]interface{}{
{"item_id": 2001, "item_index": 0, "found": 0},
{"item_id": 2002, "item_index": 1, "found": 0},
}
db.collectionRewards[2] = []map[string]interface{}{
{"reward_type": "item", "reward_value": "5001", "reward_quantity": 1},
}
}
func createTestManager() *Manager {
db := NewMockDatabase()
setupTestData(db)
// For now, we'll create collections manually since we can't easily mock the database queries
manager := NewManager(nil) // Pass nil for now since we're testing without real DB
// Manually create test collections
collection1 := NewWithData(1, "Shiny Objects", "Common Collections", 10, nil)
collection1.CollectionItems = []CollectionItem{
{ItemID: 1001, Index: 0, Found: 0},
{ItemID: 1002, Index: 1, Found: 0},
{ItemID: 1003, Index: 2, Found: 0},
}
collection1.RewardCoin = 1000
collection1.RewardXP = 500
collection2 := NewWithData(2, "Rare Gems", "Valuable Collections", 25, nil)
collection2.CollectionItems = []CollectionItem{
{ItemID: 2001, Index: 0, Found: 0},
{ItemID: 2002, Index: 1, Found: 0},
}
collection2.RewardItems = []CollectionRewardItem{
{ItemID: 5001, Quantity: 1},
}
manager.AddCollection(collection1)
manager.AddCollection(collection2)
return manager
}
// Test Manager Creation and Initialization
func TestManagerCreation(t *testing.T) {
manager := NewManager(nil)
if manager == nil {
t.Fatal("NewManager returned nil")
}
if manager.masterList == nil {
t.Error("Manager master list is nil")
}
if manager.Size() != 0 {
t.Errorf("Expected empty manager, got size %d", manager.Size())
}
// Check default configuration
config := manager.GetConfiguration()
if config["enable_caching"].(bool) != true {
t.Error("Expected caching to be enabled by default")
}
if config["enable_statistics"].(bool) != true {
t.Error("Expected statistics to be enabled by default")
}
if config["enable_validation"].(bool) != true {
t.Error("Expected validation to be enabled by default")
}
}
// Test Collection Operations
func TestCollectionOperations(t *testing.T) {
manager := createTestManager()
// Test Size
if manager.Size() != 2 {
t.Errorf("Expected 2 collections, got %d", manager.Size())
}
// Test GetCollection
collection := manager.GetCollection(1)
if collection == nil {
t.Fatal("Failed to retrieve collection 1")
}
if collection.GetName() != "Shiny Objects" {
t.Errorf("Expected 'Shiny Objects', got '%s'", collection.GetName())
}
if collection.GetLevel() != 10 {
t.Errorf("Expected level 10, got %d", collection.GetLevel())
}
// Test non-existent collection
nonExistent := manager.GetCollection(999)
if nonExistent != nil {
t.Error("Expected nil for non-existent collection")
}
// Test NeedsItem
if !manager.NeedsItem(1001) {
t.Error("Expected collection to need item 1001")
}
if manager.NeedsItem(9999) {
t.Error("Expected no collection to need item 9999")
}
}
// Test Collection Management
func TestCollectionManagement(t *testing.T) {
manager := createTestManager()
// Test adding new collection
newCollection := NewWithData(3, "Test Collection", "Test Category", 1, nil)
newCollection.CollectionItems = []CollectionItem{
{ItemID: 3001, Index: 0, Found: 0},
}
if !manager.AddCollection(newCollection) {
t.Error("Failed to add new collection")
}
if manager.Size() != 3 {
t.Errorf("Expected 3 collections after adding, got %d", manager.Size())
}
// Test adding duplicate collection
duplicate := NewWithData(3, "Duplicate", "Test", 1, nil)
if manager.AddCollection(duplicate) {
t.Error("Should not be able to add collection with duplicate ID")
}
// Test UpdateCollection
newCollection.Name = "Updated Test Collection"
err := manager.UpdateCollection(newCollection)
if err != nil {
t.Errorf("Failed to update collection: %v", err)
}
updated := manager.GetCollection(3)
if updated.GetName() != "Updated Test Collection" {
t.Error("Collection name was not updated")
}
}
// Test Item Needs Detection
func TestItemNeeds(t *testing.T) {
manager := createTestManager()
testCases := []struct {
itemID int32
expected bool
name string
}{
{1001, true, "Collection 1 item 1"},
{1002, true, "Collection 1 item 2"},
{1003, true, "Collection 1 item 3"},
{2001, true, "Collection 2 item 1"},
{2002, true, "Collection 2 item 2"},
{9999, false, "Non-existent item"},
{0, false, "Invalid item ID"},
}
for _, tc := range testCases {
result := manager.NeedsItem(tc.itemID)
if result != tc.expected {
t.Errorf("%s: expected %v, got %v for item %d", tc.name, tc.expected, result, tc.itemID)
}
}
}
// Test Master List Operations
func TestMasterListOperations(t *testing.T) {
manager := createTestManager()
masterList := manager.GetMasterList()
if masterList == nil {
t.Fatal("Master list is nil")
}
// Test getting all collections
allCollections := masterList.GetAllCollections()
if len(allCollections) != 2 {
t.Errorf("Expected 2 collections in master list, got %d", len(allCollections))
}
// Test collections by category
commonCollections := masterList.FindCollectionsByCategory("Common Collections")
if len(commonCollections) != 1 {
t.Errorf("Expected 1 common collection, got %d", len(commonCollections))
}
// Test collections by level
level10Collections := masterList.GetCollectionsByExactLevel(10)
if len(level10Collections) != 1 {
t.Errorf("Expected 1 level 10 collection, got %d", len(level10Collections))
}
levelRangeCollections := masterList.FindCollectionsByLevel(10, 30)
if len(levelRangeCollections) != 2 {
t.Errorf("Expected 2 collections in level range 10-30, got %d", len(levelRangeCollections))
}
// Test collections needing specific item
collectionsNeedingItem := masterList.GetCollectionsNeedingItem(1001)
if len(collectionsNeedingItem) != 1 {
t.Errorf("Expected 1 collection needing item 1001, got %d", len(collectionsNeedingItem))
}
}
// Test Player Collection Management
func TestPlayerCollectionManagement(t *testing.T) {
manager := createTestManager()
// Create player list
playerList := manager.CreatePlayerList(12345)
if playerList == nil {
t.Fatal("Failed to create player list")
}
if playerList.CharacterID != 12345 {
t.Errorf("Expected character ID 12345, got %d", playerList.CharacterID)
}
if playerList.Size() != 0 {
t.Errorf("Expected empty player list, got size %d", playerList.Size())
}
// Add collection to player
collection := manager.GetCollection(1).Clone()
if !playerList.AddCollection(collection) {
t.Error("Failed to add collection to player")
}
if playerList.Size() != 1 {
t.Errorf("Expected 1 collection in player list, got %d", playerList.Size())
}
// Test player collection operations
if playerList.HasCollectionsToHandIn() {
t.Error("Player should not have collections ready to turn in initially")
}
// Mark all items as found
for i := range collection.CollectionItems {
collection.CollectionItems[i].Found = 1
}
if !playerList.HasCollectionsToHandIn() {
t.Error("Player should have collections ready to turn in after finding all items")
}
readyCollections := playerList.GetCollectionsToHandIn()
if len(readyCollections) != 1 {
t.Errorf("Expected 1 collection ready to turn in, got %d", len(readyCollections))
}
}
// Test Packet Building (these would normally require packet definitions)
func TestPacketBuilding(t *testing.T) {
manager := createTestManager()
// Since we don't have actual packet definitions loaded for testing,
// these tests will expect errors, but we can test the code paths
characterID := int32(12345)
clientVersion := uint32(57048)
// Test collection update packet - should fail without packet definitions
playerCollections := make(map[int32]*Collection)
playerCollections[1] = manager.GetCollection(1)
_, err := manager.SendCollectionUpdate(characterID, clientVersion, playerCollections)
if err == nil {
t.Error("Expected error for missing packet definition, but got none")
}
// Test collection filter packet - should fail without packet definitions
collectionsForItem := []*Collection{manager.GetCollection(1)}
_, err = manager.SendCollectionFilter(characterID, clientVersion, 1001, collectionsForItem)
if err == nil {
t.Error("Expected error for missing packet definition, but got none")
}
// Test collection item packet - should fail without packet definitions
_, err = manager.SendCollectionItem(characterID, clientVersion, 1, 1001)
if err == nil {
t.Error("Expected error for missing packet definition, but got none")
}
}
// Test Statistics and Monitoring
func TestStatisticsAndMonitoring(t *testing.T) {
manager := createTestManager()
// Perform some operations to generate statistics
_ = manager.GetCollection(1) // Cache hit
_ = manager.GetCollection(999) // Cache miss
manager.NeedsItem(1001)
manager.NeedsItem(9999)
stats := manager.GetStatistics()
if stats == nil {
t.Fatal("Statistics returned nil")
}
// Check some expected statistics exist
if _, exists := stats["collections_loaded"]; !exists {
t.Error("Expected collections_loaded statistic")
}
if _, exists := stats["cache_hits"]; !exists {
t.Error("Expected cache_hits statistic")
}
if _, exists := stats["cache_misses"]; !exists {
t.Error("Expected cache_misses statistic")
}
if _, exists := stats["item_checks"]; !exists {
t.Error("Expected item_checks statistic")
}
// Test reset statistics
manager.ResetStatistics()
newStats := manager.GetStatistics()
if newStats["cache_hits"].(int64) != 0 {
t.Error("Expected cache hits to be reset to 0")
}
}
// Test Configuration Management
func TestConfigurationManagement(t *testing.T) {
manager := createTestManager()
// Test default configuration
config := manager.GetConfiguration()
if !config["enable_caching"].(bool) {
t.Error("Expected caching to be enabled by default")
}
// Test configuration update
newConfig := map[string]interface{}{
"enable_caching": false,
"enable_validation": false,
"max_players_to_track": 500,
}
manager.SetConfiguration(newConfig)
updatedConfig := manager.GetConfiguration()
if updatedConfig["enable_caching"].(bool) {
t.Error("Expected caching to be disabled after update")
}
if updatedConfig["enable_validation"].(bool) {
t.Error("Expected validation to be disabled after update")
}
if updatedConfig["max_players_to_track"].(int) != 500 {
t.Error("Expected max_players_to_track to be updated to 500")
}
}
// Test Validation and Health Checks
func TestValidationAndHealthChecks(t *testing.T) {
manager := createTestManager()
// Test health check - manager with nil db is not healthy by design
if manager.IsHealthy() {
t.Error("Manager with nil database should not be healthy")
}
// Test validation
issues := manager.ValidateCollections()
if len(issues) > 0 {
t.Errorf("Expected no validation issues, got %d: %v", len(issues), issues)
}
// Add invalid collection to test validation
invalidCollection := NewWithData(-1, "", "", -1, nil)
manager.AddCollection(invalidCollection)
validationIssues := manager.ValidateCollections()
if len(validationIssues) == 0 {
t.Error("Expected validation issues for invalid collection")
}
}
// Test Thread Safety (basic test)
func TestThreadSafety(t *testing.T) {
manager := createTestManager()
// Perform concurrent operations
done := make(chan bool, 10)
// Start multiple goroutines performing different operations
for i := 0; i < 10; i++ {
go func(id int) {
defer func() { done <- true }()
// Mix of read and write operations
manager.GetCollection(1)
manager.NeedsItem(1001)
manager.GetStatistics()
// Try to add a collection (most will fail due to duplicate IDs, which is expected)
newCollection := NewWithData(int32(1000+id), "Test", "Category", 1, nil)
manager.AddCollection(newCollection)
}(i)
}
// Wait for all goroutines to complete
for i := 0; i < 10; i++ {
<-done
}
// Manager should still be functional
if manager.Size() < 2 { // At least original 2 collections
t.Error("Manager appears corrupted after concurrent access")
}
}
// Test Collection Item Management
func TestCollectionItemManagement(t *testing.T) {
manager := createTestManager()
collection := manager.GetCollection(1)
if collection == nil {
t.Fatal("Failed to get test collection")
}
// Test getting collection item by ID
item := collection.GetCollectionItemByItemID(1001)
if item == nil {
t.Fatal("Failed to get collection item 1001")
}
if item.ItemID != 1001 {
t.Errorf("Expected item ID 1001, got %d", item.ItemID)
}
// Test needs item
if !collection.NeedsItem(1001) {
t.Error("Collection should need item 1001")
}
// Test marking item found
if !collection.MarkItemFound(1001) {
t.Error("Failed to mark item 1001 as found")
}
// Test item is no longer needed
if collection.NeedsItem(1001) {
t.Error("Collection should not need item 1001 after marking as found")
}
// Test collection is not ready to turn in (still missing items)
if collection.GetIsReadyToTurnIn() {
t.Error("Collection should not be ready to turn in with only 1 of 3 items found")
}
// Mark all items as found
collection.MarkItemFound(1002)
collection.MarkItemFound(1003)
// Test collection is now ready to turn in
if !collection.GetIsReadyToTurnIn() {
t.Error("Collection should be ready to turn in after finding all items")
}
// Test progress calculation
progress := collection.GetProgress()
if progress != 100.0 {
t.Errorf("Expected 100%% progress, got %.2f%%", progress)
}
}
// Test Collection Cloning
func TestCollectionCloning(t *testing.T) {
manager := createTestManager()
original := manager.GetCollection(1)
if original == nil {
t.Fatal("Failed to get original collection")
}
// Clone the collection
clone := original.Clone()
if clone == nil {
t.Fatal("Failed to clone collection")
}
// Verify clone has same data
if clone.GetID() != original.GetID() {
t.Error("Clone has different ID")
}
if clone.GetName() != original.GetName() {
t.Error("Clone has different name")
}
if clone.GetCategory() != original.GetCategory() {
t.Error("Clone has different category")
}
if clone.GetLevel() != original.GetLevel() {
t.Error("Clone has different level")
}
// Verify clone is independent (modify clone)
clone.Name = "Modified Clone"
if original.GetName() == "Modified Clone" {
t.Error("Modifying clone affected original")
}
// Verify collection items were cloned
if len(clone.CollectionItems) != len(original.CollectionItems) {
t.Error("Clone has different number of collection items")
}
// Modify clone items and verify original is unaffected
if len(clone.CollectionItems) > 0 {
clone.CollectionItems[0].Found = 1
if original.CollectionItems[0].Found == 1 {
t.Error("Modifying clone item affected original")
}
}
}
// Test Edge Cases and Error Conditions
func TestEdgeCases(t *testing.T) {
manager := createTestManager()
// Test nil operations
if manager.AddCollection(nil) {
t.Error("Should not be able to add nil collection")
}
err := manager.UpdateCollection(nil)
if err == nil {
t.Error("Should return error for nil collection update")
}
// Test invalid collection data
invalidCollection := NewWithData(0, "", "", -5, nil)
err = manager.UpdateCollection(invalidCollection)
if err == nil {
t.Error("Should return error for invalid collection data")
}
// Test empty manager operations
emptyManager := NewManager(nil)
if emptyManager.Size() != 0 {
t.Error("Empty manager should have size 0")
}
if emptyManager.NeedsItem(1001) {
t.Error("Empty manager should not need any items")
}
// Test clear collections
manager.ClearCollections()
if manager.Size() != 0 {
t.Error("Manager should be empty after clearing collections")
}
// Test operations on cleared manager
if manager.GetCollection(1) != nil {
t.Error("Should return nil for collection after clearing")
}
if manager.NeedsItem(1001) {
t.Error("Cleared manager should not need any items")
}
}
// Benchmark tests
func BenchmarkGetCollection(b *testing.B) {
manager := createTestManager()
b.ResetTimer()
for i := 0; i < b.N; i++ {
manager.GetCollection(1)
}
}
func BenchmarkNeedsItem(b *testing.B) {
manager := createTestManager()
b.ResetTimer()
for i := 0; i < b.N; i++ {
manager.NeedsItem(1001)
}
}
func BenchmarkGetStatistics(b *testing.B) {
manager := createTestManager()
b.ResetTimer()
for i := 0; i < b.N; i++ {
manager.GetStatistics()
}
}