Compare commits
3 Commits
966501670f
...
a3a10406d5
Author | SHA1 | Date | |
---|---|---|---|
a3a10406d5 | |||
aa362bde07 | |||
1c26bb600d |
@ -1,25 +1,34 @@
|
||||
package ground_spawn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"eq2emu/internal/database"
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
)
|
||||
|
||||
// DatabaseAdapter implements the Database interface using the internal database wrapper
|
||||
// DatabaseAdapter implements the Database interface using zombiezen.com/go/sqlite
|
||||
type DatabaseAdapter struct {
|
||||
db *database.DB
|
||||
pool *sqlitex.Pool
|
||||
}
|
||||
|
||||
// NewDatabaseAdapter creates a new database adapter using the database wrapper
|
||||
func NewDatabaseAdapter(db *database.DB) *DatabaseAdapter {
|
||||
// NewDatabaseAdapter creates a new database adapter using the sqlite pool
|
||||
func NewDatabaseAdapter(pool *sqlitex.Pool) *DatabaseAdapter {
|
||||
return &DatabaseAdapter{
|
||||
db: db,
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadGroundSpawnEntries loads harvest entries for a ground spawn
|
||||
func (da *DatabaseAdapter) LoadGroundSpawnEntries(groundspawnID int32) ([]*GroundSpawnEntry, error) {
|
||||
ctx := context.Background()
|
||||
conn, err := da.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer da.pool.Put(conn)
|
||||
|
||||
query := `
|
||||
SELECT min_skill_level, min_adventure_level, bonus_table, harvest_1, harvest_3,
|
||||
harvest_5, harvest_imbue, harvest_rare, harvest_10, harvest_coin
|
||||
@ -29,27 +38,25 @@ func (da *DatabaseAdapter) LoadGroundSpawnEntries(groundspawnID int32) ([]*Groun
|
||||
`
|
||||
|
||||
var entries []*GroundSpawnEntry
|
||||
|
||||
err := da.db.Query(query, func(row *database.Row) error {
|
||||
entry := &GroundSpawnEntry{}
|
||||
var bonusTable int32
|
||||
|
||||
entry.MinSkillLevel = int16(row.Int(0))
|
||||
entry.MinAdventureLevel = int16(row.Int(1))
|
||||
bonusTable = int32(row.Int(2))
|
||||
entry.Harvest1 = float32(row.Float(3))
|
||||
entry.Harvest3 = float32(row.Float(4))
|
||||
entry.Harvest5 = float32(row.Float(5))
|
||||
entry.HarvestImbue = float32(row.Float(6))
|
||||
entry.HarvestRare = float32(row.Float(7))
|
||||
entry.Harvest10 = float32(row.Float(8))
|
||||
entry.HarvestCoin = float32(row.Float(9))
|
||||
|
||||
entry.BonusTable = bonusTable == 1
|
||||
entries = append(entries, entry)
|
||||
|
||||
return nil
|
||||
}, groundspawnID)
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{groundspawnID},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
entry := &GroundSpawnEntry{
|
||||
MinSkillLevel: int16(stmt.ColumnInt64(0)),
|
||||
MinAdventureLevel: int16(stmt.ColumnInt64(1)),
|
||||
BonusTable: stmt.ColumnInt64(2) == 1,
|
||||
Harvest1: float32(stmt.ColumnFloat(3)),
|
||||
Harvest3: float32(stmt.ColumnFloat(4)),
|
||||
Harvest5: float32(stmt.ColumnFloat(5)),
|
||||
HarvestImbue: float32(stmt.ColumnFloat(6)),
|
||||
HarvestRare: float32(stmt.ColumnFloat(7)),
|
||||
Harvest10: float32(stmt.ColumnFloat(8)),
|
||||
HarvestCoin: float32(stmt.ColumnFloat(9)),
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query groundspawn entries: %w", err)
|
||||
@ -60,6 +67,13 @@ func (da *DatabaseAdapter) LoadGroundSpawnEntries(groundspawnID int32) ([]*Groun
|
||||
|
||||
// LoadGroundSpawnItems loads harvest items for a ground spawn
|
||||
func (da *DatabaseAdapter) LoadGroundSpawnItems(groundspawnID int32) ([]*GroundSpawnEntryItem, error) {
|
||||
ctx := context.Background()
|
||||
conn, err := da.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer da.pool.Put(conn)
|
||||
|
||||
query := `
|
||||
SELECT item_id, is_rare, grid_id, quantity
|
||||
FROM groundspawn_items
|
||||
@ -68,19 +82,19 @@ func (da *DatabaseAdapter) LoadGroundSpawnItems(groundspawnID int32) ([]*GroundS
|
||||
`
|
||||
|
||||
var items []*GroundSpawnEntryItem
|
||||
|
||||
err := da.db.Query(query, func(row *database.Row) error {
|
||||
item := &GroundSpawnEntryItem{}
|
||||
|
||||
item.ItemID = int32(row.Int(0))
|
||||
item.IsRare = int8(row.Int(1))
|
||||
item.GridID = int32(row.Int(2))
|
||||
item.Quantity = int16(row.Int(3))
|
||||
|
||||
items = append(items, item)
|
||||
|
||||
return nil
|
||||
}, groundspawnID)
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{groundspawnID},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
item := &GroundSpawnEntryItem{
|
||||
ItemID: int32(stmt.ColumnInt64(0)),
|
||||
IsRare: int8(stmt.ColumnInt64(1)),
|
||||
GridID: int32(stmt.ColumnInt64(2)),
|
||||
Quantity: int16(stmt.ColumnInt64(3)),
|
||||
}
|
||||
items = append(items, item)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query groundspawn items: %w", err)
|
||||
@ -95,6 +109,13 @@ func (da *DatabaseAdapter) SaveGroundSpawn(gs *GroundSpawn) error {
|
||||
return fmt.Errorf("ground spawn cannot be nil")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
conn, err := da.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer da.pool.Put(conn)
|
||||
|
||||
query := `
|
||||
INSERT OR REPLACE INTO groundspawns (
|
||||
id, name, x, y, z, heading, respawn_timer, collection_skill,
|
||||
@ -112,21 +133,23 @@ func (da *DatabaseAdapter) SaveGroundSpawn(gs *GroundSpawn) error {
|
||||
// TODO: Get actual zone ID from spawn
|
||||
zoneID := int32(1)
|
||||
|
||||
err := da.db.Exec(query,
|
||||
gs.GetID(),
|
||||
gs.GetName(),
|
||||
gs.GetX(),
|
||||
gs.GetY(),
|
||||
gs.GetZ(),
|
||||
int16(gs.GetHeading()),
|
||||
300, // Default 5 minutes respawn timer
|
||||
gs.GetCollectionSkill(),
|
||||
gs.GetNumberHarvests(),
|
||||
gs.GetAttemptsPerHarvest(),
|
||||
gs.GetGroundSpawnEntryID(),
|
||||
randomizeHeading,
|
||||
zoneID,
|
||||
)
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{
|
||||
gs.GetID(),
|
||||
gs.GetName(),
|
||||
gs.GetX(),
|
||||
gs.GetY(),
|
||||
gs.GetZ(),
|
||||
int16(gs.GetHeading()),
|
||||
300, // Default 5 minutes respawn timer
|
||||
gs.GetCollectionSkill(),
|
||||
gs.GetNumberHarvests(),
|
||||
gs.GetAttemptsPerHarvest(),
|
||||
gs.GetGroundSpawnEntryID(),
|
||||
randomizeHeading,
|
||||
zoneID,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save ground spawn %d: %w", gs.GetID(), err)
|
||||
@ -137,6 +160,13 @@ func (da *DatabaseAdapter) SaveGroundSpawn(gs *GroundSpawn) error {
|
||||
|
||||
// LoadAllGroundSpawns loads all ground spawns from the database
|
||||
func (da *DatabaseAdapter) LoadAllGroundSpawns() ([]*GroundSpawn, error) {
|
||||
ctx := context.Background()
|
||||
conn, err := da.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer da.pool.Put(conn)
|
||||
|
||||
query := `
|
||||
SELECT id, name, x, y, z, heading, collection_skill, number_harvests,
|
||||
attempts_per_harvest, groundspawn_entry_id, randomize_heading
|
||||
@ -149,44 +179,45 @@ func (da *DatabaseAdapter) LoadAllGroundSpawns() ([]*GroundSpawn, error) {
|
||||
zoneID := int32(1)
|
||||
|
||||
var groundSpawns []*GroundSpawn
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{zoneID},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
id := int32(stmt.ColumnInt64(0))
|
||||
name := stmt.ColumnText(1)
|
||||
x := float32(stmt.ColumnFloat(2))
|
||||
y := float32(stmt.ColumnFloat(3))
|
||||
z := float32(stmt.ColumnFloat(4))
|
||||
heading := float32(stmt.ColumnFloat(5))
|
||||
collectionSkill := stmt.ColumnText(6)
|
||||
numberHarvests := int8(stmt.ColumnInt64(7))
|
||||
attemptsPerHarvest := int8(stmt.ColumnInt64(8))
|
||||
groundspawnEntryID := int32(stmt.ColumnInt64(9))
|
||||
randomizeHeading := stmt.ColumnInt64(10) == 1
|
||||
|
||||
err := da.db.Query(query, func(row *database.Row) error {
|
||||
id := int32(row.Int(0))
|
||||
name := row.Text(1)
|
||||
x := float32(row.Float(2))
|
||||
y := float32(row.Float(3))
|
||||
z := float32(row.Float(4))
|
||||
heading := float32(row.Float(5))
|
||||
collectionSkill := row.Text(6)
|
||||
numberHarvests := int8(row.Int(7))
|
||||
attemptsPerHarvest := int8(row.Int(8))
|
||||
groundspawnEntryID := int32(row.Int(9))
|
||||
randomizeHeading := int32(row.Int(10))
|
||||
config := GroundSpawnConfig{
|
||||
GroundSpawnID: groundspawnEntryID,
|
||||
CollectionSkill: collectionSkill,
|
||||
NumberHarvests: numberHarvests,
|
||||
AttemptsPerHarvest: attemptsPerHarvest,
|
||||
RandomizeHeading: randomizeHeading,
|
||||
Location: SpawnLocation{
|
||||
X: x,
|
||||
Y: y,
|
||||
Z: z,
|
||||
Heading: heading,
|
||||
GridID: 1, // TODO: Load from database
|
||||
},
|
||||
Name: name,
|
||||
Description: fmt.Sprintf("A %s node", collectionSkill),
|
||||
}
|
||||
|
||||
config := GroundSpawnConfig{
|
||||
GroundSpawnID: groundspawnEntryID,
|
||||
CollectionSkill: collectionSkill,
|
||||
NumberHarvests: numberHarvests,
|
||||
AttemptsPerHarvest: attemptsPerHarvest,
|
||||
RandomizeHeading: randomizeHeading == 1,
|
||||
Location: SpawnLocation{
|
||||
X: x,
|
||||
Y: y,
|
||||
Z: z,
|
||||
Heading: heading,
|
||||
GridID: 1, // TODO: Load from database
|
||||
},
|
||||
Name: name,
|
||||
Description: fmt.Sprintf("A %s node", collectionSkill),
|
||||
}
|
||||
gs := NewGroundSpawn(config)
|
||||
gs.SetID(id)
|
||||
|
||||
gs := NewGroundSpawn(config)
|
||||
gs.SetID(id)
|
||||
|
||||
groundSpawns = append(groundSpawns, gs)
|
||||
|
||||
return nil
|
||||
}, zoneID)
|
||||
groundSpawns = append(groundSpawns, gs)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query groundspawns: %w", err)
|
||||
@ -197,9 +228,18 @@ func (da *DatabaseAdapter) LoadAllGroundSpawns() ([]*GroundSpawn, error) {
|
||||
|
||||
// DeleteGroundSpawn deletes a ground spawn from the database
|
||||
func (da *DatabaseAdapter) DeleteGroundSpawn(id int32) error {
|
||||
ctx := context.Background()
|
||||
conn, err := da.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer da.pool.Put(conn)
|
||||
|
||||
query := `DELETE FROM groundspawns WHERE id = ?`
|
||||
|
||||
err := da.db.Exec(query, id)
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{id},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete ground spawn %d: %w", id, err)
|
||||
}
|
||||
@ -209,6 +249,13 @@ func (da *DatabaseAdapter) DeleteGroundSpawn(id int32) error {
|
||||
|
||||
// LoadPlayerHarvestStatistics loads harvest statistics for a player
|
||||
func (da *DatabaseAdapter) LoadPlayerHarvestStatistics(playerID int32) (map[string]int64, error) {
|
||||
ctx := context.Background()
|
||||
conn, err := da.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer da.pool.Put(conn)
|
||||
|
||||
query := `
|
||||
SELECT skill_name, harvest_count
|
||||
FROM player_harvest_stats
|
||||
@ -216,15 +263,15 @@ func (da *DatabaseAdapter) LoadPlayerHarvestStatistics(playerID int32) (map[stri
|
||||
`
|
||||
|
||||
stats := make(map[string]int64)
|
||||
|
||||
err := da.db.Query(query, func(row *database.Row) error {
|
||||
skillName := row.Text(0)
|
||||
harvestCount := row.Int64(1)
|
||||
|
||||
stats[skillName] = harvestCount
|
||||
|
||||
return nil
|
||||
}, playerID)
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{playerID},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
skillName := stmt.ColumnText(0)
|
||||
harvestCount := stmt.ColumnInt64(1)
|
||||
stats[skillName] = harvestCount
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query player harvest stats: %w", err)
|
||||
@ -235,15 +282,125 @@ func (da *DatabaseAdapter) LoadPlayerHarvestStatistics(playerID int32) (map[stri
|
||||
|
||||
// SavePlayerHarvestStatistic saves a player's harvest statistic
|
||||
func (da *DatabaseAdapter) SavePlayerHarvestStatistic(playerID int32, skillName string, count int64) error {
|
||||
ctx := context.Background()
|
||||
conn, err := da.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer da.pool.Put(conn)
|
||||
|
||||
query := `
|
||||
INSERT OR REPLACE INTO player_harvest_stats (player_id, skill_name, harvest_count, updated_at)
|
||||
VALUES (?, ?, ?, datetime('now'))
|
||||
`
|
||||
|
||||
err := da.db.Exec(query, playerID, skillName, count)
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{playerID, skillName, count},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save player harvest stat: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureGroundSpawnTables creates the necessary database tables if they don't exist
|
||||
func (da *DatabaseAdapter) EnsureGroundSpawnTables(ctx context.Context) error {
|
||||
conn, err := da.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer da.pool.Put(conn)
|
||||
|
||||
// Create groundspawns table
|
||||
createGroundSpawnsTable := `
|
||||
CREATE TABLE IF NOT EXISTS groundspawns (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
x REAL NOT NULL,
|
||||
y REAL NOT NULL,
|
||||
z REAL NOT NULL,
|
||||
heading REAL NOT NULL,
|
||||
respawn_timer INTEGER NOT NULL DEFAULT 300,
|
||||
collection_skill TEXT NOT NULL,
|
||||
number_harvests INTEGER NOT NULL DEFAULT 1,
|
||||
attempts_per_harvest INTEGER NOT NULL DEFAULT 1,
|
||||
groundspawn_entry_id INTEGER NOT NULL,
|
||||
randomize_heading INTEGER NOT NULL DEFAULT 0,
|
||||
zone_id INTEGER NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`
|
||||
|
||||
// Create groundspawn_entries table
|
||||
createGroundSpawnEntriesTable := `
|
||||
CREATE TABLE IF NOT EXISTS groundspawn_entries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
groundspawn_id INTEGER NOT NULL,
|
||||
min_skill_level INTEGER NOT NULL DEFAULT 0,
|
||||
min_adventure_level INTEGER NOT NULL DEFAULT 0,
|
||||
bonus_table INTEGER NOT NULL DEFAULT 0,
|
||||
harvest_1 REAL NOT NULL DEFAULT 0.0,
|
||||
harvest_3 REAL NOT NULL DEFAULT 0.0,
|
||||
harvest_5 REAL NOT NULL DEFAULT 0.0,
|
||||
harvest_imbue REAL NOT NULL DEFAULT 0.0,
|
||||
harvest_rare REAL NOT NULL DEFAULT 0.0,
|
||||
harvest_10 REAL NOT NULL DEFAULT 0.0,
|
||||
harvest_coin REAL NOT NULL DEFAULT 0.0
|
||||
)
|
||||
`
|
||||
|
||||
// Create groundspawn_items table
|
||||
createGroundSpawnItemsTable := `
|
||||
CREATE TABLE IF NOT EXISTS groundspawn_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
groundspawn_id INTEGER NOT NULL,
|
||||
item_id INTEGER NOT NULL,
|
||||
is_rare INTEGER NOT NULL DEFAULT 0,
|
||||
grid_id INTEGER NOT NULL DEFAULT 0,
|
||||
quantity INTEGER NOT NULL DEFAULT 1
|
||||
)
|
||||
`
|
||||
|
||||
// Create player_harvest_stats table
|
||||
createPlayerHarvestStatsTable := `
|
||||
CREATE TABLE IF NOT EXISTS player_harvest_stats (
|
||||
player_id INTEGER NOT NULL,
|
||||
skill_name TEXT NOT NULL,
|
||||
harvest_count INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (player_id, skill_name)
|
||||
)
|
||||
`
|
||||
|
||||
// Execute table creation statements
|
||||
tables := []string{
|
||||
createGroundSpawnsTable,
|
||||
createGroundSpawnEntriesTable,
|
||||
createGroundSpawnItemsTable,
|
||||
createPlayerHarvestStatsTable,
|
||||
}
|
||||
|
||||
for _, tableSQL := range tables {
|
||||
if err := sqlitex.Execute(conn, tableSQL, nil); err != nil {
|
||||
return fmt.Errorf("failed to create table: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create indexes
|
||||
indexes := []string{
|
||||
`CREATE INDEX IF NOT EXISTS idx_groundspawn_entries_groundspawn_id ON groundspawn_entries(groundspawn_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_groundspawn_items_groundspawn_id ON groundspawn_items(groundspawn_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_groundspawns_zone_id ON groundspawns(zone_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_player_harvest_stats_player_id ON player_harvest_stats(player_id)`,
|
||||
}
|
||||
|
||||
for _, indexSQL := range indexes {
|
||||
if err := sqlitex.Execute(conn, indexSQL, nil); err != nil {
|
||||
return fmt.Errorf("failed to create index: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -6,8 +6,8 @@ const (
|
||||
MaxAbilities = 6
|
||||
|
||||
// Special ability icon values
|
||||
AbilityIconAny = 0xFFFF // Wildcard - any ability can be used
|
||||
AbilityIconNone = 0 // No ability required
|
||||
AbilityIconAny = -1 // Wildcard - any ability can be used (using -1 to fit in int16)
|
||||
AbilityIconNone = 0 // No ability required
|
||||
|
||||
// Default wheel timer (in seconds)
|
||||
DefaultWheelTimerSeconds = 10
|
||||
|
File diff suppressed because it is too large
Load Diff
721
internal/heroic_ops/database_test.go
Normal file
721
internal/heroic_ops/database_test.go
Normal file
@ -0,0 +1,721 @@
|
||||
package heroic_ops
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
)
|
||||
|
||||
// createTestPool creates a temporary test database pool
|
||||
func createTestPool(t *testing.T) *sqlitex.Pool {
|
||||
// Create temporary directory for test database
|
||||
tempDir := t.TempDir()
|
||||
dbPath := filepath.Join(tempDir, "test_heroic_ops.db")
|
||||
|
||||
// Create and initialize database pool
|
||||
pool, err := sqlitex.NewPool(dbPath, sqlitex.PoolOptions{
|
||||
Flags: sqlite.OpenReadWrite | sqlite.OpenCreate,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test database pool: %v", err)
|
||||
}
|
||||
|
||||
// Create heroic ops tables for testing
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
err = dhom.EnsureHOTables(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create heroic ops tables: %v", err)
|
||||
}
|
||||
|
||||
return pool
|
||||
}
|
||||
|
||||
// execSQL is a helper to execute SQL with parameters
|
||||
func execSQL(t *testing.T, pool *sqlitex.Pool, query string, args ...interface{}) {
|
||||
conn, err := pool.Take(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get connection: %v", err)
|
||||
}
|
||||
defer pool.Put(conn)
|
||||
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: args,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute SQL: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_LoadStarters tests loading starters from database
|
||||
func TestDatabaseHeroicOPManager_LoadStarters(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Insert test starter data
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
1, HOTypeStarter, 5, 100, 10, 20, 30, 40, 50, 60, "Test Starter", "A test starter")
|
||||
|
||||
// Test loading starters
|
||||
starters, err := dhom.LoadStarters(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load starters: %v", err)
|
||||
}
|
||||
|
||||
if len(starters) != 1 {
|
||||
t.Errorf("Expected 1 starter, got %d", len(starters))
|
||||
}
|
||||
|
||||
if len(starters) > 0 {
|
||||
starter := starters[0]
|
||||
if starter.ID != 1 {
|
||||
t.Errorf("Expected starter ID 1, got %d", starter.ID)
|
||||
}
|
||||
if starter.StarterClass != 5 {
|
||||
t.Errorf("Expected starter class 5, got %d", starter.StarterClass)
|
||||
}
|
||||
if starter.Name != "Test Starter" {
|
||||
t.Errorf("Expected name 'Test Starter', got '%s'", starter.Name)
|
||||
}
|
||||
if starter.Ability1 != 10 {
|
||||
t.Errorf("Expected ability1 10, got %d", starter.Ability1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_LoadStarter tests loading a specific starter
|
||||
func TestDatabaseHeroicOPManager_LoadStarter(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Insert test starter data
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
2, HOTypeStarter, 10, 200, 11, 21, 31, 41, 51, 61, "Specific Starter", "A specific test starter")
|
||||
|
||||
// Test loading specific starter
|
||||
starter, err := dhom.LoadStarter(ctx, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load starter: %v", err)
|
||||
}
|
||||
|
||||
if starter.ID != 2 {
|
||||
t.Errorf("Expected starter ID 2, got %d", starter.ID)
|
||||
}
|
||||
if starter.StarterClass != 10 {
|
||||
t.Errorf("Expected starter class 10, got %d", starter.StarterClass)
|
||||
}
|
||||
if starter.Name != "Specific Starter" {
|
||||
t.Errorf("Expected name 'Specific Starter', got '%s'", starter.Name)
|
||||
}
|
||||
|
||||
// Test loading non-existent starter
|
||||
_, err = dhom.LoadStarter(ctx, 999)
|
||||
if err == nil {
|
||||
t.Error("Expected error loading non-existent starter, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_LoadWheels tests loading wheels from database
|
||||
func TestDatabaseHeroicOPManager_LoadWheels(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Insert test wheel data
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
10, HOTypeWheel, 1, 1, 300, 1001, 0.75, 15, 25, 35, 45, 55, 65, "Test Wheel", "A test wheel")
|
||||
|
||||
// Test loading wheels
|
||||
wheels, err := dhom.LoadWheels(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load wheels: %v", err)
|
||||
}
|
||||
|
||||
if len(wheels) != 1 {
|
||||
t.Errorf("Expected 1 wheel, got %d", len(wheels))
|
||||
}
|
||||
|
||||
if len(wheels) > 0 {
|
||||
wheel := wheels[0]
|
||||
if wheel.ID != 10 {
|
||||
t.Errorf("Expected wheel ID 10, got %d", wheel.ID)
|
||||
}
|
||||
if wheel.StarterLinkID != 1 {
|
||||
t.Errorf("Expected starter link ID 1, got %d", wheel.StarterLinkID)
|
||||
}
|
||||
if wheel.SpellID != 1001 {
|
||||
t.Errorf("Expected spell ID 1001, got %d", wheel.SpellID)
|
||||
}
|
||||
if wheel.Chance != 0.75 {
|
||||
t.Errorf("Expected chance 0.75, got %f", wheel.Chance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_LoadWheelsForStarter tests loading wheels for specific starter
|
||||
func TestDatabaseHeroicOPManager_LoadWheelsForStarter(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
starterID := int32(5)
|
||||
|
||||
// Insert multiple wheels for the same starter
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
20, HOTypeWheel, starterID, 1, 400, 1002, 0.5, 16, 26, 36, 46, 56, 66, "Wheel 1", "First wheel")
|
||||
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
21, HOTypeWheel, starterID, 2, 500, 1003, 0.8, 17, 27, 37, 47, 57, 67, "Wheel 2", "Second wheel")
|
||||
|
||||
// Test loading wheels for specific starter
|
||||
wheels, err := dhom.LoadWheelsForStarter(ctx, starterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load wheels for starter: %v", err)
|
||||
}
|
||||
|
||||
if len(wheels) != 2 {
|
||||
t.Errorf("Expected 2 wheels, got %d", len(wheels))
|
||||
}
|
||||
|
||||
// Test loading wheels for non-existent starter
|
||||
wheels, err = dhom.LoadWheelsForStarter(ctx, 999)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load wheels for non-existent starter: %v", err)
|
||||
}
|
||||
|
||||
if len(wheels) != 0 {
|
||||
t.Errorf("Expected 0 wheels for non-existent starter, got %d", len(wheels))
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_SaveStarter tests saving a heroic op starter
|
||||
func TestDatabaseHeroicOPManager_SaveStarter(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a test starter
|
||||
starter := &HeroicOPStarter{
|
||||
ID: 100,
|
||||
StartClass: 15,
|
||||
StarterIcon: 500,
|
||||
Abilities: [6]int16{70, 80, 90, 100, 110, 120},
|
||||
Name: "Saved Starter",
|
||||
Description: "A saved test starter",
|
||||
}
|
||||
|
||||
// Save the starter
|
||||
err := dhom.SaveStarter(ctx, starter)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save starter: %v", err)
|
||||
}
|
||||
|
||||
// Load and verify
|
||||
loaded, err := dhom.LoadStarter(ctx, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load saved starter: %v", err)
|
||||
}
|
||||
|
||||
if loaded.StarterClass != 15 {
|
||||
t.Errorf("Expected starter class 15, got %d", loaded.StarterClass)
|
||||
}
|
||||
if loaded.Name != "Saved Starter" {
|
||||
t.Errorf("Expected name 'Saved Starter', got '%s'", loaded.Name)
|
||||
}
|
||||
if loaded.Ability1 != 70 {
|
||||
t.Errorf("Expected ability1 70, got %d", loaded.Ability1)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_SaveWheel tests saving a heroic op wheel
|
||||
func TestDatabaseHeroicOPManager_SaveWheel(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a test wheel
|
||||
wheel := &HeroicOPWheel{
|
||||
ID: 200,
|
||||
StarterLinkID: 100,
|
||||
Order: 2,
|
||||
ShiftIcon: 600,
|
||||
SpellID: 2001,
|
||||
Chance: 0.9,
|
||||
Abilities: [6]int16{71, 81, 91, 101, 111, 121},
|
||||
Name: "Saved Wheel",
|
||||
Description: "A saved test wheel",
|
||||
}
|
||||
|
||||
// Save the wheel
|
||||
err := dhom.SaveWheel(ctx, wheel)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save wheel: %v", err)
|
||||
}
|
||||
|
||||
// Load and verify
|
||||
loaded, err := dhom.LoadWheel(ctx, 200)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load saved wheel: %v", err)
|
||||
}
|
||||
|
||||
if loaded.StarterLinkID != 100 {
|
||||
t.Errorf("Expected starter link ID 100, got %d", loaded.StarterLinkID)
|
||||
}
|
||||
if loaded.SpellID != 2001 {
|
||||
t.Errorf("Expected spell ID 2001, got %d", loaded.SpellID)
|
||||
}
|
||||
if loaded.Chance != 0.9 {
|
||||
t.Errorf("Expected chance 0.9, got %f", loaded.Chance)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_DeleteStarter tests deleting a starter and its wheels
|
||||
func TestDatabaseHeroicOPManager_DeleteStarter(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
starterID := int32(300)
|
||||
|
||||
// Insert starter
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
starterID, HOTypeStarter, 20, 700, 72, 82, 92, 102, 112, 122, "Delete Test Starter", "To be deleted")
|
||||
|
||||
// Insert associated wheels
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
400, HOTypeWheel, starterID, 1, 800, 3001, 0.6, 73, 83, 93, 103, 113, 123, "Wheel to Delete", "Will be deleted")
|
||||
|
||||
// Verify they exist
|
||||
_, err := dhom.LoadStarter(ctx, starterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Starter should exist before deletion: %v", err)
|
||||
}
|
||||
|
||||
wheels, err := dhom.LoadWheelsForStarter(ctx, starterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load wheels before deletion: %v", err)
|
||||
}
|
||||
if len(wheels) != 1 {
|
||||
t.Errorf("Expected 1 wheel before deletion, got %d", len(wheels))
|
||||
}
|
||||
|
||||
// Delete the starter
|
||||
err = dhom.DeleteStarter(ctx, starterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete starter: %v", err)
|
||||
}
|
||||
|
||||
// Verify they're gone
|
||||
_, err = dhom.LoadStarter(ctx, starterID)
|
||||
if err == nil {
|
||||
t.Error("Expected error loading deleted starter, got nil")
|
||||
}
|
||||
|
||||
wheels, err = dhom.LoadWheelsForStarter(ctx, starterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load wheels after deletion: %v", err)
|
||||
}
|
||||
if len(wheels) != 0 {
|
||||
t.Errorf("Expected 0 wheels after deletion, got %d", len(wheels))
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_HeroicOPInstance tests HO instance operations
|
||||
func TestDatabaseHeroicOPManager_HeroicOPInstance(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a test HO instance
|
||||
ho := &HeroicOP{
|
||||
ID: 1000,
|
||||
EncounterID: 500,
|
||||
StarterID: 10,
|
||||
WheelID: 20,
|
||||
State: int8(HOStateWheelPhase),
|
||||
StartTime: time.Now(),
|
||||
WheelStartTime: time.Now().Add(5 * time.Second),
|
||||
TimeRemaining: 8000,
|
||||
TotalTime: 10000,
|
||||
Complete: 0,
|
||||
Countered: [6]int8{1, 1, 0, 0, 0, 0},
|
||||
ShiftUsed: 0,
|
||||
StarterProgress: 3,
|
||||
CompletedBy: 0,
|
||||
SpellName: "Test Spell",
|
||||
SpellDescription: "A test completion spell",
|
||||
}
|
||||
|
||||
// Save the HO instance
|
||||
err := dhom.SaveHOInstance(ctx, ho)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save HO instance: %v", err)
|
||||
}
|
||||
|
||||
// Load and verify
|
||||
loaded, err := dhom.LoadHOInstance(ctx, 1000)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load HO instance: %v", err)
|
||||
}
|
||||
|
||||
if loaded.EncounterID != 500 {
|
||||
t.Errorf("Expected encounter ID 500, got %d", loaded.EncounterID)
|
||||
}
|
||||
if loaded.State != int8(HOStateWheelPhase) {
|
||||
t.Errorf("Expected state %d, got %d", HOStateWheelPhase, loaded.State)
|
||||
}
|
||||
if loaded.TimeRemaining != 8000 {
|
||||
t.Errorf("Expected time remaining 8000, got %d", loaded.TimeRemaining)
|
||||
}
|
||||
if loaded.SpellName != "Test Spell" {
|
||||
t.Errorf("Expected spell name 'Test Spell', got '%s'", loaded.SpellName)
|
||||
}
|
||||
|
||||
// Delete the instance
|
||||
err = dhom.DeleteHOInstance(ctx, 1000)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete HO instance: %v", err)
|
||||
}
|
||||
|
||||
// Verify it's gone
|
||||
_, err = dhom.LoadHOInstance(ctx, 1000)
|
||||
if err == nil {
|
||||
t.Error("Expected error loading deleted HO instance, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_HeroicOPEvents tests HO event operations
|
||||
func TestDatabaseHeroicOPManager_HeroicOPEvents(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
instanceID := int64(2000)
|
||||
|
||||
// Create test events
|
||||
events := []*HeroicOPEvent{
|
||||
{
|
||||
ID: 1,
|
||||
InstanceID: instanceID,
|
||||
EventType: EventHOStarted,
|
||||
CharacterID: 100,
|
||||
AbilityIcon: 50,
|
||||
Timestamp: time.Now(),
|
||||
Data: "started",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
InstanceID: instanceID,
|
||||
EventType: EventHOAbilityUsed,
|
||||
CharacterID: 101,
|
||||
AbilityIcon: 51,
|
||||
Timestamp: time.Now().Add(1 * time.Second),
|
||||
Data: "ability used",
|
||||
},
|
||||
}
|
||||
|
||||
// Save events
|
||||
for _, event := range events {
|
||||
err := dhom.SaveHOEvent(ctx, event)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save HO event: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load and verify
|
||||
loadedEvents, err := dhom.LoadHOEvents(ctx, instanceID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load HO events: %v", err)
|
||||
}
|
||||
|
||||
if len(loadedEvents) != 2 {
|
||||
t.Errorf("Expected 2 events, got %d", len(loadedEvents))
|
||||
}
|
||||
|
||||
// Events should be ordered by timestamp
|
||||
if len(loadedEvents) >= 2 {
|
||||
if loadedEvents[0].EventType != EventHOStarted {
|
||||
t.Errorf("Expected first event type %d, got %d", EventHOStarted, loadedEvents[0].EventType)
|
||||
}
|
||||
if loadedEvents[1].EventType != EventHOAbilityUsed {
|
||||
t.Errorf("Expected second event type %d, got %d", EventHOAbilityUsed, loadedEvents[1].EventType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_Statistics tests HO statistics retrieval
|
||||
func TestDatabaseHeroicOPManager_Statistics(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
characterID := int32(1001)
|
||||
|
||||
// Insert test events for statistics
|
||||
execSQL(t, pool, `INSERT INTO heroic_op_events
|
||||
(id, instance_id, event_type, character_id, ability_icon, timestamp, data)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
1, 100, EventHOStarted, characterID, 0, time.Now().Unix(), "")
|
||||
|
||||
execSQL(t, pool, `INSERT INTO heroic_op_events
|
||||
(id, instance_id, event_type, character_id, ability_icon, timestamp, data)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
2, 100, EventHOCompleted, characterID, 0, time.Now().Add(10*time.Second).Unix(), "")
|
||||
|
||||
execSQL(t, pool, `INSERT INTO heroic_op_events
|
||||
(id, instance_id, event_type, character_id, ability_icon, timestamp, data)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
3, 101, EventHOStarted, characterID, 0, time.Now().Add(20*time.Second).Unix(), "")
|
||||
|
||||
// Get statistics
|
||||
stats, err := dhom.GetHOStatistics(ctx, characterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get HO statistics: %v", err)
|
||||
}
|
||||
|
||||
if stats.TotalHOsStarted != 2 {
|
||||
t.Errorf("Expected 2 HOs started, got %d", stats.TotalHOsStarted)
|
||||
}
|
||||
if stats.TotalHOsCompleted != 1 {
|
||||
t.Errorf("Expected 1 HO completed, got %d", stats.TotalHOsCompleted)
|
||||
}
|
||||
if stats.SuccessRate != 50.0 {
|
||||
t.Errorf("Expected success rate 50.0, got %f", stats.SuccessRate)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_NextIDs tests ID generation
|
||||
func TestDatabaseHeroicOPManager_NextIDs(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Test next starter ID (should be 1 for empty database)
|
||||
starterID, err := dhom.GetNextStarterID(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get next starter ID: %v", err)
|
||||
}
|
||||
if starterID != 1 {
|
||||
t.Errorf("Expected next starter ID 1, got %d", starterID)
|
||||
}
|
||||
|
||||
// Insert a starter and test again
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
5, HOTypeStarter, 1, 100, 1, 2, 3, 4, 5, 6, "Test", "Test")
|
||||
|
||||
starterID, err = dhom.GetNextStarterID(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get next starter ID after insert: %v", err)
|
||||
}
|
||||
if starterID != 6 {
|
||||
t.Errorf("Expected next starter ID 6, got %d", starterID)
|
||||
}
|
||||
|
||||
// Test next wheel ID
|
||||
wheelID, err := dhom.GetNextWheelID(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get next wheel ID: %v", err)
|
||||
}
|
||||
if wheelID != 1 {
|
||||
t.Errorf("Expected next wheel ID 1, got %d", wheelID)
|
||||
}
|
||||
|
||||
// Test next instance ID
|
||||
instanceID, err := dhom.GetNextInstanceID(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get next instance ID: %v", err)
|
||||
}
|
||||
if instanceID != 1 {
|
||||
t.Errorf("Expected next instance ID 1, got %d", instanceID)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDatabaseHeroicOPManager_LoadStarters benchmarks loading starters
|
||||
func BenchmarkDatabaseHeroicOPManager_LoadStarters(b *testing.B) {
|
||||
pool := createTestPool(&testing.T{})
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Insert test data
|
||||
for i := 1; i <= 50; i++ {
|
||||
execSQL(&testing.T{}, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
i, HOTypeStarter, i%10+1, i*10,
|
||||
i*1, i*2, i*3, i*4, i*5, i*6,
|
||||
fmt.Sprintf("Starter %d", i), fmt.Sprintf("Description %d", i))
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
starters, err := dhom.LoadStarters(ctx)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to load starters: %v", err)
|
||||
}
|
||||
if len(starters) != 50 {
|
||||
b.Errorf("Expected 50 starters, got %d", len(starters))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDatabaseHeroicOPManager_LoadWheels benchmarks loading wheels
|
||||
func BenchmarkDatabaseHeroicOPManager_LoadWheels(b *testing.B) {
|
||||
pool := createTestPool(&testing.T{})
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Insert test wheel data
|
||||
for i := 1; i <= 100; i++ {
|
||||
execSQL(&testing.T{}, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
i, HOTypeWheel, i%10+1, i%3+1, i*10, i*100+1000, float32(i%100)/100.0,
|
||||
i*1, i*2, i*3, i*4, i*5, i*6,
|
||||
fmt.Sprintf("Wheel %d", i), fmt.Sprintf("Wheel Description %d", i))
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
wheels, err := dhom.LoadWheels(ctx)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to load wheels: %v", err)
|
||||
}
|
||||
if len(wheels) != 100 {
|
||||
b.Errorf("Expected 100 wheels, got %d", len(wheels))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDatabaseHeroicOPManager_SaveStarter benchmarks saving starters
|
||||
func BenchmarkDatabaseHeroicOPManager_SaveStarter(b *testing.B) {
|
||||
pool := createTestPool(&testing.T{})
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
starter := &HeroicOPStarter{
|
||||
ID: 1000,
|
||||
StartClass: 5,
|
||||
StarterIcon: 100,
|
||||
Abilities: [6]int16{10, 20, 30, 40, 50, 60},
|
||||
Name: "Benchmark Starter",
|
||||
Description: "A benchmark test starter",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := dhom.SaveStarter(ctx, starter)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to save starter: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDatabaseHeroicOPManager_SaveHOInstance benchmarks saving HO instances
|
||||
func BenchmarkDatabaseHeroicOPManager_SaveHOInstance(b *testing.B) {
|
||||
pool := createTestPool(&testing.T{})
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
ho := &HeroicOP{
|
||||
ID: 5000,
|
||||
EncounterID: 1000,
|
||||
StarterID: 50,
|
||||
WheelID: 100,
|
||||
State: int8(HOStateWheelPhase),
|
||||
StartTime: time.Now(),
|
||||
WheelStartTime: time.Now(),
|
||||
TimeRemaining: 10000,
|
||||
TotalTime: 10000,
|
||||
Complete: 0,
|
||||
Countered: [6]int8{0, 0, 0, 0, 0, 0},
|
||||
ShiftUsed: 0,
|
||||
StarterProgress: 0,
|
||||
CompletedBy: 0,
|
||||
SpellName: "Benchmark Spell",
|
||||
SpellDescription: "A benchmark spell",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := dhom.SaveHOInstance(ctx, ho)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to save HO instance: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,9 @@ func NewHeroicOPManager(masterList *MasterHeroicOPList, database HeroicOPDatabas
|
||||
encounterHOs: make(map[int32][]*HeroicOP),
|
||||
masterList: masterList,
|
||||
database: database,
|
||||
clientManager: clientManager,
|
||||
encounterManager: encounterManager,
|
||||
playerManager: playerManager,
|
||||
nextInstanceID: 1,
|
||||
defaultWheelTimer: DefaultWheelTimerSeconds * 1000, // Convert to milliseconds
|
||||
maxConcurrentHOs: MaxConcurrentHOs,
|
||||
@ -415,7 +418,7 @@ func (hom *HeroicOPManager) completeHO(ctx context.Context, ho *HeroicOP, wheel
|
||||
|
||||
// Cast completion spell
|
||||
if wheel.SpellID > 0 {
|
||||
participants := ho.GetParticipants()
|
||||
_ = ho.GetParticipants() // participants will be used when spell manager is integrated
|
||||
// TODO: Cast spell on participants through spell manager
|
||||
// hom.spellManager.CastSpell(completedBy, wheel.SpellID, participants)
|
||||
}
|
||||
@ -547,9 +550,9 @@ func (hom *HeroicOPManager) sendShiftUpdate(ho *HeroicOP, oldWheelID, newWheelID
|
||||
participants := ho.GetParticipants()
|
||||
packetBuilder := NewHeroicOPPacketBuilder(0)
|
||||
|
||||
for _, characterID := range participants {
|
||||
for range participants {
|
||||
if packet, err := packetBuilder.BuildHOShiftPacket(ho, oldWheelID, newWheelID); err == nil {
|
||||
// TODO: Send packet through client manager
|
||||
// TODO: Send packet through client manager using characterID
|
||||
_ = packet // Placeholder
|
||||
}
|
||||
}
|
||||
|
@ -97,14 +97,17 @@ type MasterHeroicOPList struct {
|
||||
|
||||
// HeroicOPManager manages active heroic opportunity instances
|
||||
type HeroicOPManager struct {
|
||||
mu sync.RWMutex
|
||||
activeHOs map[int64]*HeroicOP // instance_id -> HO
|
||||
encounterHOs map[int32][]*HeroicOP // encounter_id -> HOs
|
||||
masterList *MasterHeroicOPList
|
||||
database HeroicOPDatabase
|
||||
eventHandler HeroicOPEventHandler
|
||||
logger LogHandler
|
||||
nextInstanceID int64
|
||||
mu sync.RWMutex
|
||||
activeHOs map[int64]*HeroicOP // instance_id -> HO
|
||||
encounterHOs map[int32][]*HeroicOP // encounter_id -> HOs
|
||||
masterList *MasterHeroicOPList
|
||||
database HeroicOPDatabase
|
||||
eventHandler HeroicOPEventHandler
|
||||
logger LogHandler
|
||||
clientManager ClientManager
|
||||
encounterManager EncounterManager
|
||||
playerManager PlayerManager
|
||||
nextInstanceID int64
|
||||
// Configuration
|
||||
defaultWheelTimer int32 // milliseconds
|
||||
maxConcurrentHOs int
|
||||
|
777
internal/housing/bench_test.go
Normal file
777
internal/housing/bench_test.go
Normal file
@ -0,0 +1,777 @@
|
||||
package housing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
)
|
||||
|
||||
// setupBenchmarkDB creates a test database with sample data for benchmarking
|
||||
func setupBenchmarkDB(b *testing.B) (*DatabaseHousingManager, context.Context) {
|
||||
// Create truly unique database name to avoid cross-benchmark contamination
|
||||
dbName := fmt.Sprintf("file:bench_%s_%d.db?mode=memory&cache=shared", b.Name(), rand.Int63())
|
||||
pool, err := sqlitex.NewPool(dbName, sqlitex.PoolOptions{})
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to create benchmark database pool: %v", err)
|
||||
}
|
||||
|
||||
dhm := NewDatabaseHousingManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := dhm.EnsureHousingTables(ctx); err != nil {
|
||||
b.Fatalf("Failed to create benchmark tables: %v", err)
|
||||
}
|
||||
|
||||
return dhm, ctx
|
||||
}
|
||||
|
||||
// insertBenchmarkData inserts a large dataset for benchmarking
|
||||
func insertBenchmarkData(b *testing.B, dhm *DatabaseHousingManager, ctx context.Context, houseZones, playerHouses int) {
|
||||
// Insert house zones
|
||||
for i := 1; i <= houseZones; i++ {
|
||||
zone := &HouseZone{
|
||||
ID: int32(i),
|
||||
Name: fmt.Sprintf("House Type %d", i),
|
||||
ZoneID: int32(100 + i),
|
||||
CostCoin: int64(50000 + i*10000),
|
||||
CostStatus: int64(1000 + i*100),
|
||||
UpkeepCoin: int64(5000 + i*1000),
|
||||
UpkeepStatus: int64(100 + i*10),
|
||||
Alignment: int8(i % 3 - 1), // -1, 0, 1
|
||||
GuildLevel: int8(i % 50),
|
||||
VaultSlots: int(4 + i%4),
|
||||
MaxItems: int(100 + i*50),
|
||||
MaxVisitors: int(10 + i*5),
|
||||
UpkeepPeriod: 604800, // 1 week
|
||||
Description: fmt.Sprintf("Benchmark house type %d description", i),
|
||||
}
|
||||
|
||||
if err := dhm.SaveHouseZone(ctx, zone); err != nil {
|
||||
b.Fatalf("Failed to insert benchmark house zone: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert player houses
|
||||
for i := 1; i <= playerHouses; i++ {
|
||||
houseData := PlayerHouseData{
|
||||
CharacterID: int32(1000 + i),
|
||||
HouseID: int32((i % houseZones) + 1),
|
||||
InstanceID: int32(5000 + i),
|
||||
UpkeepDue: time.Now().Add(time.Duration(i%168) * time.Hour), // Random within a week
|
||||
EscrowCoins: int64(10000 + i*1000),
|
||||
EscrowStatus: int64(200 + i*20),
|
||||
Status: HouseStatusActive,
|
||||
HouseName: fmt.Sprintf("House %d", i),
|
||||
VisitPermission: int8(i % 3),
|
||||
PublicNote: fmt.Sprintf("Welcome to house %d!", i),
|
||||
PrivateNote: fmt.Sprintf("Private note %d", i),
|
||||
AllowFriends: i%2 == 0,
|
||||
AllowGuild: i%3 == 0,
|
||||
RequireApproval: i%4 == 0,
|
||||
ShowOnDirectory: i%5 != 0,
|
||||
AllowDecoration: i%6 != 0,
|
||||
TaxExempt: i%10 == 0,
|
||||
}
|
||||
|
||||
_, err := dhm.AddPlayerHouse(ctx, houseData)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to insert benchmark player house: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insertHouseRelatedData inserts deposits, history, access, etc. for benchmarking
|
||||
func insertHouseRelatedData(b *testing.B, dhm *DatabaseHousingManager, ctx context.Context, houseID int64, entries int) {
|
||||
// Insert deposits
|
||||
for i := 1; i <= entries; i++ {
|
||||
deposit := HouseDeposit{
|
||||
Timestamp: time.Now().Add(-time.Duration(i) * time.Hour),
|
||||
Amount: int64(1000 + i*100),
|
||||
LastAmount: int64(2000 + i*100),
|
||||
Status: int64(50 + i*5),
|
||||
LastStatus: int64(100 + i*5),
|
||||
Name: fmt.Sprintf("Player %d", i%10+1),
|
||||
CharacterID: int32(2000 + i%10),
|
||||
}
|
||||
|
||||
if err := dhm.SaveDeposit(ctx, houseID, deposit); err != nil {
|
||||
b.Fatalf("Failed to insert benchmark deposit: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert history
|
||||
for i := 1; i <= entries; i++ {
|
||||
history := HouseHistory{
|
||||
Timestamp: time.Now().Add(-time.Duration(i*2) * time.Hour),
|
||||
Amount: int64(500 + i*50),
|
||||
Status: int64(25 + i*2),
|
||||
Reason: fmt.Sprintf("Transaction %d", i),
|
||||
Name: fmt.Sprintf("Player %d", i%10+1),
|
||||
CharacterID: int32(2000 + i%10),
|
||||
PosFlag: int8(i % 2),
|
||||
Type: int(i % 5),
|
||||
}
|
||||
|
||||
if err := dhm.AddHistory(ctx, houseID, history); err != nil {
|
||||
b.Fatalf("Failed to insert benchmark history: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert access entries
|
||||
accessList := make([]HouseAccess, 0, entries/10) // Fewer access entries
|
||||
for i := 1; i <= entries/10; i++ {
|
||||
access := HouseAccess{
|
||||
CharacterID: int32(3000 + i),
|
||||
PlayerName: fmt.Sprintf("AccessPlayer%d", i),
|
||||
AccessLevel: int8(i % 3),
|
||||
Permissions: int32(i % 16), // 0-15
|
||||
GrantedBy: int32(1001),
|
||||
GrantedDate: time.Now().Add(-time.Duration(i*24) * time.Hour),
|
||||
ExpiresDate: time.Now().Add(time.Duration(30-i) * 24 * time.Hour),
|
||||
Notes: fmt.Sprintf("Access notes for player %d", i),
|
||||
}
|
||||
accessList = append(accessList, access)
|
||||
}
|
||||
|
||||
if len(accessList) > 0 {
|
||||
if err := dhm.SaveHouseAccess(ctx, houseID, accessList); err != nil {
|
||||
b.Fatalf("Failed to insert benchmark access: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert amenities
|
||||
for i := 1; i <= entries/5; i++ {
|
||||
amenity := HouseAmenity{
|
||||
ID: int32(i),
|
||||
Type: int(i % 10),
|
||||
Name: fmt.Sprintf("Amenity %d", i),
|
||||
Cost: int64(1000 + i*500),
|
||||
StatusCost: int64(20 + i*10),
|
||||
PurchaseDate: time.Now().Add(-time.Duration(i*12) * time.Hour),
|
||||
X: float32(100 + i*10),
|
||||
Y: float32(200 + i*15),
|
||||
Z: float32(50 + i*5),
|
||||
Heading: float32(i % 360),
|
||||
IsActive: i%2 == 0,
|
||||
}
|
||||
|
||||
if err := dhm.SaveHouseAmenity(ctx, houseID, amenity); err != nil {
|
||||
b.Fatalf("Failed to insert benchmark amenity: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert items
|
||||
for i := 1; i <= entries/3; i++ {
|
||||
item := HouseItem{
|
||||
ID: int64(i),
|
||||
ItemID: int32(10000 + i),
|
||||
CharacterID: int32(1001),
|
||||
X: float32(150 + i*5),
|
||||
Y: float32(250 + i*7),
|
||||
Z: float32(75 + i*3),
|
||||
Heading: float32(i % 360),
|
||||
PitchX: float32(i % 10),
|
||||
PitchY: float32(i % 5),
|
||||
RollX: float32(i % 15),
|
||||
RollY: float32(i % 8),
|
||||
PlacedDate: time.Now().Add(-time.Duration(i*6) * time.Hour),
|
||||
Quantity: int32(1 + i%5),
|
||||
Condition: int8(100 - i%100),
|
||||
House: fmt.Sprintf("room_%d", i%5),
|
||||
}
|
||||
|
||||
if err := dhm.SaveHouseItem(ctx, houseID, item); err != nil {
|
||||
b.Fatalf("Failed to insert benchmark item: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoadHouseZones benchmarks loading all house zones
|
||||
func BenchmarkLoadHouseZones(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 100, 0) // 100 house zones, no player houses
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
zones, err := dhm.LoadHouseZones(ctx)
|
||||
if err != nil {
|
||||
b.Fatalf("LoadHouseZones failed: %v", err)
|
||||
}
|
||||
if len(zones) != 100 {
|
||||
b.Errorf("Expected 100 zones, got %d", len(zones))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoadHouseZone benchmarks loading a single house zone
|
||||
func BenchmarkLoadHouseZone(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 100, 0)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
zone, err := dhm.LoadHouseZone(ctx, int32((i%100)+1))
|
||||
if err != nil {
|
||||
b.Fatalf("LoadHouseZone failed: %v", err)
|
||||
}
|
||||
if zone == nil {
|
||||
b.Error("LoadHouseZone returned nil zone")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSaveHouseZone benchmarks saving a house zone
|
||||
func BenchmarkSaveHouseZone(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
zone := &HouseZone{
|
||||
ID: int32(1000 + i),
|
||||
Name: fmt.Sprintf("Benchmark House %d", i),
|
||||
ZoneID: int32(2000 + i),
|
||||
CostCoin: int64(50000 + i*1000),
|
||||
CostStatus: int64(1000 + i*10),
|
||||
UpkeepCoin: int64(5000 + i*100),
|
||||
UpkeepStatus: int64(100 + i),
|
||||
Alignment: int8(i % 3 - 1),
|
||||
GuildLevel: int8(i % 50),
|
||||
VaultSlots: int(4 + i%4),
|
||||
MaxItems: int(100 + i*10),
|
||||
MaxVisitors: int(10 + i),
|
||||
UpkeepPeriod: 604800,
|
||||
Description: fmt.Sprintf("Benchmark description %d", i),
|
||||
}
|
||||
|
||||
if err := dhm.SaveHouseZone(ctx, zone); err != nil {
|
||||
b.Fatalf("SaveHouseZone failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoadPlayerHouses benchmarks loading player houses
|
||||
func BenchmarkLoadPlayerHouses(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 10, 1000) // 10 house types, 1000 player houses
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
characterID := int32(1000 + (i%1000) + 1)
|
||||
houses, err := dhm.LoadPlayerHouses(ctx, characterID)
|
||||
if err != nil {
|
||||
b.Fatalf("LoadPlayerHouses failed: %v", err)
|
||||
}
|
||||
if len(houses) != 1 {
|
||||
b.Errorf("Expected 1 house for character %d, got %d", characterID, len(houses))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoadPlayerHouse benchmarks loading a single player house
|
||||
func BenchmarkLoadPlayerHouse(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 10, 100)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
houseID := int64((i % 100) + 1)
|
||||
house, err := dhm.LoadPlayerHouse(ctx, houseID)
|
||||
if err != nil {
|
||||
b.Fatalf("LoadPlayerHouse failed: %v", err)
|
||||
}
|
||||
if house == nil {
|
||||
b.Error("LoadPlayerHouse returned nil house")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkAddPlayerHouse benchmarks adding player houses
|
||||
func BenchmarkAddPlayerHouse(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 10, 0) // Just house zones
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
houseData := PlayerHouseData{
|
||||
CharacterID: int32(5000 + i),
|
||||
HouseID: int32((i % 10) + 1),
|
||||
InstanceID: int32(10000 + i),
|
||||
UpkeepDue: time.Now().Add(24 * time.Hour),
|
||||
EscrowCoins: int64(25000 + i*100),
|
||||
EscrowStatus: int64(500 + i*5),
|
||||
Status: HouseStatusActive,
|
||||
HouseName: fmt.Sprintf("Benchmark House %d", i),
|
||||
VisitPermission: int8(i % 3),
|
||||
PublicNote: fmt.Sprintf("Welcome to benchmark house %d", i),
|
||||
PrivateNote: fmt.Sprintf("Private note %d", i),
|
||||
AllowFriends: i%2 == 0,
|
||||
AllowGuild: i%3 == 0,
|
||||
RequireApproval: i%4 == 0,
|
||||
ShowOnDirectory: i%5 != 0,
|
||||
AllowDecoration: i%6 != 0,
|
||||
TaxExempt: i%10 == 0,
|
||||
}
|
||||
|
||||
_, err := dhm.AddPlayerHouse(ctx, houseData)
|
||||
if err != nil {
|
||||
b.Fatalf("AddPlayerHouse failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoadDeposits benchmarks loading house deposits
|
||||
func BenchmarkLoadDeposits(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 1)
|
||||
|
||||
// Insert deposit data for house ID 1
|
||||
insertHouseRelatedData(b, dhm, ctx, 1, 500) // 500 deposits
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
deposits, err := dhm.LoadDeposits(ctx, 1)
|
||||
if err != nil {
|
||||
b.Fatalf("LoadDeposits failed: %v", err)
|
||||
}
|
||||
if len(deposits) == 0 {
|
||||
b.Error("LoadDeposits returned no deposits")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSaveDeposit benchmarks saving deposits
|
||||
func BenchmarkSaveDeposit(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 1)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
deposit := HouseDeposit{
|
||||
Timestamp: time.Now(),
|
||||
Amount: int64(1000 + i*10),
|
||||
LastAmount: int64(2000 + i*10),
|
||||
Status: int64(50 + i),
|
||||
LastStatus: int64(100 + i),
|
||||
Name: fmt.Sprintf("Benchmark Player %d", i),
|
||||
CharacterID: int32(2000 + i),
|
||||
}
|
||||
|
||||
if err := dhm.SaveDeposit(ctx, 1, deposit); err != nil {
|
||||
b.Fatalf("SaveDeposit failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoadHistory benchmarks loading house history
|
||||
func BenchmarkLoadHistory(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 1)
|
||||
|
||||
// Insert history data for house ID 1
|
||||
insertHouseRelatedData(b, dhm, ctx, 1, 500) // 500 history entries
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
history, err := dhm.LoadHistory(ctx, 1)
|
||||
if err != nil {
|
||||
b.Fatalf("LoadHistory failed: %v", err)
|
||||
}
|
||||
if len(history) == 0 {
|
||||
b.Error("LoadHistory returned no history")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkAddHistory benchmarks adding history entries
|
||||
func BenchmarkAddHistory(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 1)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
history := HouseHistory{
|
||||
Timestamp: time.Now(),
|
||||
Amount: int64(500 + i*5),
|
||||
Status: int64(25 + i),
|
||||
Reason: fmt.Sprintf("Benchmark transaction %d", i),
|
||||
Name: fmt.Sprintf("Benchmark Player %d", i),
|
||||
CharacterID: int32(2000 + i),
|
||||
PosFlag: int8(i % 2),
|
||||
Type: int(i % 5),
|
||||
}
|
||||
|
||||
if err := dhm.AddHistory(ctx, 1, history); err != nil {
|
||||
b.Fatalf("AddHistory failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoadHouseAccess benchmarks loading house access
|
||||
func BenchmarkLoadHouseAccess(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 1)
|
||||
|
||||
// Insert access data for house ID 1
|
||||
insertHouseRelatedData(b, dhm, ctx, 1, 100) // Will create 10 access entries
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
access, err := dhm.LoadHouseAccess(ctx, 1)
|
||||
if err != nil {
|
||||
b.Fatalf("LoadHouseAccess failed: %v", err)
|
||||
}
|
||||
if len(access) == 0 {
|
||||
b.Error("LoadHouseAccess returned no access entries")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSaveHouseAccess benchmarks saving house access
|
||||
func BenchmarkSaveHouseAccess(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 1)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
accessList := []HouseAccess{
|
||||
{
|
||||
CharacterID: int32(4000 + i),
|
||||
PlayerName: fmt.Sprintf("BenchPlayer%d", i),
|
||||
AccessLevel: int8(i % 3),
|
||||
Permissions: int32(i % 16),
|
||||
GrantedBy: 1001,
|
||||
GrantedDate: time.Now(),
|
||||
ExpiresDate: time.Now().Add(30 * 24 * time.Hour),
|
||||
Notes: fmt.Sprintf("Benchmark access %d", i),
|
||||
},
|
||||
}
|
||||
|
||||
if err := dhm.SaveHouseAccess(ctx, 1, accessList); err != nil {
|
||||
b.Fatalf("SaveHouseAccess failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoadHouseAmenities benchmarks loading house amenities
|
||||
func BenchmarkLoadHouseAmenities(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 1)
|
||||
|
||||
// Insert amenity data for house ID 1
|
||||
insertHouseRelatedData(b, dhm, ctx, 1, 100) // Will create 20 amenities
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
amenities, err := dhm.LoadHouseAmenities(ctx, 1)
|
||||
if err != nil {
|
||||
b.Fatalf("LoadHouseAmenities failed: %v", err)
|
||||
}
|
||||
if len(amenities) == 0 {
|
||||
b.Error("LoadHouseAmenities returned no amenities")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSaveHouseAmenity benchmarks saving house amenities
|
||||
func BenchmarkSaveHouseAmenity(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 1)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
amenity := HouseAmenity{
|
||||
ID: int32(5000 + i),
|
||||
Type: int(i % 10),
|
||||
Name: fmt.Sprintf("Benchmark Amenity %d", i),
|
||||
Cost: int64(1000 + i*100),
|
||||
StatusCost: int64(20 + i*2),
|
||||
PurchaseDate: time.Now(),
|
||||
X: float32(100 + i),
|
||||
Y: float32(200 + i),
|
||||
Z: float32(50 + i),
|
||||
Heading: float32(i % 360),
|
||||
IsActive: i%2 == 0,
|
||||
}
|
||||
|
||||
if err := dhm.SaveHouseAmenity(ctx, 1, amenity); err != nil {
|
||||
b.Fatalf("SaveHouseAmenity failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoadHouseItems benchmarks loading house items
|
||||
func BenchmarkLoadHouseItems(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 1)
|
||||
|
||||
// Insert item data for house ID 1
|
||||
insertHouseRelatedData(b, dhm, ctx, 1, 150) // Will create 50 items
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
items, err := dhm.LoadHouseItems(ctx, 1)
|
||||
if err != nil {
|
||||
b.Fatalf("LoadHouseItems failed: %v", err)
|
||||
}
|
||||
if len(items) == 0 {
|
||||
b.Error("LoadHouseItems returned no items")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSaveHouseItem benchmarks saving house items
|
||||
func BenchmarkSaveHouseItem(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 1)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
item := HouseItem{
|
||||
ID: int64(6000 + i),
|
||||
ItemID: int32(20000 + i),
|
||||
CharacterID: 1001,
|
||||
X: float32(150 + i),
|
||||
Y: float32(250 + i),
|
||||
Z: float32(75 + i),
|
||||
Heading: float32(i % 360),
|
||||
PitchX: float32(i % 10),
|
||||
PitchY: float32(i % 5),
|
||||
RollX: float32(i % 15),
|
||||
RollY: float32(i % 8),
|
||||
PlacedDate: time.Now(),
|
||||
Quantity: int32(1 + i%5),
|
||||
Condition: int8(100 - i%100),
|
||||
House: "benchmark",
|
||||
}
|
||||
|
||||
if err := dhm.SaveHouseItem(ctx, 1, item); err != nil {
|
||||
b.Fatalf("SaveHouseItem failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkUpdateHouseUpkeepDue benchmarks updating house upkeep due date
|
||||
func BenchmarkUpdateHouseUpkeepDue(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 100)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
houseID := int64((i % 100) + 1)
|
||||
newUpkeepDue := time.Now().Add(time.Duration(i) * time.Hour)
|
||||
|
||||
if err := dhm.UpdateHouseUpkeepDue(ctx, houseID, newUpkeepDue); err != nil {
|
||||
b.Fatalf("UpdateHouseUpkeepDue failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkUpdateHouseEscrow benchmarks updating house escrow
|
||||
func BenchmarkUpdateHouseEscrow(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 100)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
houseID := int64((i % 100) + 1)
|
||||
coins := int64(50000 + i*1000)
|
||||
status := int64(1000 + i*10)
|
||||
|
||||
if err := dhm.UpdateHouseEscrow(ctx, houseID, coins, status); err != nil {
|
||||
b.Fatalf("UpdateHouseEscrow failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetHousesForUpkeep benchmarks getting houses for upkeep
|
||||
func BenchmarkGetHousesForUpkeep(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 10, 1000)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
cutoffTime := time.Now().Add(time.Duration(i%200) * time.Hour)
|
||||
houses, err := dhm.GetHousesForUpkeep(ctx, cutoffTime)
|
||||
if err != nil {
|
||||
b.Fatalf("GetHousesForUpkeep failed: %v", err)
|
||||
}
|
||||
_ = houses // Prevent unused variable warning
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetHouseStatistics benchmarks getting house statistics
|
||||
func BenchmarkGetHouseStatistics(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 20, 2000)
|
||||
|
||||
// Add some deposits and history for more realistic stats
|
||||
insertHouseRelatedData(b, dhm, ctx, 1, 100)
|
||||
insertHouseRelatedData(b, dhm, ctx, 2, 150)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
stats, err := dhm.GetHouseStatistics(ctx)
|
||||
if err != nil {
|
||||
b.Fatalf("GetHouseStatistics failed: %v", err)
|
||||
}
|
||||
if stats.TotalHouses != 2000 {
|
||||
b.Errorf("Expected 2000 total houses, got %d", stats.TotalHouses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetHouseByInstance benchmarks finding houses by instance ID
|
||||
func BenchmarkGetHouseByInstance(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 10, 1000)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
instanceID := int32(5001 + (i % 1000))
|
||||
house, err := dhm.GetHouseByInstance(ctx, instanceID)
|
||||
if err != nil {
|
||||
b.Fatalf("GetHouseByInstance failed: %v", err)
|
||||
}
|
||||
if house == nil {
|
||||
b.Error("GetHouseByInstance returned nil house")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetNextHouseID benchmarks getting the next house ID
|
||||
func BenchmarkGetNextHouseID(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 5, 100)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
nextID, err := dhm.GetNextHouseID(ctx)
|
||||
if err != nil {
|
||||
b.Fatalf("GetNextHouseID failed: %v", err)
|
||||
}
|
||||
if nextID <= 100 {
|
||||
b.Errorf("Expected next ID > 100, got %d", nextID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDeletePlayerHouse benchmarks deleting player houses
|
||||
func BenchmarkDeletePlayerHouse(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
|
||||
// We need to create houses to delete in each iteration
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
|
||||
// Create a house to delete
|
||||
houseData := PlayerHouseData{
|
||||
CharacterID: int32(7000 + i),
|
||||
HouseID: 1,
|
||||
InstanceID: int32(8000 + i),
|
||||
UpkeepDue: time.Now().Add(24 * time.Hour),
|
||||
EscrowCoins: 25000,
|
||||
EscrowStatus: 500,
|
||||
Status: HouseStatusActive,
|
||||
HouseName: fmt.Sprintf("DeleteMe %d", i),
|
||||
VisitPermission: 1,
|
||||
AllowFriends: true,
|
||||
ShowOnDirectory: true,
|
||||
}
|
||||
|
||||
houseID, err := dhm.AddPlayerHouse(ctx, houseData)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to create house for deletion: %v", err)
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
// Delete the house
|
||||
if err := dhm.DeletePlayerHouse(ctx, houseID); err != nil {
|
||||
b.Fatalf("DeletePlayerHouse failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkConcurrentReads benchmarks concurrent read operations
|
||||
func BenchmarkConcurrentReads(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 10, 100)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
characterID := int32(1001 + (i % 100))
|
||||
houses, err := dhm.LoadPlayerHouses(ctx, characterID)
|
||||
if err != nil {
|
||||
b.Errorf("LoadPlayerHouses failed: %v", err)
|
||||
}
|
||||
if len(houses) != 1 {
|
||||
b.Errorf("Expected 1 house, got %d", len(houses))
|
||||
}
|
||||
i++
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkConcurrentWrites benchmarks concurrent write operations
|
||||
func BenchmarkConcurrentWrites(b *testing.B) {
|
||||
dhm, ctx := setupBenchmarkDB(b)
|
||||
insertBenchmarkData(b, dhm, ctx, 10, 0)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
i := 0
|
||||
for pb.Next() {
|
||||
houseData := PlayerHouseData{
|
||||
CharacterID: int32(10000 + i),
|
||||
HouseID: int32((i % 10) + 1),
|
||||
InstanceID: int32(20000 + i),
|
||||
UpkeepDue: time.Now().Add(24 * time.Hour),
|
||||
EscrowCoins: 25000,
|
||||
EscrowStatus: 500,
|
||||
Status: HouseStatusActive,
|
||||
HouseName: fmt.Sprintf("Concurrent House %d", i),
|
||||
VisitPermission: 1,
|
||||
AllowFriends: true,
|
||||
ShowOnDirectory: true,
|
||||
}
|
||||
|
||||
_, err := dhm.AddPlayerHouse(ctx, houseData)
|
||||
if err != nil {
|
||||
b.Errorf("AddPlayerHouse failed: %v", err)
|
||||
}
|
||||
i++
|
||||
}
|
||||
})
|
||||
}
|
File diff suppressed because it is too large
Load Diff
816
internal/housing/database_test.go
Normal file
816
internal/housing/database_test.go
Normal file
@ -0,0 +1,816 @@
|
||||
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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user