538 lines
13 KiB
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)
|
|
}
|
|
} |