package guilds 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_guilds.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 guild tables for testing conn, err := pool.Take(context.Background()) if err != nil { t.Fatalf("Failed to get connection: %v", err) } defer pool.Put(conn) err = createGuildTables(conn) if err != nil { t.Fatalf("Failed to create guild tables: %v", err) } return pool } // createGuildTables creates the necessary tables for guild testing func createGuildTables(conn *sqlite.Conn) error { tables := []string{ `CREATE TABLE IF NOT EXISTS guilds ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, motd TEXT, level INTEGER DEFAULT 1, xp INTEGER DEFAULT 111, xp_needed INTEGER DEFAULT 2521, formed_on INTEGER DEFAULT 0 )`, `CREATE TABLE IF NOT EXISTS guild_members ( char_id INTEGER PRIMARY KEY, guild_id INTEGER NOT NULL, account_id INTEGER NOT NULL, recruiter_id INTEGER DEFAULT 0, name TEXT NOT NULL, guild_status INTEGER DEFAULT 0, points REAL DEFAULT 0.0, adventure_class INTEGER DEFAULT 0, adventure_level INTEGER DEFAULT 1, tradeskill_class INTEGER DEFAULT 0, tradeskill_level INTEGER DEFAULT 1, rank INTEGER DEFAULT 7, member_flags INTEGER DEFAULT 0, zone TEXT DEFAULT '', join_date INTEGER DEFAULT 0, last_login_date INTEGER DEFAULT 0, note TEXT DEFAULT '', officer_note TEXT DEFAULT '', recruiter_description TEXT DEFAULT '', recruiter_picture_data BLOB, recruiting_show_adventure_class INTEGER DEFAULT 0, FOREIGN KEY (guild_id) REFERENCES guilds(id) )`, `CREATE TABLE IF NOT EXISTS guild_events ( event_id INTEGER NOT NULL, guild_id INTEGER NOT NULL, date INTEGER NOT NULL, type INTEGER NOT NULL, description TEXT NOT NULL, locked INTEGER DEFAULT 0, PRIMARY KEY (event_id, guild_id), FOREIGN KEY (guild_id) REFERENCES guilds(id) )`, `CREATE TABLE IF NOT EXISTS guild_ranks ( guild_id INTEGER NOT NULL, rank INTEGER NOT NULL, name TEXT NOT NULL, PRIMARY KEY (guild_id, rank), FOREIGN KEY (guild_id) REFERENCES guilds(id) )`, `CREATE TABLE IF NOT EXISTS guild_permissions ( guild_id INTEGER NOT NULL, rank INTEGER NOT NULL, permission INTEGER NOT NULL, value INTEGER NOT NULL, PRIMARY KEY (guild_id, rank, permission), FOREIGN KEY (guild_id) REFERENCES guilds(id) )`, `CREATE TABLE IF NOT EXISTS guild_event_filters ( guild_id INTEGER NOT NULL, event_id INTEGER NOT NULL, category INTEGER NOT NULL, value INTEGER NOT NULL, PRIMARY KEY (guild_id, event_id, category), FOREIGN KEY (guild_id) REFERENCES guilds(id) )`, `CREATE TABLE IF NOT EXISTS guild_recruiting ( guild_id INTEGER NOT NULL, flag INTEGER NOT NULL, value INTEGER NOT NULL, PRIMARY KEY (guild_id, flag), FOREIGN KEY (guild_id) REFERENCES guilds(id) )`, `CREATE TABLE IF NOT EXISTS guild_point_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, char_id INTEGER NOT NULL, date INTEGER NOT NULL, modified_by TEXT NOT NULL, comment TEXT NOT NULL, points REAL NOT NULL, FOREIGN KEY (char_id) REFERENCES guild_members(char_id) )`, } for _, sql := range tables { err := sqlitex.ExecScript(conn, sql) if err != nil { return err } } return nil } // execSQL is a helper to execute SQL with parameters func execSQL(t *testing.T, pool *sqlitex.Pool, query string, args ...any) { conn, err := pool.Take(context.Background()) if err != nil { t.Fatalf("Failed to get connection: %v", err) } defer pool.Put(conn) stmt := conn.Prep(query) defer stmt.Finalize() for i, arg := range args { switch v := arg.(type) { case int: stmt.BindInt64(i+1, int64(v)) case int32: stmt.BindInt64(i+1, int64(v)) case int64: stmt.BindInt64(i+1, v) case float64: stmt.BindFloat(i+1, v) case string: stmt.BindText(i+1, v) case []byte: stmt.BindBytes(i+1, v) case nil: stmt.BindNull(i + 1) default: t.Fatalf("Unsupported argument type: %T", v) } } _, err = stmt.Step() if err != nil { t.Fatalf("Failed to execute SQL: %v", err) } } // TestDatabaseGuildManager_LoadGuilds tests loading guilds from database func TestDatabaseGuildManager_LoadGuilds(t *testing.T) { pool := createTestPool(t) defer pool.Close() dgm := NewDatabaseGuildManager(pool) ctx := context.Background() // Insert test data formedTime := time.Now().Unix() execSQL(t, pool, `INSERT INTO guilds (id, name, motd, level, xp, xp_needed, formed_on) VALUES (?, ?, ?, ?, ?, ?, ?)`, 1, "Test Guild", "Welcome!", 5, 1000, 5000, formedTime) // Test loading guilds guilds, err := dgm.LoadGuilds(ctx) if err != nil { t.Fatalf("Failed to load guilds: %v", err) } if len(guilds) != 1 { t.Errorf("Expected 1 guild, got %d", len(guilds)) } if len(guilds) > 0 { guild := guilds[0] if guild.ID != 1 { t.Errorf("Expected guild ID 1, got %d", guild.ID) } if guild.Name != "Test Guild" { t.Errorf("Expected guild name 'Test Guild', got '%s'", guild.Name) } if guild.MOTD != "Welcome!" { t.Errorf("Expected MOTD 'Welcome!', got '%s'", guild.MOTD) } if guild.Level != 5 { t.Errorf("Expected level 5, got %d", guild.Level) } } } // TestDatabaseGuildManager_LoadGuildMembers tests loading guild members func TestDatabaseGuildManager_LoadGuildMembers(t *testing.T) { pool := createTestPool(t) defer pool.Close() dgm := NewDatabaseGuildManager(pool) ctx := context.Background() // Insert test guild and members execSQL(t, pool, `INSERT INTO guilds (id, name) VALUES (?, ?)`, 1, "Test Guild") joinTime := time.Now().Unix() loginTime := time.Now().Unix() execSQL(t, pool, `INSERT INTO guild_members (char_id, guild_id, account_id, name, guild_status, points, adventure_class, adventure_level, tradeskill_class, tradeskill_level, rank, member_flags, zone, join_date, last_login_date, note, officer_note, recruiter_description, recruiter_picture_data, recruiting_show_adventure_class) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, 100, 1, 10, "TestPlayer", 1, 100.5, 5, 50, 2, 20, 3, 0, "Test Zone", joinTime, loginTime, "Player note", "Officer note", "Recruiter desc", []byte{0x01, 0x02, 0x03}, 1) // Test loading members members, err := dgm.LoadGuildMembers(ctx, 1) if err != nil { t.Fatalf("Failed to load guild members: %v", err) } if len(members) != 1 { t.Errorf("Expected 1 member, got %d", len(members)) } if len(members) > 0 { member := members[0] if member.CharacterID != 100 { t.Errorf("Expected character ID 100, got %d", member.CharacterID) } if member.Name != "TestPlayer" { t.Errorf("Expected name 'TestPlayer', got '%s'", member.Name) } if member.Points != 100.5 { t.Errorf("Expected points 100.5, got %f", member.Points) } if len(member.RecruiterPictureData) != 3 { t.Errorf("Expected picture data length 3, got %d", len(member.RecruiterPictureData)) } } } // TestDatabaseGuildManager_SaveGuild tests saving guild data func TestDatabaseGuildManager_SaveGuild(t *testing.T) { pool := createTestPool(t) defer pool.Close() dgm := NewDatabaseGuildManager(pool) ctx := context.Background() // Create a test guild guild := &Guild{ id: 1, name: "New Guild", motd: "Test MOTD", level: 10, expCurrent: 2000, expToNextLevel: 8000, formedDate: time.Now(), } // Save the guild err := dgm.SaveGuild(ctx, guild) if err != nil { t.Fatalf("Failed to save guild: %v", err) } // Load and verify guilds, err := dgm.LoadGuilds(ctx) if err != nil { t.Fatalf("Failed to load guilds: %v", err) } if len(guilds) != 1 { t.Errorf("Expected 1 guild, got %d", len(guilds)) } if len(guilds) > 0 { loaded := guilds[0] if loaded.Name != "New Guild" { t.Errorf("Expected name 'New Guild', got '%s'", loaded.Name) } if loaded.MOTD != "Test MOTD" { t.Errorf("Expected MOTD 'Test MOTD', got '%s'", loaded.MOTD) } if loaded.Level != 10 { t.Errorf("Expected level 10, got %d", loaded.Level) } } } // TestDatabaseGuildManager_CreateAndDeleteGuild tests guild creation and deletion func TestDatabaseGuildManager_CreateAndDeleteGuild(t *testing.T) { pool := createTestPool(t) defer pool.Close() dgm := NewDatabaseGuildManager(pool) ctx := context.Background() // Create guild data guildData := GuildData{ Name: "Delete Test Guild", MOTD: "To be deleted", Level: 1, EXPCurrent: 0, EXPToNextLevel: 1000, FormedDate: time.Now(), } // Create the guild guildID, err := dgm.CreateGuild(ctx, guildData) if err != nil { t.Fatalf("Failed to create guild: %v", err) } if guildID <= 0 { t.Errorf("Expected valid guild ID, got %d", guildID) } // Verify it exists guild, err := dgm.LoadGuild(ctx, guildID) if err != nil { t.Fatalf("Failed to load created guild: %v", err) } if guild.Name != "Delete Test Guild" { t.Errorf("Expected name 'Delete Test Guild', got '%s'", guild.Name) } // Delete the guild err = dgm.DeleteGuild(ctx, guildID) if err != nil { t.Fatalf("Failed to delete guild: %v", err) } // Verify it's gone _, err = dgm.LoadGuild(ctx, guildID) if err == nil { t.Error("Expected error loading deleted guild, got nil") } } // BenchmarkDatabaseGuildManager_LoadGuilds benchmarks loading guilds func BenchmarkDatabaseGuildManager_LoadGuilds(b *testing.B) { pool := createTestPool(&testing.T{}) defer pool.Close() dgm := NewDatabaseGuildManager(pool) ctx := context.Background() // Insert test data for i := 1; i <= 100; i++ { execSQL(&testing.T{}, pool, `INSERT INTO guilds (id, name, motd, level, xp, xp_needed, formed_on) VALUES (?, ?, ?, ?, ?, ?, ?)`, i, fmt.Sprintf("Guild %d", i), "Welcome!", i%10+1, i*100, i*1000, time.Now().Unix()) } b.ResetTimer() for i := 0; i < b.N; i++ { guilds, err := dgm.LoadGuilds(ctx) if err != nil { b.Fatalf("Failed to load guilds: %v", err) } if len(guilds) != 100 { b.Errorf("Expected 100 guilds, got %d", len(guilds)) } } } // BenchmarkDatabaseGuildManager_SaveGuild benchmarks saving guild data func BenchmarkDatabaseGuildManager_SaveGuild(b *testing.B) { pool := createTestPool(&testing.T{}) defer pool.Close() dgm := NewDatabaseGuildManager(pool) ctx := context.Background() // Create a test guild guild := &Guild{ id: 1, name: "Benchmark Guild", motd: "Benchmark MOTD", level: 50, expCurrent: 50000, expToNextLevel: 100000, formedDate: time.Now(), } b.ResetTimer() for i := 0; i < b.N; i++ { err := dgm.SaveGuild(ctx, guild) if err != nil { b.Fatalf("Failed to save guild: %v", err) } } }