eq2go/internal/items/item_db_test.go

501 lines
13 KiB
Go

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)
}
}