package items import ( "database/sql" "testing" _ "zombiezen.com/go/sqlite" ) // setupTestDB creates a test database with minimal schema func setupTestDB(t *testing.T) *sql.DB { db, err := sql.Open("sqlite", ":memory:") if err != nil { t.Fatalf("Failed to open test database: %v", err) } // Create minimal test schema schema := ` CREATE TABLE items ( id INTEGER PRIMARY KEY, soe_id INTEGER DEFAULT 0, name TEXT NOT NULL, 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 character_items ( char_id INTEGER, item_id INTEGER, unique_id INTEGER PRIMARY KEY, inv_slot_id INTEGER, slot_id INTEGER, appearance_type INTEGER DEFAULT 0, icon INTEGER DEFAULT 0, icon2 INTEGER DEFAULT 0, count INTEGER DEFAULT 1, tier INTEGER DEFAULT 1, bag_id INTEGER DEFAULT 0, details_count INTEGER DEFAULT 1, creator TEXT DEFAULT '', adornment_slot0 INTEGER DEFAULT 0, adornment_slot1 INTEGER DEFAULT 0, adornment_slot2 INTEGER DEFAULT 0, group_id INTEGER DEFAULT 0, creator_app TEXT DEFAULT '', random_seed INTEGER DEFAULT 0, created TEXT DEFAULT CURRENT_TIMESTAMP ); 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 ); ` if _, err := db.Exec(schema); err != nil { t.Fatalf("Failed to create test schema: %v", err) } return db } // insertTestItem inserts a test item into the database func insertTestItem(t *testing.T, db *sql.DB, itemID int32, name string, itemType int8) { query := ` INSERT INTO items (id, name, generic_info_item_type) VALUES (?, ?, ?) ` _, err := db.Exec(query, itemID, name, itemType) if err != nil { t.Fatalf("Failed to insert test item: %v", err) } } func TestNewItemDatabase(t *testing.T) { db := setupTestDB(t) defer db.Close() idb := NewItemDatabase(db) if idb == nil { t.Fatal("Expected non-nil ItemDatabase") } if idb.db != db { t.Error("Expected database connection to be set") } if len(idb.queries) == 0 { t.Error("Expected queries to be prepared") } if len(idb.loadedItems) != 0 { t.Error("Expected loadedItems to be empty initially") } } func TestLoadItems(t *testing.T) { db := setupTestDB(t) defer db.Close() // Insert test items insertTestItem(t, db, 1, "Test Sword", ItemTypeWeapon) insertTestItem(t, db, 2, "Test Armor", ItemTypeArmor) insertTestItem(t, db, 3, "Test Bag", ItemTypeBag) // Add weapon details for sword _, err := db.Exec(` INSERT INTO item_details_weapon (item_id, damage_low1, damage_high1, delay_hundredths) VALUES (1, 10, 15, 250) `) if err != nil { t.Fatalf("Failed to insert weapon details: %v", err) } // Add armor details _, err = db.Exec(` INSERT INTO item_details_armor (item_id, mitigation_low, mitigation_high) VALUES (2, 5, 8) `) if err != nil { t.Fatalf("Failed to insert armor details: %v", err) } // Add bag details _, err = db.Exec(` INSERT INTO item_details_bag (item_id, num_slots, weight_reduction) VALUES (3, 6, 10) `) if err != nil { t.Fatalf("Failed to insert bag details: %v", err) } idb := NewItemDatabase(db) masterList := NewMasterItemList() err = idb.LoadItems(masterList) if err != nil { t.Fatalf("Failed to load items: %v", err) } if masterList.GetItemCount() != 3 { t.Errorf("Expected 3 items, got %d", masterList.GetItemCount()) } // Test specific items sword := masterList.GetItem(1) if sword == nil { t.Fatal("Expected to find sword item") } if sword.Name != "Test Sword" { t.Errorf("Expected sword name 'Test Sword', got '%s'", sword.Name) } if sword.WeaponInfo == nil { t.Error("Expected weapon info to be loaded") } else { if sword.WeaponInfo.DamageLow1 != 10 { t.Errorf("Expected damage low 10, got %d", sword.WeaponInfo.DamageLow1) } } armor := masterList.GetItem(2) if armor == nil { t.Fatal("Expected to find armor item") } if armor.ArmorInfo == nil { t.Error("Expected armor info to be loaded") } bag := masterList.GetItem(3) if bag == nil { t.Fatal("Expected to find bag item") } if bag.BagInfo == nil { t.Error("Expected bag info to be loaded") } } func TestLoadItemStats(t *testing.T) { db := setupTestDB(t) defer db.Close() // Insert test item insertTestItem(t, db, 1, "Test Item", ItemTypeNormal) // Add item stats _, err := db.Exec(` INSERT INTO item_mod_stats (item_id, stat_type, stat_subtype, value, stat_name) VALUES (1, 0, 0, 10.0, 'Strength') `) if err != nil { t.Fatalf("Failed to insert item stats: %v", err) } idb := NewItemDatabase(db) masterList := NewMasterItemList() err = idb.LoadItems(masterList) if err != nil { t.Fatalf("Failed to load items: %v", err) } item := masterList.GetItem(1) if item == nil { t.Fatal("Expected to find item") } if len(item.ItemStats) != 1 { t.Errorf("Expected 1 item stat, got %d", len(item.ItemStats)) } if item.ItemStats[0].StatName != "Strength" { t.Errorf("Expected stat name 'Strength', got '%s'", item.ItemStats[0].StatName) } if item.ItemStats[0].Value != 10.0 { t.Errorf("Expected stat value 10.0, got %f", item.ItemStats[0].Value) } } func TestSaveAndLoadCharacterItems(t *testing.T) { db := setupTestDB(t) defer db.Close() // Insert test item template insertTestItem(t, db, 1, "Test Item", ItemTypeNormal) idb := NewItemDatabase(db) masterList := NewMasterItemList() // Load item templates err := idb.LoadItems(masterList) if err != nil { t.Fatalf("Failed to load items: %v", err) } // Create test character items inventory := NewPlayerItemList() equipment := NewEquipmentItemList() // Create an item instance template := masterList.GetItem(1) if template == nil { t.Fatal("Expected to find item template") } item := NewItemFromTemplate(template) item.Details.InvSlotID = 1000 // Inventory slot item.Details.Count = 5 inventory.AddItem(item) // Save character items charID := uint32(123) err = idb.SaveCharacterItems(charID, inventory, equipment) if err != nil { t.Fatalf("Failed to save character items: %v", err) } // Load character items loadedInventory, loadedEquipment, err := idb.LoadCharacterItems(charID, masterList) if err != nil { t.Fatalf("Failed to load character items: %v", err) } if loadedInventory.GetNumberOfItems() != 1 { t.Errorf("Expected 1 inventory item, got %d", loadedInventory.GetNumberOfItems()) } if loadedEquipment.GetNumberOfItems() != 0 { t.Errorf("Expected 0 equipped items, got %d", loadedEquipment.GetNumberOfItems()) } // Verify item properties allItems := loadedInventory.GetAllItems() if len(allItems) != 1 { t.Fatalf("Expected 1 item in all items, got %d", len(allItems)) } loadedItem := allItems[int32(item.Details.UniqueID)] if loadedItem == nil { t.Fatal("Expected to find loaded item") } if loadedItem.Details.Count != 5 { t.Errorf("Expected item count 5, got %d", loadedItem.Details.Count) } if loadedItem.Name != "Test Item" { t.Errorf("Expected item name 'Test Item', got '%s'", loadedItem.Name) } } func TestDeleteCharacterItem(t *testing.T) { db := setupTestDB(t) defer db.Close() // Insert test character item directly charID := uint32(123) uniqueID := int64(456) _, err := db.Exec(` INSERT INTO character_items (char_id, item_id, unique_id, inv_slot_id, slot_id, count) VALUES (?, 1, ?, 1000, 0, 1) `, charID, uniqueID) if err != nil { t.Fatalf("Failed to insert test character item: %v", err) } idb := NewItemDatabase(db) // Delete the item err = idb.DeleteCharacterItem(charID, uniqueID) if err != nil { t.Fatalf("Failed to delete character item: %v", err) } // Verify item was deleted var count int err = db.QueryRow("SELECT COUNT(*) FROM character_items WHERE char_id = ? AND unique_id = ?", charID, uniqueID).Scan(&count) if err != nil { t.Fatalf("Failed to query character items: %v", err) } if count != 0 { t.Errorf("Expected 0 items after deletion, got %d", count) } } func TestGetCharacterItemCount(t *testing.T) { db := setupTestDB(t) defer db.Close() charID := uint32(123) idb := NewItemDatabase(db) // Initially should be 0 count, err := idb.GetCharacterItemCount(charID) if err != nil { t.Fatalf("Failed to get character item count: %v", err) } if count != 0 { t.Errorf("Expected 0 items initially, got %d", count) } // Insert test items for i := 0; i < 3; i++ { _, err := db.Exec(` INSERT INTO character_items (char_id, item_id, unique_id, inv_slot_id, slot_id, count) VALUES (?, 1, ?, 1000, 0, 1) `, charID, i+1) if err != nil { t.Fatalf("Failed to insert test character item: %v", err) } } // Should now be 3 count, err = idb.GetCharacterItemCount(charID) if err != nil { t.Fatalf("Failed to get character item count: %v", err) } if count != 3 { t.Errorf("Expected 3 items, got %d", count) } } func TestNextUniqueItemID(t *testing.T) { id1 := NextUniqueItemID() id2 := NextUniqueItemID() if id1 >= id2 { t.Errorf("Expected unique IDs to be increasing, got %d and %d", id1, id2) } if id1 == id2 { t.Error("Expected unique IDs to be different") } } func TestItemDatabaseClose(t *testing.T) { db := setupTestDB(t) defer db.Close() idb := NewItemDatabase(db) // Should not error when closing err := idb.Close() if err != nil { t.Errorf("Expected no error when closing, got: %v", err) } }