package items import ( "context" "fmt" "math/rand" "testing" "zombiezen.com/go/sqlite/sqlitex" ) // setupTestDB creates a test database with minimal schema func setupTestDB(t *testing.T) *sqlitex.Pool { // Create unique database name to avoid test contamination dbName := fmt.Sprintf("file:test_%s_%d.db?mode=memory&cache=shared", t.Name(), rand.Int63()) pool, err := sqlitex.NewPool(dbName, sqlitex.PoolOptions{ PoolSize: 10, }) if err != nil { t.Fatalf("Failed to create test database pool: %v", err) } // Create complete test schema matching the real database structure schema := ` CREATE TABLE items ( id INTEGER PRIMARY KEY, soe_id INTEGER DEFAULT 0, name TEXT NOT NULL DEFAULT '', description TEXT DEFAULT '', icon INTEGER DEFAULT 0, icon2 INTEGER DEFAULT 0, icon_heroic_op INTEGER DEFAULT 0, icon_heroic_op2 INTEGER DEFAULT 0, icon_id INTEGER DEFAULT 0, icon_backdrop INTEGER DEFAULT 0, icon_border INTEGER DEFAULT 0, icon_tint_red INTEGER DEFAULT 0, icon_tint_green INTEGER DEFAULT 0, icon_tint_blue INTEGER DEFAULT 0, tier INTEGER DEFAULT 1, level INTEGER DEFAULT 1, success_sellback INTEGER DEFAULT 0, stack_size INTEGER DEFAULT 1, generic_info_show_name INTEGER DEFAULT 1, generic_info_item_flags INTEGER DEFAULT 0, generic_info_item_flags2 INTEGER DEFAULT 0, generic_info_creator_flag INTEGER DEFAULT 0, generic_info_condition INTEGER DEFAULT 100, generic_info_weight INTEGER DEFAULT 10, generic_info_skill_req1 INTEGER DEFAULT 0, generic_info_skill_req2 INTEGER DEFAULT 0, generic_info_skill_min_level INTEGER DEFAULT 0, generic_info_item_type INTEGER DEFAULT 0, generic_info_appearance_id INTEGER DEFAULT 0, generic_info_appearance_red INTEGER DEFAULT 0, generic_info_appearance_green INTEGER DEFAULT 0, generic_info_appearance_blue INTEGER DEFAULT 0, generic_info_appearance_highlight_red INTEGER DEFAULT 0, generic_info_appearance_highlight_green INTEGER DEFAULT 0, generic_info_appearance_highlight_blue INTEGER DEFAULT 0, generic_info_collectable INTEGER DEFAULT 0, generic_info_offers_quest_id INTEGER DEFAULT 0, generic_info_part_of_quest_id INTEGER DEFAULT 0, generic_info_max_charges INTEGER DEFAULT 0, generic_info_adventure_classes INTEGER DEFAULT 0, generic_info_tradeskill_classes INTEGER DEFAULT 0, generic_info_adventure_default_level INTEGER DEFAULT 1, generic_info_tradeskill_default_level INTEGER DEFAULT 1, generic_info_usable INTEGER DEFAULT 0, generic_info_harvest INTEGER DEFAULT 0, generic_info_body_drop INTEGER DEFAULT 0, generic_info_pvp_description INTEGER DEFAULT 0, generic_info_merc_only INTEGER DEFAULT 0, generic_info_mount_only INTEGER DEFAULT 0, generic_info_set_id INTEGER DEFAULT 0, generic_info_collectable_unk INTEGER DEFAULT 0, generic_info_transmuted_material INTEGER DEFAULT 0, broker_price INTEGER DEFAULT 0, sell_price INTEGER DEFAULT 0, max_sell_value INTEGER DEFAULT 0, created TEXT DEFAULT CURRENT_TIMESTAMP, script_name TEXT DEFAULT '', lua_script TEXT DEFAULT '' ); CREATE TABLE item_mod_stats ( item_id INTEGER, stat_type INTEGER, stat_subtype INTEGER DEFAULT 0, value REAL, stat_name TEXT DEFAULT '', level INTEGER DEFAULT 0 ); CREATE TABLE item_effects ( item_id INTEGER, effect TEXT, percentage INTEGER DEFAULT 0, subbulletflag INTEGER DEFAULT 0 ); CREATE TABLE item_appearances ( item_id INTEGER, type INTEGER, red INTEGER DEFAULT 0, green INTEGER DEFAULT 0, blue INTEGER DEFAULT 0, highlight_red INTEGER DEFAULT 0, highlight_green INTEGER DEFAULT 0, highlight_blue INTEGER DEFAULT 0 ); CREATE TABLE item_levels_override ( item_id INTEGER, adventure_class INTEGER, tradeskill_class INTEGER, level INTEGER ); CREATE TABLE item_mod_strings ( item_id INTEGER, stat_string TEXT ); CREATE TABLE item_details_weapon ( item_id INTEGER PRIMARY KEY, wield_type INTEGER DEFAULT 2, damage_low1 INTEGER DEFAULT 1, damage_high1 INTEGER DEFAULT 2, damage_low2 INTEGER DEFAULT 0, damage_high2 INTEGER DEFAULT 0, damage_low3 INTEGER DEFAULT 0, damage_high3 INTEGER DEFAULT 0, delay_hundredths INTEGER DEFAULT 300, rating REAL DEFAULT 1.0 ); CREATE TABLE item_details_armor ( item_id INTEGER PRIMARY KEY, mitigation_low INTEGER DEFAULT 1, mitigation_high INTEGER DEFAULT 2 ); CREATE TABLE item_details_bag ( item_id INTEGER PRIMARY KEY, num_slots INTEGER DEFAULT 6, weight_reduction INTEGER DEFAULT 0 ); ` // Execute schema on connection ctx := context.Background() conn, err := pool.Take(ctx) if err != nil { t.Fatalf("Failed to get connection: %v", err) } defer pool.Put(conn) if err := sqlitex.ExecuteScript(conn, schema, nil); err != nil { t.Fatalf("Failed to create test schema: %v", err) } return pool } func TestNewItemDatabase(t *testing.T) { pool := setupTestDB(t) defer pool.Close() idb := NewItemDatabase(pool) if idb == nil { t.Fatal("Expected non-nil ItemDatabase") } if idb.pool == nil { t.Fatal("Expected non-nil database pool") } } func TestItemDatabaseBasicOperation(t *testing.T) { pool := setupTestDB(t) defer pool.Close() idb := NewItemDatabase(pool) masterList := NewMasterItemList() // Test that LoadItems doesn't crash (even with empty database) err := idb.LoadItems(masterList) if err != nil { t.Fatalf("LoadItems should not fail with empty database: %v", err) } if masterList.GetItemCount() != 0 { t.Errorf("Expected empty master list, got %d items", masterList.GetItemCount()) } } func TestItemDatabaseWithData(t *testing.T) { pool := setupTestDB(t) defer pool.Close() // Insert test data ctx := context.Background() conn, err := pool.Take(ctx) if err != nil { t.Fatalf("Failed to get connection: %v", err) } defer pool.Put(conn) // Insert a test item err = sqlitex.Execute(conn, `INSERT INTO items (id, name, generic_info_item_type) VALUES (?, ?, ?)`, &sqlitex.ExecOptions{ Args: []any{1, "Test Sword", ItemTypeWeapon}, }) if err != nil { t.Fatalf("Failed to insert test item: %v", err) } // Insert weapon details err = sqlitex.Execute(conn, `INSERT INTO item_details_weapon (item_id, damage_low1, damage_high1) VALUES (?, ?, ?)`, &sqlitex.ExecOptions{ Args: []any{1, 10, 15}, }) if err != nil { t.Fatalf("Failed to insert weapon details: %v", err) } // Load items idb := NewItemDatabase(pool) masterList := NewMasterItemList() err = idb.LoadItems(masterList) if err != nil { t.Fatalf("Failed to load items: %v", err) } if masterList.GetItemCount() != 1 { t.Errorf("Expected 1 item, got %d items", masterList.GetItemCount()) } // Verify the item was loaded correctly item := masterList.GetItem(1) if item == nil { t.Fatal("Expected to find item with ID 1") } if item.Name != "Test Sword" { t.Errorf("Expected item name 'Test Sword', got '%s'", item.Name) } if item.WeaponInfo == nil { t.Error("Expected weapon info to be loaded") } else { if item.WeaponInfo.DamageLow1 != 10 { t.Errorf("Expected damage low 10, got %d", item.WeaponInfo.DamageLow1) } if item.WeaponInfo.DamageHigh1 != 15 { t.Errorf("Expected damage high 15, got %d", item.WeaponInfo.DamageHigh1) } } }