package housing import ( "context" "testing" "time" ) // MockLogger implements the Logger interface for testing type MockLogger struct{} func (m *MockLogger) LogInfo(system, format string, args ...interface{}) {} func (m *MockLogger) LogError(system, format string, args ...interface{}) {} func (m *MockLogger) LogDebug(system, format string, args ...interface{}) {} func (m *MockLogger) LogWarning(system, format string, args ...interface{}) {} // MockPlayerManager implements the PlayerManager interface for testing type MockPlayerManager struct { CanAfford bool Alignment int8 GuildLevel int8 DeductError error } func (m *MockPlayerManager) CanPlayerAffordHouse(characterID int32, coinCost, statusCost int64) (bool, error) { return m.CanAfford, nil } func (m *MockPlayerManager) DeductPlayerCoins(characterID int32, amount int64) error { return m.DeductError } func (m *MockPlayerManager) DeductPlayerStatus(characterID int32, amount int64) error { return m.DeductError } func (m *MockPlayerManager) GetPlayerAlignment(characterID int32) (int8, error) { return m.Alignment, nil } func (m *MockPlayerManager) GetPlayerGuildLevel(characterID int32) (int8, error) { return m.GuildLevel, nil } func TestNewHousingManager(t *testing.T) { logger := &MockLogger{} config := HousingConfig{ EnableUpkeep: true, EnableForeclosure: true, UpkeepGracePeriod: 3600, MaxHousesPerPlayer: 10, EnableStatistics: true, } // Create housing manager without database for basic test hm := NewHousingManager(nil, logger, config) if hm == nil { t.Fatal("NewHousingManager returned nil") } if hm.logger != logger { t.Error("Logger not set correctly") } if hm.config.MaxHousesPerPlayer != 10 { t.Error("Config not set correctly") } } func TestHouseStructure(t *testing.T) { house := &House{ ID: 1, Name: "Test Cottage", CostCoins: 100000, CostStatus: 0, UpkeepCoins: 10000, UpkeepStatus: 0, VaultSlots: 6, Alignment: AlignmentAny, GuildLevel: 0, ZoneID: 100, ExitZoneID: 1, ExitX: 0.0, ExitY: 0.0, ExitZ: 0.0, ExitHeading: 0.0, } if house.ID != 1 { t.Error("House ID not set correctly") } if house.Name != "Test Cottage" { t.Error("House name not set correctly") } if house.CostCoins != 100000 { t.Error("House cost not set correctly") } } func TestCharacterHouseStructure(t *testing.T) { characterHouse := &CharacterHouse{ UniqueID: 123, CharacterID: 456, HouseID: 1, InstanceID: 789, UpkeepDue: time.Now().Add(7 * 24 * time.Hour), EscrowCoins: 0, EscrowStatus: 0, Status: HouseStatusActive, Settings: CharacterHouseSettings{ HouseName: "My Home", VisitPermission: VisitPermissionFriends, AllowFriends: true, ShowOnDirectory: true, }, AccessList: make(map[int32]HouseAccess), Items: []HouseItem{}, History: []HouseHistory{}, } if characterHouse.UniqueID != 123 { t.Error("CharacterHouse UniqueID not set correctly") } if characterHouse.CharacterID != 456 { t.Error("CharacterHouse CharacterID not set correctly") } if characterHouse.Status != HouseStatusActive { t.Error("CharacterHouse status not set correctly") } if characterHouse.Settings.HouseName != "My Home" { t.Error("CharacterHouse settings not set correctly") } } func TestHousingManagerOperations(t *testing.T) { logger := &MockLogger{} config := HousingConfig{ MaxHousesPerPlayer: 5, } hm := NewHousingManager(nil, logger, config) // Test adding a house type manually (simulating loaded from DB) house := &House{ ID: 1, Name: "Test House", CostCoins: 50000, CostStatus: 0, UpkeepCoins: 5000, UpkeepStatus: 0, VaultSlots: 4, Alignment: AlignmentAny, GuildLevel: 0, } hm.houses[house.ID] = house // Test GetHouse retrievedHouse, exists := hm.GetHouse(1) if !exists { t.Error("GetHouse should find the house") } if retrievedHouse.Name != "Test House" { t.Error("Retrieved house name incorrect") } // Test GetAvailableHouses availableHouses := hm.GetAvailableHouses() if len(availableHouses) != 1 { t.Error("Should have 1 available house") } } func TestPurchaseHouseValidation(t *testing.T) { logger := &MockLogger{} config := HousingConfig{ MaxHousesPerPlayer: 1, } hm := NewHousingManager(nil, logger, config) // Add a test house house := &House{ ID: 1, Name: "Test House", CostCoins: 50000, CostStatus: 100, Alignment: AlignmentGood, GuildLevel: 5, } hm.houses[house.ID] = house playerManager := &MockPlayerManager{ CanAfford: false, Alignment: AlignmentEvil, GuildLevel: 3, } ctx := context.Background() // Test insufficient funds _, err := hm.PurchaseHouse(ctx, 123, 1, playerManager) if err == nil || err.Error() != "insufficient funds" { t.Errorf("Expected insufficient funds error, got: %v", err) } // Test alignment mismatch playerManager.CanAfford = true _, err = hm.PurchaseHouse(ctx, 123, 1, playerManager) if err == nil || err.Error() != "alignment requirement not met" { t.Errorf("Expected alignment error, got: %v", err) } // Test guild level requirement playerManager.Alignment = AlignmentGood _, err = hm.PurchaseHouse(ctx, 123, 1, playerManager) if err == nil || err.Error() != "guild level requirement not met" { t.Errorf("Expected guild level error, got: %v", err) } // Test non-existent house _, err = hm.PurchaseHouse(ctx, 123, 999, playerManager) if err == nil || err.Error() != "house type 999 not found" { t.Errorf("Expected house not found error, got: %v", err) } } func TestPacketBuilding(t *testing.T) { logger := &MockLogger{} config := HousingConfig{} hm := NewHousingManager(nil, logger, config) house := &House{ ID: 1, Name: "Test House", CostCoins: 50000, CostStatus: 0, UpkeepCoins: 5000, UpkeepStatus: 0, VaultSlots: 4, Alignment: AlignmentAny, } // Test packet building (will fail due to missing XML definition, but should not panic) err := hm.SendHousePurchasePacket(123, 564, house) if err == nil { t.Log("Packet building succeeded (XML definitions must be available)") } else { t.Logf("Packet building failed as expected: %v", err) } // Test character houses packet err = hm.SendCharacterHousesPacket(123, 564) if err == nil { t.Log("Character houses packet building succeeded") } else { t.Logf("Character houses packet building failed as expected: %v", err) } } func TestConstants(t *testing.T) { // Test alignment names if AlignmentNames[AlignmentGood] != "Good" { t.Error("AlignmentNames not working correctly") } // Test transaction reasons if TransactionReasons[TransactionPurchase] != "House Purchase" { t.Error("TransactionReasons not working correctly") } // Test house type names if HouseTypeNames[HouseTypeCottage] != "Cottage" { t.Error("HouseTypeNames not working correctly") } // Test default costs if DefaultHouseCosts[HouseTypeCottage] != 200000 { t.Error("DefaultHouseCosts not working correctly") } } func TestHouseHistory(t *testing.T) { history := HouseHistory{ Timestamp: time.Now(), Amount: 50000, Status: 0, Reason: "House Purchase", Name: "TestPlayer", CharacterID: 123, Type: TransactionPurchase, } if history.Amount != 50000 { t.Error("History amount not set correctly") } if history.Type != TransactionPurchase { t.Error("History type not set correctly") } } func TestUpkeepProcessing(t *testing.T) { logger := &MockLogger{} config := HousingConfig{ EnableUpkeep: true, EnableForeclosure: true, UpkeepGracePeriod: 3600, // 1 hour } hm := NewHousingManager(nil, logger, config) // Create a house that's overdue overdueHouse := &CharacterHouse{ UniqueID: 1, CharacterID: 123, HouseID: 1, UpkeepDue: time.Now().Add(-48 * time.Hour), // 2 days overdue Status: HouseStatusActive, AccessList: make(map[int32]HouseAccess), Items: []HouseItem{}, History: []HouseHistory{}, } hm.characterHouses[1] = overdueHouse // Process upkeep ctx := context.Background() err := hm.processUpkeep(ctx) if err != nil { t.Errorf("processUpkeep failed: %v", err) } // Check that house status was updated if overdueHouse.Status != HouseStatusForeclosed { t.Error("House should be foreclosed after grace period") } } func TestPayUpkeep(t *testing.T) { logger := &MockLogger{} config := HousingConfig{ MaxHousesPerPlayer: 5, } hm := NewHousingManager(nil, logger, config) // Add a house type house := &House{ ID: 1, Name: "Test House", UpkeepCoins: 5000, UpkeepStatus: 50, } hm.houses[house.ID] = house // Add a character house characterHouse := &CharacterHouse{ UniqueID: 1, CharacterID: 123, HouseID: 1, UpkeepDue: time.Now().Add(-24 * time.Hour), // Overdue Status: HouseStatusUpkeepDue, AccessList: make(map[int32]HouseAccess), Items: []HouseItem{}, History: []HouseHistory{}, } hm.characterHouses[1] = characterHouse playerManager := &MockPlayerManager{ CanAfford: true, } ctx := context.Background() // Test successful upkeep payment err := hm.PayUpkeep(ctx, 1, playerManager) if err != nil { t.Errorf("PayUpkeep failed: %v", err) } // Check that upkeep due date was updated if characterHouse.UpkeepDue.Before(time.Now()) { t.Error("Upkeep due date should be in the future after payment") } // Check that history was added if len(characterHouse.History) == 0 { t.Error("History should be added after upkeep payment") } if characterHouse.History[0].Type != TransactionUpkeep { t.Error("History should record upkeep transaction") } } func TestFormatCurrency(t *testing.T) { tests := []struct { amount int64 expected string }{ {50, "50c"}, {150, "1s 50c"}, {10000, "1g"}, {15250, "1g 52s 50c"}, {1000000, "100g"}, {-5000, "-50s"}, } for _, test := range tests { result := FormatCurrency(test.amount) if result != test.expected { t.Errorf("FormatCurrency(%d): expected '%s', got '%s'", test.amount, test.expected, result) } } } func TestFormatUpkeepDue(t *testing.T) { now := time.Now() tests := []struct { upkeepDue time.Time expected string }{ {now.Add(-25 * time.Hour), "Overdue (1 days)"}, {now.Add(-1 * time.Hour), "Overdue (today)"}, {now.Add(25 * time.Hour), "Due in 1 days"}, {now.Add(1 * time.Hour), "Due today"}, } for _, test := range tests { result := FormatUpkeepDue(test.upkeepDue) if result != test.expected { t.Errorf("FormatUpkeepDue(%v): expected '%s', got '%s'", test.upkeepDue, test.expected, result) } } } // Test housing system integration func TestHousingSystemIntegration(t *testing.T) { logger := &MockLogger{} config := HousingConfig{ EnableUpkeep: true, EnableForeclosure: false, MaxHousesPerPlayer: 3, } hm := NewHousingManager(nil, logger, config) // Set up test house types houses := []*House{ { ID: 1, Name: "Cottage", CostCoins: 50000, CostStatus: 0, UpkeepCoins: 5000, UpkeepStatus: 0, Alignment: AlignmentAny, GuildLevel: 0, }, { ID: 2, Name: "Mansion", CostCoins: 500000, CostStatus: 1000, UpkeepCoins: 25000, UpkeepStatus: 100, Alignment: AlignmentGood, GuildLevel: 10, }, } for _, house := range houses { hm.houses[house.ID] = house } // Test player manager setup playerManager := &MockPlayerManager{ CanAfford: true, Alignment: AlignmentGood, GuildLevel: 15, } ctx := context.Background() // Test purchasing multiple houses characterID := int32(12345) // Purchase cottage cottage, err := hm.PurchaseHouse(ctx, characterID, 1, playerManager) if err != nil { t.Errorf("Failed to purchase cottage: %v", err) } if cottage == nil { t.Fatal("Cottage purchase returned nil") } // Purchase mansion mansion, err := hm.PurchaseHouse(ctx, characterID, 2, playerManager) if err != nil { t.Errorf("Failed to purchase mansion: %v", err) } if mansion == nil { t.Fatal("Mansion purchase returned nil") } // Check that houses were added to character index characterHouses, err := hm.GetCharacterHouses(characterID) if err != nil { t.Errorf("Failed to get character houses: %v", err) } if len(characterHouses) != 2 { t.Errorf("Expected 2 character houses, got %d", len(characterHouses)) } // Test house limits playerManager.CanAfford = true _, err = hm.PurchaseHouse(ctx, characterID, 1, playerManager) // Try to buy another cottage if err != nil { t.Logf("House purchase blocked as expected (would exceed limit): %v", err) } // Test upkeep processing err = hm.processUpkeep(ctx) if err != nil { t.Errorf("Upkeep processing failed: %v", err) } // Test packet building for multiple houses err = hm.SendCharacterHousesPacket(characterID, 564) if err == nil { t.Log("Character houses packet built successfully") } else { t.Logf("Character houses packet building failed as expected: %v", err) } }