package users import ( "os" "testing" "time" "dk/internal/database" ) func setupTestDB(t *testing.T) *database.DB { testDB := "test_users.db" t.Cleanup(func() { os.Remove(testDB) }) db, err := database.Open(testDB) if err != nil { t.Fatalf("Failed to open test database: %v", err) } // Create users table createTable := `CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, password TEXT NOT NULL, email TEXT NOT NULL, verified INTEGER NOT NULL DEFAULT 0, token TEXT NOT NULL DEFAULT '', registered INTEGER NOT NULL DEFAULT (unixepoch()), last_online INTEGER NOT NULL DEFAULT (unixepoch()), auth INTEGER NOT NULL DEFAULT 0, x INTEGER NOT NULL DEFAULT 0, y INTEGER NOT NULL DEFAULT 0, class_id INTEGER NOT NULL DEFAULT 0, currently TEXT NOT NULL DEFAULT 'In Town', fighting INTEGER NOT NULL DEFAULT 0, monster_id INTEGER NOT NULL DEFAULT 0, monster_hp INTEGER NOT NULL DEFAULT 0, monster_sleep INTEGER NOT NULL DEFAULT 0, monster_immune INTEGER NOT NULL DEFAULT 0, uber_damage INTEGER NOT NULL DEFAULT 0, uber_defense INTEGER NOT NULL DEFAULT 0, hp INTEGER NOT NULL DEFAULT 15, mp INTEGER NOT NULL DEFAULT 0, tp INTEGER NOT NULL DEFAULT 10, max_hp INTEGER NOT NULL DEFAULT 15, max_mp INTEGER NOT NULL DEFAULT 0, max_tp INTEGER NOT NULL DEFAULT 10, level INTEGER NOT NULL DEFAULT 1, gold INTEGER NOT NULL DEFAULT 100, exp INTEGER NOT NULL DEFAULT 0, gold_bonus INTEGER NOT NULL DEFAULT 0, exp_bonus INTEGER NOT NULL DEFAULT 0, strength INTEGER NOT NULL DEFAULT 5, dexterity INTEGER NOT NULL DEFAULT 5, attack INTEGER NOT NULL DEFAULT 5, defense INTEGER NOT NULL DEFAULT 5, weapon_id INTEGER NOT NULL DEFAULT 0, armor_id INTEGER NOT NULL DEFAULT 0, shield_id INTEGER NOT NULL DEFAULT 0, slot_1_id INTEGER NOT NULL DEFAULT 0, slot_2_id INTEGER NOT NULL DEFAULT 0, slot_3_id INTEGER NOT NULL DEFAULT 0, weapon_name TEXT NOT NULL DEFAULT '', armor_name TEXT NOT NULL DEFAULT '', shield_name TEXT NOT NULL DEFAULT '', slot_1_name TEXT NOT NULL DEFAULT '', slot_2_name TEXT NOT NULL DEFAULT '', slot_3_name TEXT NOT NULL DEFAULT '', drop_code INTEGER NOT NULL DEFAULT 0, spells TEXT NOT NULL DEFAULT '', towns TEXT NOT NULL DEFAULT '' )` if err := db.Exec(createTable); err != nil { t.Fatalf("Failed to create users table: %v", err) } // Insert test data with specific timestamps now := time.Now().Unix() testUsers := `INSERT INTO users (username, password, email, verified, token, registered, last_online, auth, x, y, class_id, level, gold, exp, hp, mp, tp, max_hp, max_mp, max_tp, strength, dexterity, attack, defense, spells, towns) VALUES ('alice', 'hashed_pass_1', 'alice@example.com', 1, '', ?, ?, 0, 10, 20, 1, 5, 500, 1250, 25, 15, 12, 25, 15, 12, 8, 7, 10, 8, '1,2,5', '1,2'), ('bob', 'hashed_pass_2', 'bob@example.com', 1, '', ?, ?, 2, -5, 15, 2, 3, 300, 750, 20, 8, 10, 20, 8, 10, 6, 8, 8, 9, '3,4', '1'), ('charlie', 'hashed_pass_3', 'charlie@example.com', 0, 'verify_token_123', ?, ?, 4, 0, 0, 3, 1, 100, 0, 15, 0, 10, 15, 0, 10, 5, 5, 5, 5, '', ''), ('diana', 'hashed_pass_4', 'diana@example.com', 1, '', ?, ?, 0, 25, -10, 1, 8, 1200, 3500, 35, 25, 15, 35, 25, 15, 12, 10, 15, 12, '1,2,3,6,7', '1,2,3,4')` timestamps := []any{ now - 86400*7, now - 3600*2, // alice: registered 1 week ago, last online 2 hours ago now - 86400*5, now - 86400*1, // bob: registered 5 days ago, last online 1 day ago now - 86400*1, now - 86400*1, // charlie: registered 1 day ago, last online 1 day ago now - 86400*30, now - 3600*1, // diana: registered 1 month ago, last online 1 hour ago } if err := db.Exec(testUsers, timestamps...); err != nil { t.Fatalf("Failed to insert test users: %v", err) } return db } func TestFind(t *testing.T) { db := setupTestDB(t) defer db.Close() // Test finding existing user user, err := Find(db, 1) if err != nil { t.Fatalf("Failed to find user: %v", err) } if user.ID != 1 { t.Errorf("Expected ID 1, got %d", user.ID) } if user.Username != "alice" { t.Errorf("Expected username 'alice', got '%s'", user.Username) } if user.Email != "alice@example.com" { t.Errorf("Expected email 'alice@example.com', got '%s'", user.Email) } if user.Verified != 1 { t.Errorf("Expected verified 1, got %d", user.Verified) } if user.Auth != 0 { t.Errorf("Expected auth 0, got %d", user.Auth) } if user.Level != 5 { t.Errorf("Expected level 5, got %d", user.Level) } // Test finding non-existent user _, err = Find(db, 999) if err == nil { t.Error("Expected error when finding non-existent user") } } func TestAll(t *testing.T) { db := setupTestDB(t) defer db.Close() users, err := All(db) if err != nil { t.Fatalf("Failed to get all users: %v", err) } if len(users) != 4 { t.Errorf("Expected 4 users, got %d", len(users)) } // Check ordering (by registered DESC) if len(users) >= 2 { if users[0].Registered < users[1].Registered { t.Error("Expected users to be ordered by registration date (newest first)") } } } func TestByUsername(t *testing.T) { db := setupTestDB(t) defer db.Close() // Test finding existing user by username user, err := ByUsername(db, "alice") if err != nil { t.Fatalf("Failed to find user by username: %v", err) } if user.Username != "alice" { t.Errorf("Expected username 'alice', got '%s'", user.Username) } if user.ID != 1 { t.Errorf("Expected ID 1, got %d", user.ID) } // Test case insensitive search userUpper, err := ByUsername(db, "ALICE") if err != nil { t.Fatalf("Failed to find user by uppercase username: %v", err) } if userUpper.ID != user.ID { t.Error("Expected case insensitive search to return same user") } // Test non-existent user _, err = ByUsername(db, "nonexistent") if err == nil { t.Error("Expected error when finding non-existent user by username") } } func TestByEmail(t *testing.T) { db := setupTestDB(t) defer db.Close() // Test finding existing user by email user, err := ByEmail(db, "bob@example.com") if err != nil { t.Fatalf("Failed to find user by email: %v", err) } if user.Email != "bob@example.com" { t.Errorf("Expected email 'bob@example.com', got '%s'", user.Email) } if user.Username != "bob" { t.Errorf("Expected username 'bob', got '%s'", user.Username) } // Test non-existent email _, err = ByEmail(db, "nonexistent@example.com") if err == nil { t.Error("Expected error when finding non-existent user by email") } } func TestByLevel(t *testing.T) { db := setupTestDB(t) defer db.Close() // Test users at level 1 level1Users, err := ByLevel(db, 1) if err != nil { t.Fatalf("Failed to get users by level: %v", err) } expectedCount := 1 // Charlie is level 1 if len(level1Users) != expectedCount { t.Errorf("Expected %d users at level 1, got %d", expectedCount, len(level1Users)) } // Verify all users are level 1 for _, user := range level1Users { if user.Level != 1 { t.Errorf("Expected level 1, got %d for user %s", user.Level, user.Username) } } // Test level with no users noUsers, err := ByLevel(db, 99) if err != nil { t.Fatalf("Failed to query non-existent level: %v", err) } if len(noUsers) != 0 { t.Errorf("Expected 0 users at level 99, got %d", len(noUsers)) } } func TestOnline(t *testing.T) { db := setupTestDB(t) defer db.Close() // Test users online within the last 6 hours onlineUsers, err := Online(db, 6*time.Hour) if err != nil { t.Fatalf("Failed to get online users: %v", err) } // Alice (2 hours ago) and Diana (1 hour ago) should be included expectedCount := 2 if len(onlineUsers) != expectedCount { t.Errorf("Expected %d users online within 6 hours, got %d", expectedCount, len(onlineUsers)) } // Check ordering (by last_online DESC) if len(onlineUsers) >= 2 { if onlineUsers[0].LastOnline < onlineUsers[1].LastOnline { t.Error("Expected online users to be ordered by last online time") } } // Test narrow time window recentUsers, err := Online(db, 30*time.Minute) if err != nil { t.Fatalf("Failed to get recently online users: %v", err) } // No users should be online within the last 30 minutes if len(recentUsers) != 0 { t.Errorf("Expected 0 users online within 30 minutes, got %d", len(recentUsers)) } } func TestBuilder(t *testing.T) { db := setupTestDB(t) defer db.Close() // Create new user using builder testTime := time.Now() user, err := NewBuilder(db). WithUsername("testuser"). WithPassword("hashed_password"). WithEmail("test@example.com"). WithVerified(true). WithAuth(2). WithClassID(2). WithPosition(50, -25). WithLevel(3). WithGold(250). WithStats(7, 6, 8, 7). WithHP(20, 20). WithMP(10, 10). WithTP(12, 12). WithCurrently("Exploring"). WithRegisteredTime(testTime). WithSpells([]string{"1", "3", "5"}). WithTowns([]string{"1", "2"}). Create() if err != nil { t.Fatalf("Failed to create user with builder: %v", err) } if user.ID == 0 { t.Error("Expected non-zero ID after creation") } if user.Username != "testuser" { t.Errorf("Expected username 'testuser', got '%s'", user.Username) } if user.Email != "test@example.com" { t.Errorf("Expected email 'test@example.com', got '%s'", user.Email) } if user.Verified != 1 { t.Errorf("Expected verified 1, got %d", user.Verified) } if user.Auth != 2 { t.Errorf("Expected auth 2, got %d", user.Auth) } if user.ClassID != 2 { t.Errorf("Expected class_id 2, got %d", user.ClassID) } if user.X != 50 || user.Y != -25 { t.Errorf("Expected position (50, -25), got (%d, %d)", user.X, user.Y) } if user.Level != 3 { t.Errorf("Expected level 3, got %d", user.Level) } if user.Gold != 250 { t.Errorf("Expected gold 250, got %d", user.Gold) } if user.Registered != testTime.Unix() { t.Errorf("Expected registered time %d, got %d", testTime.Unix(), user.Registered) } // Verify it was saved to database foundUser, err := Find(db, user.ID) if err != nil { t.Fatalf("Failed to find created user: %v", err) } if foundUser.Username != "testuser" { t.Errorf("Created user not found in database") } // Test builder with defaults defaultUser, err := NewBuilder(db). WithUsername("defaultuser"). WithPassword("password"). WithEmail("default@example.com"). Create() if err != nil { t.Fatalf("Failed to create user with defaults: %v", err) } // Should have default values if defaultUser.Level != 1 { t.Errorf("Expected default level 1, got %d", defaultUser.Level) } if defaultUser.Gold != 100 { t.Errorf("Expected default gold 100, got %d", defaultUser.Gold) } if defaultUser.HP != 15 { t.Errorf("Expected default HP 15, got %d", defaultUser.HP) } // Test convenience methods adminUser, err := NewBuilder(db). WithUsername("admin"). WithPassword("admin_pass"). WithEmail("admin@example.com"). AsAdmin(). Create() if err != nil { t.Fatalf("Failed to create admin user: %v", err) } if adminUser.Auth != 4 { t.Errorf("Expected admin auth 4, got %d", adminUser.Auth) } moderatorUser, err := NewBuilder(db). WithUsername("mod"). WithPassword("mod_pass"). WithEmail("mod@example.com"). AsModerator(). Create() if err != nil { t.Fatalf("Failed to create moderator user: %v", err) } if moderatorUser.Auth != 2 { t.Errorf("Expected moderator auth 2, got %d", moderatorUser.Auth) } } func TestSave(t *testing.T) { db := setupTestDB(t) defer db.Close() user, err := Find(db, 1) if err != nil { t.Fatalf("Failed to find user: %v", err) } // Modify user user.Username = "alice_updated" user.Email = "alice_updated@example.com" user.Level = 10 user.Gold = 1000 user.UpdateLastOnline() // Save changes err = user.Save() if err != nil { t.Fatalf("Failed to save user: %v", err) } // Verify changes were saved updatedUser, err := Find(db, 1) if err != nil { t.Fatalf("Failed to find updated user: %v", err) } if updatedUser.Username != "alice_updated" { t.Errorf("Expected updated username 'alice_updated', got '%s'", updatedUser.Username) } if updatedUser.Email != "alice_updated@example.com" { t.Errorf("Expected updated email, got '%s'", updatedUser.Email) } if updatedUser.Level != 10 { t.Errorf("Expected updated level 10, got %d", updatedUser.Level) } if updatedUser.Gold != 1000 { t.Errorf("Expected updated gold 1000, got %d", updatedUser.Gold) } } func TestDelete(t *testing.T) { db := setupTestDB(t) defer db.Close() user, err := Find(db, 1) if err != nil { t.Fatalf("Failed to find user: %v", err) } // Delete user err = user.Delete() if err != nil { t.Fatalf("Failed to delete user: %v", err) } // Verify user was deleted _, err = Find(db, 1) if err == nil { t.Error("Expected error when finding deleted user") } } func TestUserMethods(t *testing.T) { db := setupTestDB(t) defer db.Close() alice, _ := Find(db, 1) // verified, auth 0 bob, _ := Find(db, 2) // verified, auth 2 (moderator) charlie, _ := Find(db, 3) // unverified, auth 4 (admin) // Test time methods registeredTime := alice.RegisteredTime() if registeredTime.IsZero() { t.Error("Expected non-zero registered time") } lastOnlineTime := alice.LastOnlineTime() if lastOnlineTime.IsZero() { t.Error("Expected non-zero last online time") } // Test UpdateLastOnline originalLastOnline := alice.LastOnline alice.UpdateLastOnline() if alice.LastOnline <= originalLastOnline { t.Error("Expected last online to be updated to current time") } // Test verification status if !alice.IsVerified() { t.Error("Expected alice to be verified") } if charlie.IsVerified() { t.Error("Expected charlie not to be verified") } // Test authorization levels if alice.IsAdmin() { t.Error("Expected alice not to be admin") } if alice.IsModerator() { t.Error("Expected alice not to be moderator") } if !bob.IsModerator() { t.Error("Expected bob to be moderator") } if !charlie.IsAdmin() { t.Error("Expected charlie to be admin") } // Test combat status if alice.IsFighting() { t.Error("Expected alice not to be fighting") } if !alice.IsAlive() { t.Error("Expected alice to be alive") } // Test position x, y := alice.GetPosition() if x != 10 || y != 20 { t.Errorf("Expected position (10, 20), got (%d, %d)", x, y) } alice.SetPosition(30, 40) x, y = alice.GetPosition() if x != 30 || y != 40 { t.Errorf("Expected updated position (30, 40), got (%d, %d)", x, y) } } func TestSpellMethods(t *testing.T) { db := setupTestDB(t) defer db.Close() alice, _ := Find(db, 1) // spells: "1,2,5" // Test GetSpellIDs spells := alice.GetSpellIDs() expectedSpells := []string{"1", "2", "5"} if len(spells) != len(expectedSpells) { t.Errorf("Expected %d spells, got %d", len(expectedSpells), len(spells)) } for i, expected := range expectedSpells { if i < len(spells) && spells[i] != expected { t.Errorf("Expected spell '%s' at position %d, got '%s'", expected, i, spells[i]) } } // Test HasSpell if !alice.HasSpell("1") { t.Error("Expected alice to have spell '1'") } if !alice.HasSpell("2") { t.Error("Expected alice to have spell '2'") } if alice.HasSpell("3") { t.Error("Expected alice not to have spell '3'") } // Test SetSpellIDs newSpells := []string{"3", "4", "6", "7"} alice.SetSpellIDs(newSpells) if alice.Spells != "3,4,6,7" { t.Errorf("Expected spells '3,4,6,7', got '%s'", alice.Spells) } // Test with empty spells charlie, _ := Find(db, 3) // empty spells emptySpells := charlie.GetSpellIDs() if len(emptySpells) != 0 { t.Errorf("Expected 0 spells for empty list, got %d", len(emptySpells)) } } func TestTownMethods(t *testing.T) { db := setupTestDB(t) defer db.Close() alice, _ := Find(db, 1) // towns: "1,2" // Test GetTownIDs towns := alice.GetTownIDs() expectedTowns := []string{"1", "2"} if len(towns) != len(expectedTowns) { t.Errorf("Expected %d towns, got %d", len(expectedTowns), len(towns)) } for i, expected := range expectedTowns { if i < len(towns) && towns[i] != expected { t.Errorf("Expected town '%s' at position %d, got '%s'", expected, i, towns[i]) } } // Test HasVisitedTown if !alice.HasVisitedTown("1") { t.Error("Expected alice to have visited town '1'") } if !alice.HasVisitedTown("2") { t.Error("Expected alice to have visited town '2'") } if alice.HasVisitedTown("3") { t.Error("Expected alice not to have visited town '3'") } // Test SetTownIDs newTowns := []string{"1", "2", "3", "4"} alice.SetTownIDs(newTowns) if alice.Towns != "1,2,3,4" { t.Errorf("Expected towns '1,2,3,4', got '%s'", alice.Towns) } // Test with empty towns charlie, _ := Find(db, 3) // empty towns emptyTowns := charlie.GetTownIDs() if len(emptyTowns) != 0 { t.Errorf("Expected 0 towns for empty list, got %d", len(emptyTowns)) } } func TestGetEquipmentAndStats(t *testing.T) { db := setupTestDB(t) defer db.Close() alice, _ := Find(db, 1) // Test GetEquipment equipment := alice.GetEquipment() if equipment == nil { t.Error("Expected non-nil equipment map") } weapon, ok := equipment["weapon"].(map[string]any) if !ok { t.Error("Expected weapon to be a map") } if weapon["id"].(int) != alice.WeaponID { t.Errorf("Expected weapon ID %d, got %v", alice.WeaponID, weapon["id"]) } // Test GetStats stats := alice.GetStats() if stats == nil { t.Error("Expected non-nil stats map") } if stats["level"] != alice.Level { t.Errorf("Expected level %d, got %d", alice.Level, stats["level"]) } if stats["hp"] != alice.HP { t.Errorf("Expected HP %d, got %d", alice.HP, stats["hp"]) } if stats["strength"] != alice.Strength { t.Errorf("Expected strength %d, got %d", alice.Strength, stats["strength"]) } }