package trade import ( "sync" "testing" "time" ) // Test coin calculation utilities func TestCalculateCoins(t *testing.T) { tests := []struct { name string totalCopper int64 expectedPt int32 expectedGold int32 expectedSilv int32 expectedCopp int32 }{ {"Zero coins", 0, 0, 0, 0, 0}, {"Only copper", 50, 0, 0, 0, 50}, {"Only silver", 500, 0, 0, 5, 0}, {"Only gold", 50000, 0, 5, 0, 0}, {"Only platinum", 5000000, 5, 0, 0, 0}, {"Mixed coins", 1234567, 1, 23, 45, 67}, {"Large amount", 9999999999, 9999, 99, 99, 99}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := CalculateCoins(tt.totalCopper) if result.Platinum != tt.expectedPt { t.Errorf("Expected platinum %d, got %d", tt.expectedPt, result.Platinum) } if result.Gold != tt.expectedGold { t.Errorf("Expected gold %d, got %d", tt.expectedGold, result.Gold) } if result.Silver != tt.expectedSilv { t.Errorf("Expected silver %d, got %d", tt.expectedSilv, result.Silver) } if result.Copper != tt.expectedCopp { t.Errorf("Expected copper %d, got %d", tt.expectedCopp, result.Copper) } }) } } func TestCoinsToCopper(t *testing.T) { coins := CoinAmounts{ Platinum: 1, Gold: 23, Silver: 45, Copper: 67, } expected := int64(1234567) result := CoinsToCopper(coins) if result != expected { t.Errorf("Expected %d copper, got %d", expected, result) } } func TestFormatCoins(t *testing.T) { tests := []struct { name string copper int64 expected string }{ {"Zero coins", 0, "0 copper"}, {"Only copper", 50, "50 copper"}, {"Only silver", 500, "5 silver"}, {"Mixed coins", 1234567, "1 platinum, 23 gold, 45 silver, 67 copper"}, {"No copper", 1230000, "1 platinum, 23 gold"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := FormatCoins(tt.copper) if result != tt.expected { t.Errorf("Expected '%s', got '%s'", tt.expected, result) } }) } } // Test validation utilities func TestValidateTradeSlot(t *testing.T) { tests := []struct { name string slot int8 maxSlots int8 expected bool }{ {"Valid slot 0", 0, 12, true}, {"Valid slot middle", 5, 12, true}, {"Valid slot max-1", 11, 12, true}, {"Invalid negative", -1, 12, false}, {"Invalid too high", 12, 12, false}, {"Invalid way too high", 100, 12, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ValidateTradeSlot(tt.slot, tt.maxSlots) if result != tt.expected { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } func TestValidateTradeQuantity(t *testing.T) { tests := []struct { name string quantity int32 available int32 expected bool }{ {"Valid quantity", 5, 10, true}, {"Valid exact match", 10, 10, true}, {"Invalid zero", 0, 10, false}, {"Invalid negative", -1, 10, false}, {"Invalid too much", 11, 10, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ValidateTradeQuantity(tt.quantity, tt.available) if result != tt.expected { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } func TestFormatTradeError(t *testing.T) { tests := []struct { name string code int32 expected string }{ {"Success", TradeResultSuccess, "Success"}, {"Already in trade", TradeResultAlreadyInTrade, "Item is already in the trade"}, {"No trade", TradeResultNoTrade, "Item cannot be traded"}, {"Heirloom", TradeResultHeirloom, "Heirloom item cannot be traded to this player"}, {"Invalid slot", TradeResultInvalidSlot, "Invalid or occupied trade slot"}, {"Slot out of range", TradeResultSlotOutOfRange, "Trade slot is out of range"}, {"Insufficient qty", TradeResultInsufficientQty, "Insufficient quantity to trade"}, {"Unknown error", 999, "Unknown trade error: 999"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := FormatTradeError(tt.code) if result != tt.expected { t.Errorf("Expected '%s', got '%s'", tt.expected, result) } }) } } func TestGetClientMaxSlots(t *testing.T) { tests := []struct { name string version int32 expected int8 }{ {"Very old client", 500, TradeMaxSlotsLegacy}, {"Legacy client", 561, TradeMaxSlotsLegacy}, {"Modern client", 562, TradeMaxSlotsDefault}, {"New client", 1000, TradeMaxSlotsDefault}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := GetClientMaxSlots(tt.version) if result != tt.expected { t.Errorf("Expected %d slots, got %d", tt.expected, result) } }) } } func TestIsValidTradeState(t *testing.T) { tests := []struct { name string state TradeState operation string expected bool }{ {"Add item active", TradeStateActive, "add_item", true}, {"Add item completed", TradeStateCompleted, "add_item", false}, {"Cancel active", TradeStateActive, "cancel", true}, {"Complete accepted", TradeStateAccepted, "complete", true}, {"Complete active", TradeStateActive, "complete", false}, {"Invalid operation", TradeStateActive, "invalid", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := IsValidTradeState(tt.state, tt.operation) if result != tt.expected { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } // Test TradeValidationError func TestTradeValidationError(t *testing.T) { err := &TradeValidationError{ Code: TradeResultNoTrade, Message: "Test error message", } if err.Error() != "Test error message" { t.Errorf("Expected 'Test error message', got '%s'", err.Error()) } } // Test placeholder implementations func TestPlaceholderEntity(t *testing.T) { entity := &PlaceholderEntity{ ID: 123, Name: "Test Entity", IsPlayerFlag: true, IsBotFlag: false, CoinsAmount: 10000, ClientVer: 800, } if entity.GetID() != 123 { t.Errorf("Expected ID 123, got %d", entity.GetID()) } if entity.GetName() != "Test Entity" { t.Errorf("Expected name 'Test Entity', got '%s'", entity.GetName()) } if !entity.IsPlayer() { t.Error("Expected entity to be a player") } if entity.IsBot() { t.Error("Expected entity not to be a bot") } if !entity.HasCoins(5000) { t.Error("Expected entity to have enough coins") } if entity.HasCoins(15000) { t.Error("Expected entity not to have enough coins") } if entity.GetClientVersion() != 800 { t.Errorf("Expected client version 800, got %d", entity.GetClientVersion()) } // Test default name entityNoName := &PlaceholderEntity{ID: 456} expectedName := "Entity_456" if entityNoName.GetName() != expectedName { t.Errorf("Expected default name '%s', got '%s'", expectedName, entityNoName.GetName()) } // Test default client version entityNoVersion := &PlaceholderEntity{ID: 789} if entityNoVersion.GetClientVersion() != 1000 { t.Errorf("Expected default client version 1000, got %d", entityNoVersion.GetClientVersion()) } } func TestPlaceholderItem(t *testing.T) { creationTime := time.Now() item := &PlaceholderItem{ ID: 456, Name: "Test Item", Quantity: 10, IconID: 789, NoTradeFlag: false, HeirloomFlag: true, AttunedFlag: false, CreatedTime: creationTime, GroupIDs: []int32{1, 2, 3}, } if item.GetID() != 456 { t.Errorf("Expected ID 456, got %d", item.GetID()) } if item.GetName() != "Test Item" { t.Errorf("Expected name 'Test Item', got '%s'", item.GetName()) } if item.GetQuantity() != 10 { t.Errorf("Expected quantity 10, got %d", item.GetQuantity()) } if item.GetIcon(1000) != 789 { t.Errorf("Expected icon ID 789, got %d", item.GetIcon(1000)) } if item.IsNoTrade() { t.Error("Expected item not to be no-trade") } if !item.IsHeirloom() { t.Error("Expected item to be heirloom") } if item.IsAttuned() { t.Error("Expected item not to be attuned") } if !item.GetCreationTime().Equal(creationTime) { t.Error("Expected creation time to match") } groupIDs := item.GetGroupCharacterIDs() if len(groupIDs) != 3 || groupIDs[0] != 1 || groupIDs[1] != 2 || groupIDs[2] != 3 { t.Errorf("Expected group IDs [1,2,3], got %v", groupIDs) } // Test default name itemNoName := &PlaceholderItem{ID: 999} expectedName := "Item_999" if itemNoName.GetName() != expectedName { t.Errorf("Expected default name '%s', got '%s'", expectedName, itemNoName.GetName()) } } // Test TradeParticipant func TestNewTradeParticipant(t *testing.T) { // Test legacy client participant := NewTradeParticipant(123, false, 561) if participant.EntityID != 123 { t.Errorf("Expected entity ID 123, got %d", participant.EntityID) } if participant.IsBot != false { t.Error("Expected participant not to be a bot") } if participant.MaxSlots != TradeMaxSlotsLegacy { t.Errorf("Expected %d max slots, got %d", TradeMaxSlotsLegacy, participant.MaxSlots) } if participant.ClientVersion != 561 { t.Errorf("Expected client version 561, got %d", participant.ClientVersion) } if participant.HasAccepted { t.Error("Expected participant not to have accepted initially") } if len(participant.Items) != 0 { t.Error("Expected empty items map initially") } if participant.Coins != 0 { t.Error("Expected zero coins initially") } // Test modern client participant2 := NewTradeParticipant(456, true, 1000) if participant2.MaxSlots != TradeMaxSlotsDefault { t.Errorf("Expected %d max slots, got %d", TradeMaxSlotsDefault, participant2.MaxSlots) } if participant2.IsBot != true { t.Error("Expected participant to be a bot") } } func TestTradeParticipantMethods(t *testing.T) { participant := NewTradeParticipant(123, false, 1000) // Test GetNextFreeSlot slot := participant.GetNextFreeSlot() if slot != 0 { t.Errorf("Expected first free slot to be 0, got %d", slot) } // Add an item and test slot finding testItem := &PlaceholderItem{ID: 100, Quantity: 5} participant.Items[0] = TradeItemInfo{Item: testItem, Quantity: 5} slot = participant.GetNextFreeSlot() if slot != 1 { t.Errorf("Expected next free slot to be 1, got %d", slot) } // Fill all slots for i := int8(1); i < participant.MaxSlots; i++ { participant.Items[i] = TradeItemInfo{Item: testItem, Quantity: 1} } slot = participant.GetNextFreeSlot() if slot != int8(TradeSlotAutoFind) { t.Errorf("Expected no free slots (%d), got %d", int8(TradeSlotAutoFind), slot) } // Test HasItem if !participant.HasItem(100) { t.Error("Expected participant to have item 100") } if participant.HasItem(999) { t.Error("Expected participant not to have item 999") } // Test GetItemCount expectedCount := int(participant.MaxSlots) if participant.GetItemCount() != expectedCount { t.Errorf("Expected %d items, got %d", expectedCount, participant.GetItemCount()) } // Test ClearItems participant.ClearItems() if participant.GetItemCount() != 0 { t.Error("Expected no items after clear") } // Test GetCoinAmounts participant.Coins = 1234567 coinAmounts := participant.GetCoinAmounts() expected := CalculateCoins(1234567) if coinAmounts != expected { t.Errorf("Expected coin amounts %+v, got %+v", expected, coinAmounts) } } // Test TradeManager func TestTradeManager(t *testing.T) { tm := NewTradeManager() if tm.GetActiveTradeCount() != 0 { t.Error("Expected no active trades initially") } // Create test entities entity1 := &PlaceholderEntity{ID: 100, Name: "Player1"} entity2 := &PlaceholderEntity{ID: 200, Name: "Player2"} // Create a trade trade := NewTrade(entity1, entity2) if trade == nil { t.Fatal("Failed to create trade") } // Add trade to manager tm.AddTrade(trade) if tm.GetActiveTradeCount() != 1 { t.Error("Expected 1 active trade") } // Test GetTrade for trader1 retrievedTrade := tm.GetTrade(100) if retrievedTrade == nil { t.Error("Expected to find trade for entity 100") } if retrievedTrade.GetTrader1ID() != 100 { t.Error("Expected trade with trader1 ID 100") } // Test GetTrade for trader2 retrievedTrade = tm.GetTrade(200) if retrievedTrade == nil { t.Error("Expected to find trade for entity 200") } if retrievedTrade.GetTrader2ID() != 200 { t.Error("Expected trade with trader2 ID 200") } // Test GetTrade for non-participant retrievedTrade = tm.GetTrade(999) if retrievedTrade != nil { t.Error("Expected no trade for entity 999") } // Remove trade tm.RemoveTrade(100) if tm.GetActiveTradeCount() != 0 { t.Error("Expected no active trades after removal") } retrievedTrade = tm.GetTrade(100) if retrievedTrade != nil { t.Error("Expected no trade after removal") } } // Test Trade creation and basic operations func TestNewTrade(t *testing.T) { entity1 := &PlaceholderEntity{ID: 100, Name: "Player1"} entity2 := &PlaceholderEntity{ID: 200, Name: "Player2"} // Valid trade creation trade := NewTrade(entity1, entity2) if trade == nil { t.Fatal("Expected trade to be created") } if trade.GetTrader1ID() != 100 { t.Errorf("Expected trader1 ID 100, got %d", trade.GetTrader1ID()) } if trade.GetTrader2ID() != 200 { t.Errorf("Expected trader2 ID 200, got %d", trade.GetTrader2ID()) } if trade.GetState() != TradeStateActive { t.Errorf("Expected trade state %d, got %d", TradeStateActive, trade.GetState()) } // Test GetTradee if trade.GetTradee(100) != 200 { t.Error("Expected tradee of 100 to be 200") } if trade.GetTradee(200) != 100 { t.Error("Expected tradee of 200 to be 100") } if trade.GetTradee(999) != 0 { t.Error("Expected tradee of 999 to be 0") } // Test GetParticipant participant := trade.GetParticipant(100) if participant == nil { t.Error("Expected to find participant 100") } if participant.EntityID != 100 { t.Error("Expected participant entity ID 100") } participant = trade.GetParticipant(999) if participant != nil { t.Error("Expected not to find participant 999") } // Invalid trade creation invalidTrade := NewTrade(nil, entity2) if invalidTrade != nil { t.Error("Expected trade creation to fail with nil entity") } invalidTrade = NewTrade(entity1, nil) if invalidTrade != nil { t.Error("Expected trade creation to fail with nil entity") } } func TestTradeAddRemoveItems(t *testing.T) { entity1 := &PlaceholderEntity{ID: 100} entity2 := &PlaceholderEntity{ID: 200} trade := NewTrade(entity1, entity2) testItem := &PlaceholderItem{ ID: 500, Name: "Test Item", Quantity: 10, } // Test adding item err := trade.AddItemToTrade(100, testItem, 5, 0) if err != nil { t.Fatalf("Failed to add item to trade: %v", err) } // Verify item was added retrievedItem := trade.GetTraderSlot(100, 0) if retrievedItem == nil { t.Error("Expected to find item in slot 0") } if retrievedItem.GetID() != 500 { t.Error("Expected item ID 500") } // Test auto-slot finding testItem2 := &PlaceholderItem{ID: 501, Quantity: 3} err = trade.AddItemToTrade(100, testItem2, 3, TradeSlotAutoFind) if err != nil { t.Fatalf("Failed to add item with auto-slot: %v", err) } retrievedItem = trade.GetTraderSlot(100, 1) if retrievedItem == nil { t.Error("Expected to find item in slot 1") } // Test adding to occupied slot err = trade.AddItemToTrade(100, testItem, 1, 0) if err == nil { t.Error("Expected error when adding to occupied slot") } // Test adding duplicate item err = trade.AddItemToTrade(100, testItem, 1, 2) if err == nil { t.Error("Expected error when adding duplicate item") } // Test invalid slot err = trade.AddItemToTrade(100, testItem, 1, 99) if err == nil { t.Error("Expected error for invalid slot") } // Test insufficient quantity err = trade.AddItemToTrade(100, testItem, 20, 3) if err == nil { t.Error("Expected error for insufficient quantity") } // Test invalid entity err = trade.AddItemToTrade(999, testItem, 1, 4) if err == nil { t.Error("Expected error for invalid entity") } // Test removing item err = trade.RemoveItemFromTrade(100, 0) if err != nil { t.Fatalf("Failed to remove item: %v", err) } retrievedItem = trade.GetTraderSlot(100, 0) if retrievedItem != nil { t.Error("Expected item to be removed from slot 0") } // Test removing from empty slot err = trade.RemoveItemFromTrade(100, 0) if err == nil { t.Error("Expected error when removing from empty slot") } // Test no-trade item noTradeItem := &PlaceholderItem{ ID: 600, Quantity: 5, NoTradeFlag: true, } err = trade.AddItemToTrade(100, noTradeItem, 5, 0) if err == nil { t.Error("Expected error for no-trade item") } // Test attuned heirloom item attunedHeirloom := &PlaceholderItem{ ID: 700, Quantity: 1, HeirloomFlag: true, AttunedFlag: true, } err = trade.AddItemToTrade(100, attunedHeirloom, 1, 0) if err == nil { t.Error("Expected error for attuned heirloom item") } // Test expired heirloom item expiredHeirloom := &PlaceholderItem{ ID: 701, Quantity: 1, HeirloomFlag: true, AttunedFlag: false, CreatedTime: time.Now().Add(-72 * time.Hour), // 3 days ago } err = trade.AddItemToTrade(100, expiredHeirloom, 1, 0) if err == nil { t.Error("Expected error for expired heirloom item") } // Test valid recent heirloom item recentHeirloom := &PlaceholderItem{ ID: 702, Quantity: 1, HeirloomFlag: true, AttunedFlag: false, CreatedTime: time.Now().Add(-1 * time.Hour), // 1 hour ago } err = trade.AddItemToTrade(100, recentHeirloom, 1, 0) if err != nil { t.Errorf("Expected recent heirloom item to be tradeable: %v", err) } } func TestTradeCoins(t *testing.T) { entity1 := &PlaceholderEntity{ID: 100} entity2 := &PlaceholderEntity{ID: 200} trade := NewTrade(entity1, entity2) // Test adding coins err := trade.AddCoinsToTrade(100, 5000) if err != nil { t.Fatalf("Failed to add coins to trade: %v", err) } participant := trade.GetParticipant(100) if participant.Coins != 5000 { t.Errorf("Expected 5000 coins, got %d", participant.Coins) } // Test adding more coins err = trade.AddCoinsToTrade(100, 2000) if err != nil { t.Fatalf("Failed to add more coins: %v", err) } if participant.Coins != 7000 { t.Errorf("Expected 7000 coins, got %d", participant.Coins) } // Test removing coins err = trade.RemoveCoinsFromTrade(100, 3000) if err != nil { t.Fatalf("Failed to remove coins: %v", err) } if participant.Coins != 4000 { t.Errorf("Expected 4000 coins, got %d", participant.Coins) } // Test removing more coins than available err = trade.RemoveCoinsFromTrade(100, 10000) if err != nil { t.Fatalf("Failed to remove excess coins: %v", err) } if participant.Coins != 0 { t.Errorf("Expected 0 coins, got %d", participant.Coins) } // Test invalid coin amount err = trade.AddCoinsToTrade(100, -100) if err == nil { t.Error("Expected error for negative coin amount") } err = trade.AddCoinsToTrade(100, 0) if err == nil { t.Error("Expected error for zero coin amount") } // Test invalid entity err = trade.AddCoinsToTrade(999, 1000) if err == nil { t.Error("Expected error for invalid entity") } } func TestTradeAcceptance(t *testing.T) { entity1 := &PlaceholderEntity{ID: 100} entity2 := &PlaceholderEntity{ID: 200} trade := NewTrade(entity1, entity2) // Initially, neither should have accepted if trade.HasAcceptedTrade(100) { t.Error("Expected trader1 not to have accepted initially") } if trade.HasAcceptedTrade(200) { t.Error("Expected trader2 not to have accepted initially") } if trade.HasAcceptedTrade(999) { t.Error("Expected invalid entity not to have accepted") } // First trader accepts completed, err := trade.SetTradeAccepted(100) if err != nil { t.Fatalf("Failed to set trade accepted: %v", err) } if completed { t.Error("Expected trade not to be completed after one acceptance") } if !trade.HasAcceptedTrade(100) { t.Error("Expected trader1 to have accepted") } if trade.HasAcceptedTrade(200) { t.Error("Expected trader2 not to have accepted yet") } // Second trader accepts - should complete completed, err = trade.SetTradeAccepted(200) if err != nil { t.Fatalf("Failed to set second trade accepted: %v", err) } if !completed { t.Error("Expected trade to be completed after both acceptances") } if trade.GetState() != TradeStateCompleted { t.Errorf("Expected trade state %d, got %d", TradeStateCompleted, trade.GetState()) } // Test accepting invalid entity _, err = trade.SetTradeAccepted(999) if err == nil { t.Error("Expected error for invalid entity acceptance") } } func TestTradeCancel(t *testing.T) { entity1 := &PlaceholderEntity{ID: 100} entity2 := &PlaceholderEntity{ID: 200} trade := NewTrade(entity1, entity2) // Cancel trade err := trade.CancelTrade(100) if err != nil { t.Fatalf("Failed to cancel trade: %v", err) } if trade.GetState() != TradeStateCanceled { t.Errorf("Expected trade state %d, got %d", TradeStateCanceled, trade.GetState()) } // Test operations on canceled trade testItem := &PlaceholderItem{ID: 500, Quantity: 5} err = trade.AddItemToTrade(100, testItem, 5, 0) if err == nil { t.Error("Expected error adding item to canceled trade") } err = trade.AddCoinsToTrade(100, 1000) if err == nil { t.Error("Expected error adding coins to canceled trade") } _, err = trade.SetTradeAccepted(100) if err == nil { t.Error("Expected error accepting canceled trade") } } func TestTradeStateChangesResetAcceptance(t *testing.T) { entity1 := &PlaceholderEntity{ID: 100} entity2 := &PlaceholderEntity{ID: 200} trade := NewTrade(entity1, entity2) // Both traders accept trade.SetTradeAccepted(100) trade.SetTradeAccepted(200) // This completes the trade // Create new trade for testing reset behavior trade2 := NewTrade(entity1, entity2) // First trader accepts trade2.SetTradeAccepted(100) if !trade2.HasAcceptedTrade(100) { t.Error("Expected trader1 to have accepted") } // Add item - should reset acceptance testItem := &PlaceholderItem{ID: 500, Quantity: 5} err := trade2.AddItemToTrade(100, testItem, 5, 0) if err != nil { t.Fatalf("Failed to add item: %v", err) } if trade2.HasAcceptedTrade(100) { t.Error("Expected acceptance to be reset after adding item") } // Accept again and add coins - should reset trade2.SetTradeAccepted(100) err = trade2.AddCoinsToTrade(100, 1000) if err != nil { t.Fatalf("Failed to add coins: %v", err) } if trade2.HasAcceptedTrade(100) { t.Error("Expected acceptance to be reset after adding coins") } // Accept again and remove item - should reset trade2.SetTradeAccepted(100) err = trade2.RemoveItemFromTrade(100, 0) if err != nil { t.Fatalf("Failed to remove item: %v", err) } if trade2.HasAcceptedTrade(100) { t.Error("Expected acceptance to be reset after removing item") } // Accept again and remove coins - should reset trade2.SetTradeAccepted(100) err = trade2.RemoveCoinsFromTrade(100, 500) if err != nil { t.Fatalf("Failed to remove coins: %v", err) } if trade2.HasAcceptedTrade(100) { t.Error("Expected acceptance to be reset after removing coins") } } func TestGetTradeInfo(t *testing.T) { entity1 := &PlaceholderEntity{ID: 100} entity2 := &PlaceholderEntity{ID: 200} trade := NewTrade(entity1, entity2) // Add some items and coins testItem := &PlaceholderItem{ID: 500, Quantity: 5} trade.AddItemToTrade(100, testItem, 5, 0) trade.AddCoinsToTrade(200, 10000) info := trade.GetTradeInfo() if info["state"] != TradeStateActive { t.Error("Expected trade state to be active") } if info["trader1_id"] != int32(100) { t.Error("Expected trader1_id to be 100") } if info["trader2_id"] != int32(200) { t.Error("Expected trader2_id to be 200") } if info["trader1_items"] != 1 { t.Error("Expected trader1 to have 1 item") } if info["trader2_items"] != 0 { t.Error("Expected trader2 to have 0 items") } if info["trader1_coins"] != int64(0) { t.Error("Expected trader1 to have 0 coins") } if info["trader2_coins"] != int64(10000) { t.Error("Expected trader2 to have 10000 coins") } if info["trader1_accepted"] != false { t.Error("Expected trader1 not to have accepted") } if info["trader2_accepted"] != false { t.Error("Expected trader2 not to have accepted") } // Test after acceptance trade.SetTradeAccepted(100) info = trade.GetTradeInfo() if info["trader1_accepted"] != true { t.Error("Expected trader1 to have accepted") } } // Test TradeService func TestTradeService(t *testing.T) { service := NewTradeService() if service.GetActiveTradeCount() != 0 { t.Error("Expected no active trades initially") } // Test InitiateTrade trade, err := service.InitiateTrade(100, 200) if err != nil { t.Fatalf("Failed to initiate trade: %v", err) } if trade == nil { t.Fatal("Expected trade to be created") } if service.GetActiveTradeCount() != 1 { t.Error("Expected 1 active trade") } // Test duplicate trade initiation _, err = service.InitiateTrade(100, 300) if err == nil { t.Error("Expected error when initiating duplicate trade") } _, err = service.InitiateTrade(300, 200) if err == nil { t.Error("Expected error when target is already in trade") } // Test GetTrade retrievedTrade := service.GetTrade(100) if retrievedTrade == nil { t.Error("Expected to find trade") } // Test service operations testItem := &PlaceholderItem{ID: 500, Quantity: 10} err = service.AddItemToTrade(100, testItem, 5, 0) if err != nil { t.Fatalf("Failed to add item via service: %v", err) } err = service.AddCoinsToTrade(200, 5000) if err != nil { t.Fatalf("Failed to add coins via service: %v", err) } // Test GetTradeInfo info, err := service.GetTradeInfo(100) if err != nil { t.Fatalf("Failed to get trade info: %v", err) } if info["trader1_items"] != 1 { t.Error("Expected 1 item in trade info") } // Test AcceptTrade completed, err := service.AcceptTrade(100) if err != nil { t.Fatalf("Failed to accept trade: %v", err) } if completed { t.Error("Expected trade not to be completed yet") } completed, err = service.AcceptTrade(200) if err != nil { t.Fatalf("Failed to accept trade for second trader: %v", err) } if !completed { t.Error("Expected trade to be completed") } if service.GetActiveTradeCount() != 0 { t.Error("Expected no active trades after completion") } } func TestTradeServiceValidation(t *testing.T) { service := NewTradeService() // Test ValidateTradeRequest err := service.ValidateTradeRequest(100, 100) if err == nil { t.Error("Expected error for self-trade") } err = service.ValidateTradeRequest(0, 100) if err == nil { t.Error("Expected error for invalid initiator ID") } err = service.ValidateTradeRequest(100, -1) if err == nil { t.Error("Expected error for invalid target ID") } // Valid request err = service.ValidateTradeRequest(100, 200) if err != nil { t.Errorf("Expected valid trade request: %v", err) } // After creating trade, validation should fail service.InitiateTrade(100, 200) err = service.ValidateTradeRequest(100, 300) if err == nil { t.Error("Expected error for already trading initiator") } err = service.ValidateTradeRequest(300, 200) if err == nil { t.Error("Expected error for already trading target") } } func TestTradeServiceCancel(t *testing.T) { service := NewTradeService() trade, _ := service.InitiateTrade(100, 200) if trade == nil { t.Fatal("Failed to create trade") } err := service.CancelTrade(100) if err != nil { t.Fatalf("Failed to cancel trade: %v", err) } if service.GetActiveTradeCount() != 0 { t.Error("Expected no active trades after cancellation") } // Test canceling non-existent trade err = service.CancelTrade(999) if err == nil { t.Error("Expected error canceling non-existent trade") } } func TestTradeServiceAdminFunctions(t *testing.T) { service := NewTradeService() trade, _ := service.InitiateTrade(100, 200) if trade == nil { t.Fatal("Failed to create trade") } // Test ForceCompleteTrade err := service.ForceCompleteTrade(100) if err != nil { t.Fatalf("Failed to force complete trade: %v", err) } if service.GetActiveTradeCount() != 0 { t.Error("Expected no active trades after forced completion") } // Test ForceCancelTrade trade2, _ := service.InitiateTrade(300, 400) if trade2 == nil { t.Fatal("Failed to create second trade") } err = service.ForceCancelTrade(300, "Admin intervention") if err != nil { t.Fatalf("Failed to force cancel trade: %v", err) } if service.GetActiveTradeCount() != 0 { t.Error("Expected no active trades after forced cancellation") } } func TestTradeServiceStatistics(t *testing.T) { service := NewTradeService() stats := service.GetTradeStatistics() if stats["active_trades"] != 0 { t.Error("Expected 0 active trades in statistics") } if stats["max_trade_duration_minutes"] != float64(30) { t.Error("Expected 30 minute max duration") } // Create some trades service.InitiateTrade(100, 200) service.InitiateTrade(300, 400) stats = service.GetTradeStatistics() if stats["active_trades"] != 2 { t.Error("Expected 2 active trades in statistics") } } func TestTradeServiceShutdown(t *testing.T) { service := NewTradeService() service.InitiateTrade(100, 200) service.InitiateTrade(300, 400) if service.GetActiveTradeCount() != 2 { t.Fatal("Expected 2 active trades before shutdown") } service.Shutdown() if service.GetActiveTradeCount() != 0 { t.Error("Expected no active trades after shutdown") } } // Test concurrent access func TestTradeConcurrency(t *testing.T) { service := NewTradeService() trade, _ := service.InitiateTrade(100, 200) var wg sync.WaitGroup numGoroutines := 10 numOperations := 100 // Test concurrent coin operations wg.Add(numGoroutines) for i := 0; i < numGoroutines; i++ { go func(id int) { defer wg.Done() for j := 0; j < numOperations; j++ { if j%2 == 0 { trade.AddCoinsToTrade(100, 100) } else { trade.RemoveCoinsFromTrade(100, 50) } } }(i) } wg.Wait() // Verify trade is still in valid state if trade.GetState() != TradeStateActive { t.Error("Expected trade to remain active after concurrent operations") } info := trade.GetTradeInfo() if info == nil { t.Error("Expected to get trade info after concurrent operations") } } // Test utility functions func TestCompareTradeItems(t *testing.T) { item1 := &PlaceholderItem{ID: 100} item2 := &PlaceholderItem{ID: 200} tradeItem1 := TradeItemInfo{Item: item1, Quantity: 5} tradeItem2 := TradeItemInfo{Item: item1, Quantity: 5} tradeItem3 := TradeItemInfo{Item: item2, Quantity: 5} tradeItem4 := TradeItemInfo{Item: item1, Quantity: 10} // Test equal items if !CompareTradeItems(tradeItem1, tradeItem2) { t.Error("Expected items to be equal") } // Test different items if CompareTradeItems(tradeItem1, tradeItem3) { t.Error("Expected items to be different (different item)") } // Test different quantities if CompareTradeItems(tradeItem1, tradeItem4) { t.Error("Expected items to be different (different quantity)") } // Test nil items nilItem1 := TradeItemInfo{Item: nil, Quantity: 5} nilItem2 := TradeItemInfo{Item: nil, Quantity: 5} nilItem3 := TradeItemInfo{Item: nil, Quantity: 10} if !CompareTradeItems(nilItem1, nilItem2) { t.Error("Expected nil items with same quantity to be equal") } if CompareTradeItems(nilItem1, nilItem3) { t.Error("Expected nil items with different quantity to be different") } if CompareTradeItems(nilItem1, tradeItem1) { t.Error("Expected nil item and real item to be different") } } func TestCalculateTradeValue(t *testing.T) { participant := NewTradeParticipant(123, false, 1000) participant.Coins = 50000 item1 := &PlaceholderItem{ID: 100, Name: "Sword"} item2 := &PlaceholderItem{ID: 200, Name: "Shield"} participant.Items[0] = TradeItemInfo{Item: item1, Quantity: 1} participant.Items[1] = TradeItemInfo{Item: item2, Quantity: 2} value := CalculateTradeValue(participant) if value["coins"] != int64(50000) { t.Error("Expected coins value to be 50000") } coinsFormatted, ok := value["coins_formatted"].(string) if !ok || coinsFormatted != "5 gold" { t.Errorf("Expected coins_formatted to be '5 gold', got %v", coinsFormatted) } if value["item_count"] != 2 { t.Error("Expected item count to be 2") } items, ok := value["items"].([]map[string]any) if !ok || len(items) != 2 { t.Error("Expected items array with 2 elements") } // Verify item data (order may vary due to map iteration) foundSword := false foundShield := false for _, item := range items { if item["item_name"] == "Sword" { foundSword = true if item["quantity"] != int32(1) { t.Error("Expected sword quantity to be 1") } } if item["item_name"] == "Shield" { foundShield = true if item["quantity"] != int32(2) { t.Error("Expected shield quantity to be 2") } } } if !foundSword || !foundShield { t.Error("Expected to find both sword and shield in items") } } func TestValidateTradeCompletion(t *testing.T) { entity1 := &PlaceholderEntity{ID: 100} entity2 := &PlaceholderEntity{ID: 200} trade := NewTrade(entity1, entity2) // Test validation with no acceptance errors := ValidateTradeCompletion(trade) expectedErrors := 2 // Neither trader has accepted if len(errors) != expectedErrors { t.Errorf("Expected %d errors, got %d: %v", expectedErrors, len(errors), errors) } // Test validation with one acceptance trade.SetTradeAccepted(100) errors = ValidateTradeCompletion(trade) expectedErrors = 1 // Only trader2 hasn't accepted if len(errors) != expectedErrors { t.Errorf("Expected %d errors, got %d: %v", expectedErrors, len(errors), errors) } // Test validation with both acceptances trade.SetTradeAccepted(200) // This completes the trade // Create new trade for testing completed state validation completedTrade := NewTrade(entity1, entity2) completedTrade.SetTradeAccepted(100) completedTrade.SetTradeAccepted(200) // This marks as completed errors = ValidateTradeCompletion(completedTrade) expectedErrors = 1 // Trade is not in active state if len(errors) != expectedErrors { t.Errorf("Expected %d errors for completed trade, got %d: %v", expectedErrors, len(errors), errors) } } func TestGenerateTradeLogEntry(t *testing.T) { tradeID := "trade_123" operation := "add_item" entityID := int32(456) details := map[string]any{"item_id": 789, "quantity": 5} logEntry := GenerateTradeLogEntry(tradeID, operation, entityID, details) expected := "[Trade:trade_123] add_item by entity 456: map[item_id:789 quantity:5]" if logEntry != expected { t.Errorf("Expected log entry '%s', got '%s'", expected, logEntry) } } // Benchmark tests func BenchmarkCalculateCoins(b *testing.B) { for i := 0; i < b.N; i++ { CalculateCoins(1234567) } } func BenchmarkTradeCreation(b *testing.B) { entity1 := &PlaceholderEntity{ID: 100} entity2 := &PlaceholderEntity{ID: 200} b.ResetTimer() for i := 0; i < b.N; i++ { trade := NewTrade(entity1, entity2) _ = trade } } func BenchmarkTradeItemOperations(b *testing.B) { entity1 := &PlaceholderEntity{ID: 100} entity2 := &PlaceholderEntity{ID: 200} trade := NewTrade(entity1, entity2) item := &PlaceholderItem{ID: 500, Quantity: 100} b.ResetTimer() for i := 0; i < b.N; i++ { slot := int8(i % int(TradeMaxSlotsDefault)) trade.AddItemToTrade(100, item, 1, slot) trade.RemoveItemFromTrade(100, slot) } } func BenchmarkTradeCoinOperations(b *testing.B) { entity1 := &PlaceholderEntity{ID: 100} entity2 := &PlaceholderEntity{ID: 200} trade := NewTrade(entity1, entity2) b.ResetTimer() for i := 0; i < b.N; i++ { trade.AddCoinsToTrade(100, 1000) trade.RemoveCoinsFromTrade(100, 500) } } func BenchmarkTradeManagerOperations(b *testing.B) { tm := NewTradeManager() entity1 := &PlaceholderEntity{ID: 100} entity2 := &PlaceholderEntity{ID: 200} b.ResetTimer() for i := 0; i < b.N; i++ { trade := NewTrade(entity1, entity2) tm.AddTrade(trade) tm.GetTrade(100) tm.RemoveTrade(100) } }