eq2go/internal/housing/housing_test.go

538 lines
13 KiB
Go

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)
}
}