eq2go/internal/housing/database_test.go

816 lines
20 KiB
Go

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
}