package housing import ( "context" "fmt" "testing" "time" "zombiezen.com/go/sqlite/sqlitex" ) // createTestPool creates an in-memory SQLite database pool for testing func createTestPool(t *testing.T) *sqlitex.Pool { dbName := fmt.Sprintf("file:test_%s.db?mode=memory&cache=shared", t.Name()) pool, err := sqlitex.NewPool(dbName, sqlitex.PoolOptions{}) if err != nil { t.Fatalf("Failed to create test database pool: %v", err) } return pool } // setupTestDB creates test tables and returns a DatabaseHousingManager func setupTestDB(t *testing.T) *DatabaseHousingManager { pool := createTestPool(t) dhm := NewDatabaseHousingManager(pool) ctx := context.Background() if err := dhm.EnsureHousingTables(ctx); err != nil { t.Fatalf("Failed to create test tables: %v", err) } return dhm } // insertTestData inserts sample data for testing func insertTestData(t *testing.T, dhm *DatabaseHousingManager) { ctx := context.Background() // Insert test house zones testZones := []*HouseZone{ { ID: 1, Name: "Small Studio", ZoneID: 100, CostCoin: 50000, CostStatus: 1000, UpkeepCoin: 5000, UpkeepStatus: 100, Alignment: 0, // Neutral GuildLevel: 0, VaultSlots: 4, MaxItems: 100, MaxVisitors: 10, UpkeepPeriod: 604800, // 1 week Description: "A cozy small studio apartment", }, { ID: 2, Name: "Large House", ZoneID: 101, CostCoin: 500000, CostStatus: 10000, UpkeepCoin: 50000, UpkeepStatus: 1000, Alignment: 1, // Good GuildLevel: 20, VaultSlots: 8, MaxItems: 500, MaxVisitors: 50, UpkeepPeriod: 604800, Description: "A spacious large house", }, } for _, zone := range testZones { if err := dhm.SaveHouseZone(ctx, zone); err != nil { t.Fatalf("Failed to insert test house zone: %v", err) } } // Insert test player houses testPlayerHouses := []PlayerHouseData{ { CharacterID: 1001, HouseID: 1, InstanceID: 5001, UpkeepDue: time.Now().Add(24 * time.Hour), EscrowCoins: 25000, EscrowStatus: 500, Status: HouseStatusActive, HouseName: "Alice's Studio", VisitPermission: 1, PublicNote: "Welcome to my home!", PrivateNote: "Remember to water plants", AllowFriends: true, AllowGuild: false, RequireApproval: false, ShowOnDirectory: true, AllowDecoration: true, TaxExempt: false, }, { CharacterID: 1002, HouseID: 2, InstanceID: 5002, UpkeepDue: time.Now().Add(48 * time.Hour), EscrowCoins: 100000, EscrowStatus: 2000, Status: HouseStatusActive, HouseName: "Bob's Manor", VisitPermission: 2, PublicNote: "Guild meetings welcome", PrivateNote: "Check security settings", AllowFriends: true, AllowGuild: true, RequireApproval: true, ShowOnDirectory: true, AllowDecoration: false, TaxExempt: true, }, } for _, house := range testPlayerHouses { _, err := dhm.AddPlayerHouse(ctx, house) if err != nil { t.Fatalf("Failed to insert test player house: %v", err) } } } func TestNewDatabaseHousingManager(t *testing.T) { pool := createTestPool(t) dhm := NewDatabaseHousingManager(pool) if dhm == nil { t.Fatal("NewDatabaseHousingManager returned nil") } if dhm.pool != pool { t.Error("Database pool not set correctly") } } func TestEnsureHousingTables(t *testing.T) { dhm := setupTestDB(t) ctx := context.Background() // Test that tables were created (this should not error on second call) if err := dhm.EnsureHousingTables(ctx); err != nil { t.Errorf("EnsureHousingTables failed on second call: %v", err) } } func TestHouseZoneOperations(t *testing.T) { dhm := setupTestDB(t) ctx := context.Background() // Test SaveHouseZone and LoadHouseZone testZone := &HouseZone{ ID: 100, Name: "Test House", ZoneID: 200, CostCoin: 100000, CostStatus: 2000, UpkeepCoin: 10000, UpkeepStatus: 200, Alignment: -1, // Evil GuildLevel: 10, VaultSlots: 6, MaxItems: 250, MaxVisitors: 25, UpkeepPeriod: 1209600, // 2 weeks Description: "A test house for unit testing", } // Save house zone if err := dhm.SaveHouseZone(ctx, testZone); err != nil { t.Fatalf("SaveHouseZone failed: %v", err) } // Load house zone loadedZone, err := dhm.LoadHouseZone(ctx, testZone.ID) if err != nil { t.Fatalf("LoadHouseZone failed: %v", err) } // Verify loaded data if loadedZone.ID != testZone.ID { t.Errorf("Expected ID %d, got %d", testZone.ID, loadedZone.ID) } if loadedZone.Name != testZone.Name { t.Errorf("Expected Name %s, got %s", testZone.Name, loadedZone.Name) } if loadedZone.Description != testZone.Description { t.Errorf("Expected Description %s, got %s", testZone.Description, loadedZone.Description) } // Test LoadHouseZones zones, err := dhm.LoadHouseZones(ctx) if err != nil { t.Fatalf("LoadHouseZones failed: %v", err) } if len(zones) != 1 { t.Errorf("Expected 1 zone, got %d", len(zones)) } // Test DeleteHouseZone if err := dhm.DeleteHouseZone(ctx, testZone.ID); err != nil { t.Fatalf("DeleteHouseZone failed: %v", err) } // Verify deletion _, err = dhm.LoadHouseZone(ctx, testZone.ID) if err == nil { t.Error("Expected error when loading deleted house zone, got nil") } } func TestPlayerHouseOperations(t *testing.T) { dhm := setupTestDB(t) insertTestData(t, dhm) ctx := context.Background() // Test LoadPlayerHouses houses, err := dhm.LoadPlayerHouses(ctx, 1001) if err != nil { t.Fatalf("LoadPlayerHouses failed: %v", err) } if len(houses) != 1 { t.Errorf("Expected 1 house for character 1001, got %d", len(houses)) } if houses[0].HouseName != "Alice's Studio" { t.Errorf("Expected house name 'Alice's Studio', got %s", houses[0].HouseName) } // Test LoadPlayerHouse by unique ID house, err := dhm.LoadPlayerHouse(ctx, houses[0].UniqueID) if err != nil { t.Fatalf("LoadPlayerHouse failed: %v", err) } if house.CharacterID != 1001 { t.Errorf("Expected character ID 1001, got %d", house.CharacterID) } // Test GetHouseByInstance houseByInstance, err := dhm.GetHouseByInstance(ctx, 5001) if err != nil { t.Fatalf("GetHouseByInstance failed: %v", err) } if houseByInstance.CharacterID != 1001 { t.Errorf("Expected character ID 1001, got %d", houseByInstance.CharacterID) } // Test GetNextHouseID nextID, err := dhm.GetNextHouseID(ctx) if err != nil { t.Fatalf("GetNextHouseID failed: %v", err) } if nextID <= houses[0].UniqueID { t.Errorf("Expected next ID > %d, got %d", houses[0].UniqueID, nextID) } } func TestPlayerHouseUpdates(t *testing.T) { dhm := setupTestDB(t) insertTestData(t, dhm) ctx := context.Background() // Get a test house houses, err := dhm.LoadPlayerHouses(ctx, 1001) if err != nil || len(houses) == 0 { t.Fatalf("Failed to load test house: %v", err) } houseID := houses[0].UniqueID // Test UpdateHouseUpkeepDue newUpkeepDue := time.Now().Add(72 * time.Hour) if err := dhm.UpdateHouseUpkeepDue(ctx, houseID, newUpkeepDue); err != nil { t.Fatalf("UpdateHouseUpkeepDue failed: %v", err) } // Verify update updatedHouse, err := dhm.LoadPlayerHouse(ctx, houseID) if err != nil { t.Fatalf("Failed to load updated house: %v", err) } if updatedHouse.UpkeepDue.Unix() != newUpkeepDue.Unix() { t.Errorf("Expected upkeep due %v, got %v", newUpkeepDue, updatedHouse.UpkeepDue) } // Test UpdateHouseEscrow if err := dhm.UpdateHouseEscrow(ctx, houseID, 50000, 1000); err != nil { t.Fatalf("UpdateHouseEscrow failed: %v", err) } // Verify escrow update updatedHouse, err = dhm.LoadPlayerHouse(ctx, houseID) if err != nil { t.Fatalf("Failed to load updated house: %v", err) } if updatedHouse.EscrowCoins != 50000 { t.Errorf("Expected escrow coins 50000, got %d", updatedHouse.EscrowCoins) } if updatedHouse.EscrowStatus != 1000 { t.Errorf("Expected escrow status 1000, got %d", updatedHouse.EscrowStatus) } } func TestHouseDeposits(t *testing.T) { dhm := setupTestDB(t) insertTestData(t, dhm) ctx := context.Background() // Get a test house houses, err := dhm.LoadPlayerHouses(ctx, 1001) if err != nil || len(houses) == 0 { t.Fatalf("Failed to load test house: %v", err) } houseID := houses[0].UniqueID // Test SaveDeposit testDeposit := HouseDeposit{ Timestamp: time.Now(), Amount: 10000, LastAmount: 15000, Status: 200, LastStatus: 300, Name: "Alice", CharacterID: 1001, } if err := dhm.SaveDeposit(ctx, houseID, testDeposit); err != nil { t.Fatalf("SaveDeposit failed: %v", err) } // Test LoadDeposits deposits, err := dhm.LoadDeposits(ctx, houseID) if err != nil { t.Fatalf("LoadDeposits failed: %v", err) } if len(deposits) != 1 { t.Errorf("Expected 1 deposit, got %d", len(deposits)) } if deposits[0].Amount != testDeposit.Amount { t.Errorf("Expected deposit amount %d, got %d", testDeposit.Amount, deposits[0].Amount) } if deposits[0].Name != testDeposit.Name { t.Errorf("Expected deposit name %s, got %s", testDeposit.Name, deposits[0].Name) } } func TestHouseHistory(t *testing.T) { dhm := setupTestDB(t) insertTestData(t, dhm) ctx := context.Background() // Get a test house houses, err := dhm.LoadPlayerHouses(ctx, 1001) if err != nil || len(houses) == 0 { t.Fatalf("Failed to load test house: %v", err) } houseID := houses[0].UniqueID // Test AddHistory testHistory := HouseHistory{ Timestamp: time.Now(), Amount: 5000, Status: 100, Reason: "Weekly upkeep", Name: "System", CharacterID: 0, // System transaction PosFlag: 0, // Withdrawal Type: 1, // Upkeep } if err := dhm.AddHistory(ctx, houseID, testHistory); err != nil { t.Fatalf("AddHistory failed: %v", err) } // Test LoadHistory history, err := dhm.LoadHistory(ctx, houseID) if err != nil { t.Fatalf("LoadHistory failed: %v", err) } if len(history) != 1 { t.Errorf("Expected 1 history entry, got %d", len(history)) } if history[0].Reason != testHistory.Reason { t.Errorf("Expected history reason %s, got %s", testHistory.Reason, history[0].Reason) } if history[0].Amount != testHistory.Amount { t.Errorf("Expected history amount %d, got %d", testHistory.Amount, history[0].Amount) } } func TestHouseAccess(t *testing.T) { dhm := setupTestDB(t) insertTestData(t, dhm) ctx := context.Background() // Get a test house houses, err := dhm.LoadPlayerHouses(ctx, 1001) if err != nil || len(houses) == 0 { t.Fatalf("Failed to load test house: %v", err) } houseID := houses[0].UniqueID // Test SaveHouseAccess testAccess := []HouseAccess{ { CharacterID: 2001, PlayerName: "Bob", AccessLevel: 1, Permissions: 15, // Full permissions GrantedBy: 1001, GrantedDate: time.Now(), ExpiresDate: time.Now().Add(30 * 24 * time.Hour), Notes: "Trusted friend", }, { CharacterID: 2002, PlayerName: "Charlie", AccessLevel: 2, Permissions: 7, // Limited permissions GrantedBy: 1001, GrantedDate: time.Now(), ExpiresDate: time.Now().Add(7 * 24 * time.Hour), Notes: "Temporary access", }, } if err := dhm.SaveHouseAccess(ctx, houseID, testAccess); err != nil { t.Fatalf("SaveHouseAccess failed: %v", err) } // Test LoadHouseAccess accessList, err := dhm.LoadHouseAccess(ctx, houseID) if err != nil { t.Fatalf("LoadHouseAccess failed: %v", err) } if len(accessList) != 2 { t.Errorf("Expected 2 access entries, got %d", len(accessList)) } // Test DeleteHouseAccess if err := dhm.DeleteHouseAccess(ctx, houseID, 2002); err != nil { t.Fatalf("DeleteHouseAccess failed: %v", err) } // Verify deletion accessList, err = dhm.LoadHouseAccess(ctx, houseID) if err != nil { t.Fatalf("LoadHouseAccess after deletion failed: %v", err) } if len(accessList) != 1 { t.Errorf("Expected 1 access entry after deletion, got %d", len(accessList)) } if accessList[0].CharacterID != 2001 { t.Errorf("Expected remaining access for character 2001, got %d", accessList[0].CharacterID) } } func TestHouseAmenities(t *testing.T) { dhm := setupTestDB(t) insertTestData(t, dhm) ctx := context.Background() // Get a test house houses, err := dhm.LoadPlayerHouses(ctx, 1001) if err != nil || len(houses) == 0 { t.Fatalf("Failed to load test house: %v", err) } houseID := houses[0].UniqueID // Test SaveHouseAmenity testAmenity := HouseAmenity{ ID: 1, Type: 1, // Furniture Name: "Comfortable Chair", Cost: 1000, StatusCost: 20, PurchaseDate: time.Now(), X: 100.5, Y: 200.0, Z: 50.25, Heading: 180.0, IsActive: true, } if err := dhm.SaveHouseAmenity(ctx, houseID, testAmenity); err != nil { t.Fatalf("SaveHouseAmenity failed: %v", err) } // Test LoadHouseAmenities amenities, err := dhm.LoadHouseAmenities(ctx, houseID) if err != nil { t.Fatalf("LoadHouseAmenities failed: %v", err) } if len(amenities) != 1 { t.Errorf("Expected 1 amenity, got %d", len(amenities)) } if amenities[0].Name != testAmenity.Name { t.Errorf("Expected amenity name %s, got %s", testAmenity.Name, amenities[0].Name) } if amenities[0].X != testAmenity.X { t.Errorf("Expected X position %f, got %f", testAmenity.X, amenities[0].X) } // Test DeleteHouseAmenity if err := dhm.DeleteHouseAmenity(ctx, houseID, testAmenity.ID); err != nil { t.Fatalf("DeleteHouseAmenity failed: %v", err) } // Verify deletion amenities, err = dhm.LoadHouseAmenities(ctx, houseID) if err != nil { t.Fatalf("LoadHouseAmenities after deletion failed: %v", err) } if len(amenities) != 0 { t.Errorf("Expected 0 amenities after deletion, got %d", len(amenities)) } } func TestHouseItems(t *testing.T) { dhm := setupTestDB(t) insertTestData(t, dhm) ctx := context.Background() // Get a test house houses, err := dhm.LoadPlayerHouses(ctx, 1001) if err != nil || len(houses) == 0 { t.Fatalf("Failed to load test house: %v", err) } houseID := houses[0].UniqueID // Test SaveHouseItem testItem := HouseItem{ ID: 1, ItemID: 12345, CharacterID: 1001, X: 150.0, Y: 250.0, Z: 75.5, Heading: 90.0, PitchX: 5.0, PitchY: 0.0, RollX: 0.0, RollY: 2.5, PlacedDate: time.Now(), Quantity: 1, Condition: 100, House: "main", } if err := dhm.SaveHouseItem(ctx, houseID, testItem); err != nil { t.Fatalf("SaveHouseItem failed: %v", err) } // Test LoadHouseItems items, err := dhm.LoadHouseItems(ctx, houseID) if err != nil { t.Fatalf("LoadHouseItems failed: %v", err) } if len(items) != 1 { t.Errorf("Expected 1 item, got %d", len(items)) } if items[0].ItemID != testItem.ItemID { t.Errorf("Expected item ID %d, got %d", testItem.ItemID, items[0].ItemID) } if items[0].House != testItem.House { t.Errorf("Expected house %s, got %s", testItem.House, items[0].House) } // Test DeleteHouseItem if err := dhm.DeleteHouseItem(ctx, houseID, testItem.ID); err != nil { t.Fatalf("DeleteHouseItem failed: %v", err) } // Verify deletion items, err = dhm.LoadHouseItems(ctx, houseID) if err != nil { t.Fatalf("LoadHouseItems after deletion failed: %v", err) } if len(items) != 0 { t.Errorf("Expected 0 items after deletion, got %d", len(items)) } } func TestGetHousesForUpkeep(t *testing.T) { dhm := setupTestDB(t) insertTestData(t, dhm) ctx := context.Background() // Test with cutoff time in the future (should find houses) cutoffTime := time.Now().Add(72 * time.Hour) houses, err := dhm.GetHousesForUpkeep(ctx, cutoffTime) if err != nil { t.Fatalf("GetHousesForUpkeep failed: %v", err) } if len(houses) != 2 { t.Errorf("Expected 2 houses for upkeep, got %d", len(houses)) } // Test with cutoff time in the past (should find no houses) cutoffTime = time.Now().Add(-24 * time.Hour) houses, err = dhm.GetHousesForUpkeep(ctx, cutoffTime) if err != nil { t.Fatalf("GetHousesForUpkeep with past cutoff failed: %v", err) } if len(houses) != 0 { t.Errorf("Expected 0 houses for past upkeep, got %d", len(houses)) } } func TestGetHouseStatistics(t *testing.T) { dhm := setupTestDB(t) insertTestData(t, dhm) ctx := context.Background() // Add some test data for statistics houses, err := dhm.LoadPlayerHouses(ctx, 1001) if err != nil || len(houses) == 0 { t.Fatalf("Failed to load test house: %v", err) } houseID := houses[0].UniqueID // Add some deposits and history for stats testDeposit := HouseDeposit{ Timestamp: time.Now(), Amount: 5000, LastAmount: 10000, Status: 100, LastStatus: 200, Name: "Alice", CharacterID: 1001, } if err := dhm.SaveDeposit(ctx, houseID, testDeposit); err != nil { t.Fatalf("Failed to save test deposit: %v", err) } testHistory := HouseHistory{ Timestamp: time.Now(), Amount: 2000, Status: 50, Reason: "Withdrawal", Name: "Alice", CharacterID: 1001, PosFlag: 0, // Withdrawal Type: 2, } if err := dhm.AddHistory(ctx, houseID, testHistory); err != nil { t.Fatalf("Failed to add test history: %v", err) } // Test GetHouseStatistics stats, err := dhm.GetHouseStatistics(ctx) if err != nil { t.Fatalf("GetHouseStatistics failed: %v", err) } if stats.TotalHouses != 2 { t.Errorf("Expected 2 total houses, got %d", stats.TotalHouses) } if stats.ActiveHouses != 2 { t.Errorf("Expected 2 active houses, got %d", stats.ActiveHouses) } if stats.TotalDeposits != 1 { t.Errorf("Expected 1 total deposits, got %d", stats.TotalDeposits) } if stats.TotalWithdrawals != 1 { t.Errorf("Expected 1 total withdrawals, got %d", stats.TotalWithdrawals) } } func TestDeletePlayerHouse(t *testing.T) { dhm := setupTestDB(t) insertTestData(t, dhm) ctx := context.Background() // Get a test house houses, err := dhm.LoadPlayerHouses(ctx, 1001) if err != nil || len(houses) == 0 { t.Fatalf("Failed to load test house: %v", err) } houseID := houses[0].UniqueID // Add some related data that should be cascade deleted testDeposit := HouseDeposit{ Timestamp: time.Now(), Amount: 5000, LastAmount: 10000, Status: 100, LastStatus: 200, Name: "Alice", CharacterID: 1001, } if err := dhm.SaveDeposit(ctx, houseID, testDeposit); err != nil { t.Fatalf("Failed to save test deposit: %v", err) } // Test DeletePlayerHouse if err := dhm.DeletePlayerHouse(ctx, houseID); err != nil { t.Fatalf("DeletePlayerHouse failed: %v", err) } // Verify deletion _, err = dhm.LoadPlayerHouse(ctx, houseID) if err == nil { t.Error("Expected error when loading deleted player house, got nil") } // Verify related data was also deleted deposits, err := dhm.LoadDeposits(ctx, houseID) if err != nil { t.Fatalf("LoadDeposits after house deletion failed: %v", err) } if len(deposits) != 0 { t.Errorf("Expected 0 deposits after house deletion, got %d", len(deposits)) } // Verify other houses are still there remainingHouses, err := dhm.LoadPlayerHouses(ctx, 1002) if err != nil { t.Fatalf("Failed to load remaining houses: %v", err) } if len(remainingHouses) != 1 { t.Errorf("Expected 1 remaining house, got %d", len(remainingHouses)) } } func TestErrorCases(t *testing.T) { dhm := setupTestDB(t) ctx := context.Background() // Test loading non-existent house zone _, err := dhm.LoadHouseZone(ctx, 999) if err == nil { t.Error("Expected error when loading non-existent house zone, got nil") } // Test loading non-existent player house _, err = dhm.LoadPlayerHouse(ctx, 999) if err == nil { t.Error("Expected error when loading non-existent player house, got nil") } // Test loading house by non-existent instance _, err = dhm.GetHouseByInstance(ctx, 999) if err == nil { t.Error("Expected error when loading house by non-existent instance, got nil") } // Test operations on non-existent house nonExistentHouseID := int64(999) deposits, err := dhm.LoadDeposits(ctx, nonExistentHouseID) if err != nil { t.Errorf("LoadDeposits should not error on non-existent house: %v", err) } if len(deposits) != 0 { t.Errorf("Expected 0 deposits for non-existent house, got %d", len(deposits)) } history, err := dhm.LoadHistory(ctx, nonExistentHouseID) if err != nil { t.Errorf("LoadHistory should not error on non-existent house: %v", err) } if len(history) != 0 { t.Errorf("Expected 0 history entries for non-existent house, got %d", len(history)) } } // Helper function to compare times with tolerance for database precision func timesEqual(t1, t2 time.Time, tolerance time.Duration) bool { diff := t1.Sub(t2) if diff < 0 { diff = -diff } return diff <= tolerance }