diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 3df84c9..636897a 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -17,13 +17,13 @@ type User struct { type AuthManager struct { sessionStore *SessionStore - db *database.DB + db *database.DB } func NewAuthManager(db *database.DB, sessionsFilePath string) *AuthManager { return &AuthManager{ sessionStore: NewSessionStore(sessionsFilePath), - db: db, + db: db, } } @@ -35,17 +35,17 @@ func InitializeManager(db *database.DB, sessionsFilePath string) { func (am *AuthManager) Authenticate(usernameOrEmail, plainPassword string) (*User, error) { var user *users.User var err error - + // Try to find user by username first - user, err = users.GetByUsername(am.db, usernameOrEmail) + user, err = users.GetByUsername(usernameOrEmail) if err != nil { // Try by email if username lookup failed - user, err = users.GetByEmail(am.db, usernameOrEmail) + user, err = users.GetByEmail(usernameOrEmail) if err != nil { return nil, err } } - + // Verify password isValid, err := password.Verify(plainPassword, user.Password) if err != nil { @@ -54,7 +54,7 @@ func (am *AuthManager) Authenticate(usernameOrEmail, plainPassword string) (*Use if !isValid { return nil, ErrInvalidCredentials } - + return &User{ ID: user.ID, Username: user.Username, @@ -102,4 +102,4 @@ type AuthError struct { func (e *AuthError) Error() string { return e.Message -} \ No newline at end of file +} diff --git a/internal/babble/babble_test.go b/internal/babble/babble_test.go deleted file mode 100644 index 27b7aa4..0000000 --- a/internal/babble/babble_test.go +++ /dev/null @@ -1,625 +0,0 @@ -package babble - -import ( - "os" - "testing" - "time" - - "dk/internal/database" -) - -func setupTestDB(t *testing.T) *database.DB { - testDB := "test_babble.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 babble table - createTable := `CREATE TABLE babble ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - posted INTEGER NOT NULL DEFAULT (unixepoch()), - author TEXT NOT NULL DEFAULT '', - babble TEXT NOT NULL DEFAULT '' - )` - - if err := db.Exec(createTable); err != nil { - t.Fatalf("Failed to create babble table: %v", err) - } - - // Insert test data with specific timestamps for predictable testing - now := time.Now().Unix() - testBabble := `INSERT INTO babble (posted, author, babble) VALUES - (?, 'Alice', 'Hello everyone! Welcome to the game'), - (?, 'Bob', 'Thanks Alice! @Alice this game is great'), - (?, 'Charlie', 'Anyone want to team up for the dungeon?'), - (?, 'Alice', 'I can help @Charlie, let me know'), - (?, 'David', 'Server lag is really bad right now...'), - (?, 'Eve', 'Quick question about spell mechanics')` - - timestamps := []any{ - now - 3600*6, // 6 hours ago - now - 3600*4, // 4 hours ago - now - 3600*2, // 2 hours ago - now - 3600*1, // 1 hour ago - now - 1800, // 30 minutes ago - now - 300, // 5 minutes ago - } - - if err := db.Exec(testBabble, timestamps...); err != nil { - t.Fatalf("Failed to insert test babble: %v", err) - } - - return db -} - -func TestFind(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test finding existing babble - babble, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find babble: %v", err) - } - - if babble.ID != 1 { - t.Errorf("Expected ID 1, got %d", babble.ID) - } - if babble.Author != "Alice" { - t.Errorf("Expected author 'Alice', got '%s'", babble.Author) - } - if babble.Babble != "Hello everyone! Welcome to the game" { - t.Errorf("Expected specific message, got '%s'", babble.Babble) - } - if babble.Posted == 0 { - t.Error("Expected non-zero posted timestamp") - } - - // Test finding non-existent babble - _, err = Find(db, 999) - if err == nil { - t.Error("Expected error when finding non-existent babble") - } -} - -func TestAll(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - babbles, err := All(db) - if err != nil { - t.Fatalf("Failed to get all babble: %v", err) - } - - if len(babbles) != 6 { - t.Errorf("Expected 6 babble messages, got %d", len(babbles)) - } - - // Check ordering (newest first) - if len(babbles) >= 2 { - if babbles[0].Posted < babbles[1].Posted { - t.Error("Expected babble to be ordered by posted time (newest first)") - } - } - - // First message should be the most recent (5 minutes ago) - if babbles[0].Author != "Eve" { - t.Errorf("Expected newest message from Eve, got from '%s'", babbles[0].Author) - } -} - -func TestByAuthor(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test messages by Alice - aliceMessages, err := ByAuthor(db, "Alice") - if err != nil { - t.Fatalf("Failed to get babble by author: %v", err) - } - - if len(aliceMessages) != 2 { - t.Errorf("Expected 2 messages by Alice, got %d", len(aliceMessages)) - } - - // Verify all messages are by Alice - for _, message := range aliceMessages { - if message.Author != "Alice" { - t.Errorf("Expected author 'Alice', got '%s'", message.Author) - } - } - - // Check ordering (newest first) - if len(aliceMessages) == 2 { - if aliceMessages[0].Babble != "I can help @Charlie, let me know" { - t.Errorf("Expected newest message by Alice first") - } - } - - // Test case insensitive search - aliceMessagesLower, err := ByAuthor(db, "alice") - if err != nil { - t.Fatalf("Failed to get babble by lowercase author: %v", err) - } - - if len(aliceMessagesLower) != 2 { - t.Errorf("Expected case insensitive search to find 2 messages, got %d", len(aliceMessagesLower)) - } - - // Test author with no messages - noMessages, err := ByAuthor(db, "NonexistentUser") - if err != nil { - t.Fatalf("Failed to query non-existent author: %v", err) - } - - if len(noMessages) != 0 { - t.Errorf("Expected 0 messages by non-existent author, got %d", len(noMessages)) - } -} - -func TestRecent(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test getting 3 most recent messages - recentMessages, err := Recent(db, 3) - if err != nil { - t.Fatalf("Failed to get recent babble: %v", err) - } - - if len(recentMessages) != 3 { - t.Errorf("Expected 3 recent messages, got %d", len(recentMessages)) - } - - // Check ordering (newest first) - if len(recentMessages) >= 2 { - if recentMessages[0].Posted < recentMessages[1].Posted { - t.Error("Expected recent messages to be ordered newest first") - } - } - - // Test getting more messages than exist - allRecentMessages, err := Recent(db, 10) - if err != nil { - t.Fatalf("Failed to get recent babble with high limit: %v", err) - } - - if len(allRecentMessages) != 6 { - t.Errorf("Expected 6 messages (all available), got %d", len(allRecentMessages)) - } -} - -func TestSince(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test messages since 3 hours ago - threeHoursAgo := time.Now().Add(-3 * time.Hour).Unix() - recentMessages, err := Since(db, threeHoursAgo) - if err != nil { - t.Fatalf("Failed to get babble since timestamp: %v", err) - } - - // Should get messages from 2 hours ago, 1 hour ago, 30 minutes ago, and 5 minutes ago - expectedCount := 4 - if len(recentMessages) != expectedCount { - t.Errorf("Expected %d messages since 3 hours ago, got %d", expectedCount, len(recentMessages)) - } - - // Verify all messages are since the timestamp - for _, message := range recentMessages { - if message.Posted < threeHoursAgo { - t.Errorf("Message with timestamp %d is before the 'since' timestamp %d", message.Posted, threeHoursAgo) - } - } - - // Test with future timestamp (should return no messages) - futureMessages, err := Since(db, time.Now().Add(time.Hour).Unix()) - if err != nil { - t.Fatalf("Failed to query future timestamp: %v", err) - } - - if len(futureMessages) != 0 { - t.Errorf("Expected 0 messages since future timestamp, got %d", len(futureMessages)) - } -} - -func TestBetween(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test messages between 5 hours ago and 1 hour ago - start := time.Now().Add(-5 * time.Hour).Unix() - end := time.Now().Add(-1 * time.Hour).Unix() - - betweenMessages, err := Between(db, start, end) - if err != nil { - t.Fatalf("Failed to get babble between timestamps: %v", err) - } - - // Should get messages from 4 hours ago, 2 hours ago, and 1 hour ago (inclusive end) - expectedCount := 3 - if len(betweenMessages) != expectedCount { - t.Errorf("Expected %d messages between timestamps, got %d", expectedCount, len(betweenMessages)) - } - - // Verify all messages are within the range - for _, message := range betweenMessages { - if message.Posted < start || message.Posted > end { - t.Errorf("Message with timestamp %d is outside range [%d, %d]", message.Posted, start, end) - } - } -} - -func TestSearch(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test searching for "game" - gameMessages, err := Search(db, "game") - if err != nil { - t.Fatalf("Failed to search babble: %v", err) - } - - expectedCount := 2 // Alice's welcome message and Bob's response - if len(gameMessages) != expectedCount { - t.Errorf("Expected %d messages containing 'game', got %d", expectedCount, len(gameMessages)) - } - - // Verify all messages contain the search term - for _, message := range gameMessages { - if !message.Contains("game") { - t.Errorf("Message '%s' does not contain search term 'game'", message.Babble) - } - } - - // Test case insensitive search - gameMessagesUpper, err := Search(db, "GAME") - if err != nil { - t.Fatalf("Failed to search babble with uppercase: %v", err) - } - - if len(gameMessagesUpper) != expectedCount { - t.Error("Expected case insensitive search to find same results") - } - - // Test search with no results - noResults, err := Search(db, "nonexistentterm") - if err != nil { - t.Fatalf("Failed to search for non-existent term: %v", err) - } - - if len(noResults) != 0 { - t.Errorf("Expected 0 results for non-existent term, got %d", len(noResults)) - } -} - -func TestRecentByAuthor(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test recent messages by Alice (limit 1) - aliceRecent, err := RecentByAuthor(db, "Alice", 1) - if err != nil { - t.Fatalf("Failed to get recent babble by author: %v", err) - } - - if len(aliceRecent) != 1 { - t.Errorf("Expected 1 recent message by Alice, got %d", len(aliceRecent)) - } - - if len(aliceRecent) > 0 && aliceRecent[0].Babble != "I can help @Charlie, let me know" { - t.Error("Expected most recent message by Alice") - } - - // Test with higher limit - aliceAll, err := RecentByAuthor(db, "Alice", 5) - if err != nil { - t.Fatalf("Failed to get all recent messages by Alice: %v", err) - } - - if len(aliceAll) != 2 { - t.Errorf("Expected 2 total messages by Alice, got %d", len(aliceAll)) - } -} - -func TestBuilder(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Create new babble using builder - testTime := time.Now() - babble, err := NewBuilder(db). - WithAuthor("TestUser"). - WithBabble("Test message from builder"). - WithPostedTime(testTime). - Create() - - if err != nil { - t.Fatalf("Failed to create babble with builder: %v", err) - } - - if babble.ID == 0 { - t.Error("Expected non-zero ID after creation") - } - if babble.Author != "TestUser" { - t.Errorf("Expected author 'TestUser', got '%s'", babble.Author) - } - if babble.Babble != "Test message from builder" { - t.Errorf("Expected specific message, got '%s'", babble.Babble) - } - if babble.Posted != testTime.Unix() { - t.Errorf("Expected posted time %d, got %d", testTime.Unix(), babble.Posted) - } - - // Test WithMessage alias - babble2, err := NewBuilder(db). - WithAuthor("TestUser2"). - WithMessage("Using WithMessage alias"). - Create() - - if err != nil { - t.Fatalf("Failed to create babble with WithMessage: %v", err) - } - - if babble2.Babble != "Using WithMessage alias" { - t.Errorf("WithMessage alias failed, got '%s'", babble2.Babble) - } - - // Verify it was saved to database - foundBabble, err := Find(db, babble.ID) - if err != nil { - t.Fatalf("Failed to find created babble: %v", err) - } - - if foundBabble.Babble != "Test message from builder" { - t.Errorf("Created babble not found in database") - } - - // Test builder with default timestamp - defaultBabble, err := NewBuilder(db). - WithAuthor("DefaultUser"). - WithBabble("Message with default timestamp"). - Create() - - if err != nil { - t.Fatalf("Failed to create babble with default timestamp: %v", err) - } - - // Should have recent timestamp (within last minute) - if time.Since(defaultBabble.PostedTime()) > time.Minute { - t.Error("Expected default timestamp to be recent") - } -} - -func TestSave(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - babble, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find babble: %v", err) - } - - // Modify babble - babble.Author = "UpdatedAuthor" - babble.Babble = "Updated message content" - babble.Posted = time.Now().Unix() - - // Save changes - err = babble.Save() - if err != nil { - t.Fatalf("Failed to save babble: %v", err) - } - - // Verify changes were saved - updatedBabble, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find updated babble: %v", err) - } - - if updatedBabble.Author != "UpdatedAuthor" { - t.Errorf("Expected updated author 'UpdatedAuthor', got '%s'", updatedBabble.Author) - } - if updatedBabble.Babble != "Updated message content" { - t.Errorf("Expected updated message, got '%s'", updatedBabble.Babble) - } -} - -func TestDelete(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - babble, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find babble: %v", err) - } - - // Delete babble - err = babble.Delete() - if err != nil { - t.Fatalf("Failed to delete babble: %v", err) - } - - // Verify babble was deleted - _, err = Find(db, 1) - if err == nil { - t.Error("Expected error when finding deleted babble") - } -} - -func TestUtilityMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - babble, _ := Find(db, 1) - - // Test PostedTime - postedTime := babble.PostedTime() - if postedTime.IsZero() { - t.Error("Expected non-zero posted time") - } - - // Test SetPostedTime - newTime := time.Now().Add(-30 * time.Minute) - babble.SetPostedTime(newTime) - if babble.Posted != newTime.Unix() { - t.Errorf("Expected posted timestamp %d, got %d", newTime.Unix(), babble.Posted) - } - - // Test IsRecent (should be true for 30 minutes ago) - if !babble.IsRecent() { - t.Error("Expected message from 30 minutes ago to be recent") - } - - // Test Age - age := babble.Age() - if age < 0 { - t.Error("Expected positive age") - } - - // Test IsAuthor - if !babble.IsAuthor("Alice") { - t.Error("Expected IsAuthor to return true for correct author") - } - if !babble.IsAuthor("alice") { // Test case insensitive - t.Error("Expected IsAuthor to be case insensitive") - } - if babble.IsAuthor("Bob") { - t.Error("Expected IsAuthor to return false for incorrect author") - } - - // Test Preview - longMessage := "This is a very long chat message that should be truncated when preview is called for display purposes" - babble.Babble = longMessage - - preview := babble.Preview(20) - if len(preview) > 20 { - t.Errorf("Expected preview length <= 20, got %d", len(preview)) - } - if preview[len(preview)-3:] != "..." { - t.Error("Expected preview to end with ellipsis") - } - - shortPreview := babble.Preview(200) // Longer than message - if shortPreview != longMessage { - t.Error("Expected short message to not be truncated") - } - - // Test WordCount - babble.Babble = "This is a test with five words" - wordCount := babble.WordCount() - if wordCount != 7 { - t.Errorf("Expected 7 words, got %d", wordCount) - } - - // Test Length - expectedLength := len(babble.Babble) - if babble.Length() != expectedLength { - t.Errorf("Expected length %d, got %d", expectedLength, babble.Length()) - } - - // Test Contains - if !babble.Contains("test") { - t.Error("Expected message to contain 'test'") - } - if !babble.Contains("TEST") { // Case insensitive - t.Error("Expected Contains to be case insensitive") - } - if babble.Contains("nonexistent") { - t.Error("Expected message not to contain 'nonexistent'") - } - - // Test IsEmpty - babble.Babble = "" - if !babble.IsEmpty() { - t.Error("Expected empty message to be empty") - } - - babble.Babble = " " - if !babble.IsEmpty() { - t.Error("Expected whitespace-only message to be empty") - } - - babble.Babble = "Not empty" - if babble.IsEmpty() { - t.Error("Expected non-empty message not to be empty") - } - - // Test IsLongMessage - shortMsg := "Short" - babble.Babble = shortMsg - if babble.IsLongMessage(100) { - t.Error("Expected short message not to be long") - } - if !babble.IsLongMessage(3) { - t.Error("Expected message longer than threshold to be long") - } -} - -func TestMentionMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test GetMentions - babble, _ := Find(db, 2) // Bob's message: "Thanks Alice! @Alice this game is great" - mentions := babble.GetMentions() - - expectedMentions := []string{"Alice"} - if len(mentions) != len(expectedMentions) { - t.Errorf("Expected %d mentions, got %d", len(expectedMentions), len(mentions)) - } - - for i, expected := range expectedMentions { - if i < len(mentions) && mentions[i] != expected { - t.Errorf("Expected mention '%s' at position %d, got '%s'", expected, i, mentions[i]) - } - } - - // Test HasMention - if !babble.HasMention("Alice") { - t.Error("Expected message to mention Alice") - } - if !babble.HasMention("alice") { // Case insensitive - t.Error("Expected HasMention to be case insensitive") - } - if babble.HasMention("Bob") { - t.Error("Expected message not to mention Bob") - } - - // Test message with multiple mentions and punctuation - babble.Babble = "Hey @Alice, @Bob! Can you help @Charlie?" - mentions = babble.GetMentions() - expectedMentions = []string{"Alice", "Bob", "Charlie"} - - if len(mentions) != len(expectedMentions) { - t.Errorf("Expected %d mentions, got %d: %v", len(expectedMentions), len(mentions), mentions) - } - - for _, expected := range expectedMentions { - if !babble.HasMention(expected) { - t.Errorf("Expected message to mention %s", expected) - } - } - - // Test message with no mentions - babble.Babble = "No mentions in this message" - mentions = babble.GetMentions() - - if len(mentions) != 0 { - t.Errorf("Expected 0 mentions, got %d", len(mentions)) - } - - // Test malformed mentions (should be ignored) - babble.Babble = "Just @ alone or @" - mentions = babble.GetMentions() - - if len(mentions) != 0 { - t.Errorf("Expected 0 mentions for malformed @, got %d", len(mentions)) - } -} diff --git a/internal/control/control_test.go b/internal/control/control_test.go deleted file mode 100644 index 5410957..0000000 --- a/internal/control/control_test.go +++ /dev/null @@ -1,418 +0,0 @@ -package control - -import ( - "os" - "testing" - - "dk/internal/database" -) - -func setupTestDB(t *testing.T) *database.DB { - testDB := "test_control.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 control table - createTable := `CREATE TABLE control ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - world_size INTEGER NOT NULL DEFAULT 250, - open INTEGER NOT NULL DEFAULT 1, - admin_email TEXT NOT NULL DEFAULT '', - class_1_name TEXT NOT NULL DEFAULT '', - class_2_name TEXT NOT NULL DEFAULT '', - class_3_name TEXT NOT NULL DEFAULT '' - )` - - if err := db.Exec(createTable); err != nil { - t.Fatalf("Failed to create control table: %v", err) - } - - // Insert default control record - insertControl := `INSERT INTO control VALUES (1, 250, 1, 'admin@example.com', 'Mage', 'Warrior', 'Paladin')` - - if err := db.Exec(insertControl); err != nil { - t.Fatalf("Failed to insert test control: %v", err) - } - - return db -} - -func TestFind(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test finding existing control record - control, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find control: %v", err) - } - - if control.ID != 1 { - t.Errorf("Expected ID 1, got %d", control.ID) - } - if control.WorldSize != 250 { - t.Errorf("Expected world size 250, got %d", control.WorldSize) - } - if control.Open != 1 { - t.Errorf("Expected open 1, got %d", control.Open) - } - if control.AdminEmail != "admin@example.com" { - t.Errorf("Expected admin email 'admin@example.com', got '%s'", control.AdminEmail) - } - if control.Class1Name != "Mage" { - t.Errorf("Expected class 1 name 'Mage', got '%s'", control.Class1Name) - } - if control.Class2Name != "Warrior" { - t.Errorf("Expected class 2 name 'Warrior', got '%s'", control.Class2Name) - } - if control.Class3Name != "Paladin" { - t.Errorf("Expected class 3 name 'Paladin', got '%s'", control.Class3Name) - } - - // Test finding non-existent control record - _, err = Find(db, 999) - if err == nil { - t.Error("Expected error when finding non-existent control") - } -} - -func TestGet(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test getting main control record - control, err := Get(db) - if err != nil { - t.Fatalf("Failed to get control: %v", err) - } - - if control.ID != 1 { - t.Errorf("Expected ID 1, got %d", control.ID) - } - if control.WorldSize != 250 { - t.Errorf("Expected world size 250, got %d", control.WorldSize) - } -} - -func TestSave(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - control, err := Get(db) - if err != nil { - t.Fatalf("Failed to get control: %v", err) - } - - // Modify control settings - control.WorldSize = 500 - control.Open = 0 - control.AdminEmail = "newadmin@example.com" - control.Class1Name = "Wizard" - control.Class2Name = "Knight" - control.Class3Name = "Cleric" - - // Save changes - err = control.Save() - if err != nil { - t.Fatalf("Failed to save control: %v", err) - } - - // Verify changes were saved - updatedControl, err := Get(db) - if err != nil { - t.Fatalf("Failed to get updated control: %v", err) - } - - if updatedControl.WorldSize != 500 { - t.Errorf("Expected updated world size 500, got %d", updatedControl.WorldSize) - } - if updatedControl.Open != 0 { - t.Errorf("Expected updated open 0, got %d", updatedControl.Open) - } - if updatedControl.AdminEmail != "newadmin@example.com" { - t.Errorf("Expected updated admin email 'newadmin@example.com', got '%s'", updatedControl.AdminEmail) - } - if updatedControl.Class1Name != "Wizard" { - t.Errorf("Expected updated class 1 name 'Wizard', got '%s'", updatedControl.Class1Name) - } - if updatedControl.Class2Name != "Knight" { - t.Errorf("Expected updated class 2 name 'Knight', got '%s'", updatedControl.Class2Name) - } - if updatedControl.Class3Name != "Cleric" { - t.Errorf("Expected updated class 3 name 'Cleric', got '%s'", updatedControl.Class3Name) - } -} - -func TestOpenMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - control, _ := Get(db) - - // Test IsOpen - if !control.IsOpen() { - t.Error("Expected control to be open initially") - } - - // Test SetOpen - control.SetOpen(false) - if control.IsOpen() { - t.Error("Expected control to be closed after SetOpen(false)") - } - - control.SetOpen(true) - if !control.IsOpen() { - t.Error("Expected control to be open after SetOpen(true)") - } - - // Test Close - control.Close() - if control.IsOpen() { - t.Error("Expected control to be closed after Close()") - } - - // Test OpenWorld - control.OpenWorld() - if !control.IsOpen() { - t.Error("Expected control to be open after OpenWorld()") - } -} - -func TestClassMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - control, _ := Get(db) - - // Test GetClassNames - classNames := control.GetClassNames() - expectedNames := []string{"Mage", "Warrior", "Paladin"} - - if len(classNames) != len(expectedNames) { - t.Errorf("Expected %d class names, got %d", len(expectedNames), len(classNames)) - } - - for i, expected := range expectedNames { - if i < len(classNames) && classNames[i] != expected { - t.Errorf("Expected class name '%s' at position %d, got '%s'", expected, i, classNames[i]) - } - } - - // Test SetClassNames - newClasses := []string{"Sorcerer", "Barbarian", "Monk"} - control.SetClassNames(newClasses) - - if control.Class1Name != "Sorcerer" { - t.Errorf("Expected class 1 name 'Sorcerer', got '%s'", control.Class1Name) - } - if control.Class2Name != "Barbarian" { - t.Errorf("Expected class 2 name 'Barbarian', got '%s'", control.Class2Name) - } - if control.Class3Name != "Monk" { - t.Errorf("Expected class 3 name 'Monk', got '%s'", control.Class3Name) - } - - // Test SetClassNames with fewer than 3 classes - twoClasses := []string{"Ranger", "Druid"} - control.SetClassNames(twoClasses) - - if control.Class1Name != "Ranger" { - t.Errorf("Expected class 1 name 'Ranger', got '%s'", control.Class1Name) - } - if control.Class2Name != "Druid" { - t.Errorf("Expected class 2 name 'Druid', got '%s'", control.Class2Name) - } - if control.Class3Name != "" { - t.Errorf("Expected class 3 name to be empty, got '%s'", control.Class3Name) - } - - // Test GetClassName - if control.GetClassName(1) != "Ranger" { - t.Errorf("Expected class 1 name 'Ranger', got '%s'", control.GetClassName(1)) - } - if control.GetClassName(2) != "Druid" { - t.Errorf("Expected class 2 name 'Druid', got '%s'", control.GetClassName(2)) - } - if control.GetClassName(3) != "" { - t.Errorf("Expected class 3 name to be empty, got '%s'", control.GetClassName(3)) - } - if control.GetClassName(4) != "" { - t.Errorf("Expected invalid class number to return empty string, got '%s'", control.GetClassName(4)) - } - - // Test SetClassName - if !control.SetClassName(3, "Rogue") { - t.Error("Expected SetClassName(3, 'Rogue') to return true") - } - if control.Class3Name != "Rogue" { - t.Errorf("Expected class 3 name 'Rogue', got '%s'", control.Class3Name) - } - - if control.SetClassName(4, "Invalid") { - t.Error("Expected SetClassName(4, 'Invalid') to return false") - } - - // Test IsValidClassName - if !control.IsValidClassName("Ranger") { - t.Error("Expected 'Ranger' to be a valid class name") - } - if !control.IsValidClassName("Druid") { - t.Error("Expected 'Druid' to be a valid class name") - } - if !control.IsValidClassName("Rogue") { - t.Error("Expected 'Rogue' to be a valid class name") - } - if control.IsValidClassName("Bard") { - t.Error("Expected 'Bard' not to be a valid class name") - } - if control.IsValidClassName("") { - t.Error("Expected empty string not to be a valid class name") - } - - // Test GetClassNumber - if control.GetClassNumber("Ranger") != 1 { - t.Errorf("Expected 'Ranger' to be class number 1, got %d", control.GetClassNumber("Ranger")) - } - if control.GetClassNumber("Druid") != 2 { - t.Errorf("Expected 'Druid' to be class number 2, got %d", control.GetClassNumber("Druid")) - } - if control.GetClassNumber("Rogue") != 3 { - t.Errorf("Expected 'Rogue' to be class number 3, got %d", control.GetClassNumber("Rogue")) - } - if control.GetClassNumber("Bard") != 0 { - t.Errorf("Expected 'Bard' to return class number 0, got %d", control.GetClassNumber("Bard")) - } - if control.GetClassNumber("") != 0 { - t.Errorf("Expected empty string to return class number 0, got %d", control.GetClassNumber("")) - } -} - -func TestEmailMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - control, _ := Get(db) - - // Test HasAdminEmail (should be true initially) - if !control.HasAdminEmail() { - t.Error("Expected control to have admin email initially") - } - - // Test with empty email - control.AdminEmail = "" - if control.HasAdminEmail() { - t.Error("Expected control not to have admin email when empty") - } -} - -func TestWorldSizeMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - control, _ := Get(db) - - // Test IsWorldSizeValid - if !control.IsWorldSizeValid() { - t.Error("Expected world size 250 to be valid") - } - - control.WorldSize = 0 - if control.IsWorldSizeValid() { - t.Error("Expected world size 0 to be invalid") - } - - control.WorldSize = 10001 - if control.IsWorldSizeValid() { - t.Error("Expected world size 10001 to be invalid") - } - - control.WorldSize = 1000 - if !control.IsWorldSizeValid() { - t.Error("Expected world size 1000 to be valid") - } - - // Test GetWorldRadius - expectedRadius := 500 - if control.GetWorldRadius() != expectedRadius { - t.Errorf("Expected world radius %d, got %d", expectedRadius, control.GetWorldRadius()) - } - - // Test IsWithinWorldBounds - if !control.IsWithinWorldBounds(0, 0) { - t.Error("Expected (0,0) to be within world bounds") - } - if !control.IsWithinWorldBounds(500, 500) { - t.Error("Expected (500,500) to be within world bounds") - } - if !control.IsWithinWorldBounds(-500, -500) { - t.Error("Expected (-500,-500) to be within world bounds") - } - if control.IsWithinWorldBounds(501, 0) { - t.Error("Expected (501,0) to be outside world bounds") - } - if control.IsWithinWorldBounds(0, 501) { - t.Error("Expected (0,501) to be outside world bounds") - } - if control.IsWithinWorldBounds(-501, 0) { - t.Error("Expected (-501,0) to be outside world bounds") - } - if control.IsWithinWorldBounds(0, -501) { - t.Error("Expected (0,-501) to be outside world bounds") - } - - // Test GetWorldBounds - minX, minY, maxX, maxY := control.GetWorldBounds() - expectedMin, expectedMax := -500, 500 - - if minX != expectedMin { - t.Errorf("Expected minX %d, got %d", expectedMin, minX) - } - if minY != expectedMin { - t.Errorf("Expected minY %d, got %d", expectedMin, minY) - } - if maxX != expectedMax { - t.Errorf("Expected maxX %d, got %d", expectedMax, maxX) - } - if maxY != expectedMax { - t.Errorf("Expected maxY %d, got %d", expectedMax, maxY) - } -} - -func TestEmptyClassHandling(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - control, _ := Get(db) - - // Set some classes to empty - control.Class2Name = "" - control.Class3Name = "" - - // Test GetClassNames with empty classes - classNames := control.GetClassNames() - expectedCount := 1 // Only Class1Name should be included - - if len(classNames) != expectedCount { - t.Errorf("Expected %d non-empty class names, got %d", expectedCount, len(classNames)) - } - - if len(classNames) > 0 && classNames[0] != "Mage" { - t.Errorf("Expected first class name 'Mage', got '%s'", classNames[0]) - } - - // Test IsValidClassName with empty string - if control.IsValidClassName("") { - t.Error("Expected empty string not to be valid class name") - } - - // Test GetClassNumber with empty class names - if control.GetClassNumber("Warrior") != 0 { - t.Error("Expected empty class 2 not to match 'Warrior'") - } -} \ No newline at end of file diff --git a/internal/drops/drops_test.go b/internal/drops/drops_test.go deleted file mode 100644 index 6042707..0000000 --- a/internal/drops/drops_test.go +++ /dev/null @@ -1,270 +0,0 @@ -package drops - -import ( - "os" - "testing" - - "dk/internal/database" -) - -func setupTestDB(t *testing.T) *database.DB { - testDB := "test_drops.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 drops table - createTable := `CREATE TABLE drops ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL DEFAULT '', - level INTEGER NOT NULL DEFAULT 0, - type INTEGER NOT NULL DEFAULT 0, - att TEXT NOT NULL DEFAULT '' - )` - - if err := db.Exec(createTable); err != nil { - t.Fatalf("Failed to create drops table: %v", err) - } - - // Insert test data - testDrops := `INSERT INTO drops (name, level, type, att) VALUES - ('Life Pebble', 1, 1, 'maxhp,10'), - ('Magic Stone', 10, 1, 'maxmp,25'), - ('Dragon''s Scale', 10, 1, 'defensepower,25'), - ('Angel''s Joy', 25, 1, 'maxhp,25,strength,25')` - - if err := db.Exec(testDrops); err != nil { - t.Fatalf("Failed to insert test drops: %v", err) - } - - return db -} - -func TestFind(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test finding existing drop - drop, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find drop: %v", err) - } - - if drop.ID != 1 { - t.Errorf("Expected ID 1, got %d", drop.ID) - } - if drop.Name != "Life Pebble" { - t.Errorf("Expected name 'Life Pebble', got '%s'", drop.Name) - } - if drop.Level != 1 { - t.Errorf("Expected level 1, got %d", drop.Level) - } - if drop.Type != TypeConsumable { - t.Errorf("Expected type %d, got %d", TypeConsumable, drop.Type) - } - if drop.Att != "maxhp,10" { - t.Errorf("Expected att1 'maxhp,10', got '%s'", drop.Att) - } - - // Test finding non-existent drop - _, err = Find(db, 999) - if err == nil { - t.Error("Expected error when finding non-existent drop") - } -} - -func TestAll(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - drops, err := All(db) - if err != nil { - t.Fatalf("Failed to get all drops: %v", err) - } - - if len(drops) != 4 { - t.Errorf("Expected 4 drops, got %d", len(drops)) - } - - // Check first drop - if drops[0].Name != "Life Pebble" { - t.Errorf("Expected first drop to be 'Life Pebble', got '%s'", drops[0].Name) - } -} - -func TestByLevel(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test drops available at level 10 - drops, err := ByLevel(db, 10) - if err != nil { - t.Fatalf("Failed to get drops by level: %v", err) - } - - if len(drops) != 3 { - t.Errorf("Expected 3 drops at level 10, got %d", len(drops)) - } - - // Verify they are ordered by level - if drops[0].Level != 1 { - t.Errorf("Expected first drop level 1, got %d", drops[0].Level) - } - if drops[1].Level != 10 { - t.Errorf("Expected second drop level 10, got %d", drops[1].Level) - } - - // Test drops available at level 1 - lowLevelDrops, err := ByLevel(db, 1) - if err != nil { - t.Fatalf("Failed to get drops by level 1: %v", err) - } - - if len(lowLevelDrops) != 1 { - t.Errorf("Expected 1 drop at level 1, got %d", len(lowLevelDrops)) - } -} - -func TestByType(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - consumables, err := ByType(db, TypeConsumable) - if err != nil { - t.Fatalf("Failed to get consumable drops: %v", err) - } - - if len(consumables) != 4 { - t.Errorf("Expected 4 consumable drops, got %d", len(consumables)) - } - - // Verify they are ordered by level, then ID - if consumables[0].Level > consumables[1].Level { - t.Error("Expected drops to be ordered by level") - } -} - -func TestBuilder(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Create new drop using builder - drop, err := NewBuilder(db). - WithName("Test Drop"). - WithLevel(15). - WithType(TypeConsumable). - WithAtt("strength,20,dexterity,15"). - Create() - - if err != nil { - t.Fatalf("Failed to create drop with builder: %v", err) - } - - if drop.ID == 0 { - t.Error("Expected non-zero ID after creation") - } - if drop.Name != "Test Drop" { - t.Errorf("Expected name 'Test Drop', got '%s'", drop.Name) - } - if drop.Level != 15 { - t.Errorf("Expected level 15, got %d", drop.Level) - } - if drop.Type != TypeConsumable { - t.Errorf("Expected type %d, got %d", TypeConsumable, drop.Type) - } - if drop.Att != "strength,20,dexterity,15" { - t.Errorf("Expected att 'strength,20,dexterity,15', got '%s'", drop.Att) - } - - // Verify it was saved to database - foundDrop, err := Find(db, drop.ID) - if err != nil { - t.Fatalf("Failed to find created drop: %v", err) - } - - if foundDrop.Name != "Test Drop" { - t.Errorf("Created drop not found in database") - } -} - -func TestSave(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - drop, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find drop: %v", err) - } - - // Modify drop - drop.Name = "Updated Life Pebble" - drop.Level = 5 - drop.Att = "maxhp,15" - - // Save changes - err = drop.Save() - if err != nil { - t.Fatalf("Failed to save drop: %v", err) - } - - // Verify changes were saved - updatedDrop, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find updated drop: %v", err) - } - - if updatedDrop.Name != "Updated Life Pebble" { - t.Errorf("Expected updated name 'Updated Life Pebble', got '%s'", updatedDrop.Name) - } - if updatedDrop.Level != 5 { - t.Errorf("Expected updated level 5, got %d", updatedDrop.Level) - } - if updatedDrop.Att != "maxhp,15" { - t.Errorf("Expected updated att 'maxhp,15', got '%s'", updatedDrop.Att) - } -} - -func TestDelete(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - drop, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find drop: %v", err) - } - - // Delete drop - err = drop.Delete() - if err != nil { - t.Fatalf("Failed to delete drop: %v", err) - } - - // Verify drop was deleted - _, err = Find(db, 1) - if err == nil { - t.Error("Expected error when finding deleted drop") - } -} - -func TestDropMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - pebble, _ := Find(db, 1) - - // Test IsConsumable - if !pebble.IsConsumable() { - t.Error("Expected pebble to be consumable") - } - - // Test TypeName - if pebble.TypeName() != "Consumable" { - t.Errorf("Expected pebble type name 'Consumable', got '%s'", pebble.TypeName()) - } - -} diff --git a/internal/forum/forum_test.go b/internal/forum/forum_test.go deleted file mode 100644 index e983ac0..0000000 --- a/internal/forum/forum_test.go +++ /dev/null @@ -1,665 +0,0 @@ -package forum - -import ( - "os" - "testing" - "time" - - "dk/internal/database" -) - -func setupTestDB(t *testing.T) *database.DB { - testDB := "test_forum.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 forum table - createTable := `CREATE TABLE forum ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - posted INTEGER NOT NULL DEFAULT (unixepoch()), - last_post INTEGER NOT NULL DEFAULT (unixepoch()), - author INTEGER NOT NULL, - parent INTEGER NOT NULL DEFAULT 0, - replies INTEGER NOT NULL DEFAULT 0, - title TEXT NOT NULL, - content TEXT NOT NULL - )` - - if err := db.Exec(createTable); err != nil { - t.Fatalf("Failed to create forum table: %v", err) - } - - // Insert test data with specific timestamps for predictable testing - now := time.Now().Unix() - testForum := `INSERT INTO forum (posted, last_post, author, parent, replies, title, content) VALUES - (?, ?, 1, 0, 2, 'Welcome to the Game!', 'This is the first thread about our awesome game.'), - (?, ?, 2, 1, 0, 'Re: Welcome to the Game!', 'Thanks! I am excited to start playing.'), - (?, ?, 3, 1, 0, 'Re: Welcome to the Game!', 'Great game so far, loving the mechanics!'), - (?, ?, 1, 0, 1, 'Bug Reports', 'Please report any bugs you find here.'), - (?, ?, 2, 4, 0, 'Re: Bug Reports', 'Found a small issue with spell casting.'), - (?, ?, 3, 0, 0, 'Strategy Discussion', 'Let us discuss optimal character builds and strategies.')` - - timestamps := []any{ - now - 86400*7, now - 86400*1, // Thread 1, last activity 1 day ago - now - 86400*6, now - 86400*6, // Reply 1 - now - 86400*1, now - 86400*1, // Reply 2 (most recent activity on thread 1) - now - 86400*3, now - 86400*2, // Thread 2, last activity 2 days ago - now - 86400*2, now - 86400*2, // Reply to thread 2 (most recent activity on thread 2) - now - 3600*2, now - 3600*2, // Thread 3, 2 hours ago (most recent) - } - - if err := db.Exec(testForum, timestamps...); err != nil { - t.Fatalf("Failed to insert test forum data: %v", err) - } - - return db -} - -func TestFind(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test finding existing forum post - post, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find forum post: %v", err) - } - - if post.ID != 1 { - t.Errorf("Expected ID 1, got %d", post.ID) - } - if post.Author != 1 { - t.Errorf("Expected author 1, got %d", post.Author) - } - if post.Parent != 0 { - t.Errorf("Expected parent 0, got %d", post.Parent) - } - if post.Replies != 2 { - t.Errorf("Expected replies 2, got %d", post.Replies) - } - if post.Title != "Welcome to the Game!" { - t.Errorf("Expected title 'Welcome to the Game!', got '%s'", post.Title) - } - if post.Content != "This is the first thread about our awesome game." { - t.Errorf("Expected specific content, got '%s'", post.Content) - } - if post.Posted == 0 { - t.Error("Expected non-zero posted timestamp") - } - if post.LastPost == 0 { - t.Error("Expected non-zero last_post timestamp") - } - - // Test finding non-existent forum post - _, err = Find(db, 999) - if err == nil { - t.Error("Expected error when finding non-existent forum post") - } -} - -func TestAll(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - posts, err := All(db) - if err != nil { - t.Fatalf("Failed to get all forum posts: %v", err) - } - - if len(posts) != 6 { - t.Errorf("Expected 6 forum posts, got %d", len(posts)) - } - - // Check ordering (by last_post DESC) - if len(posts) >= 2 { - if posts[0].LastPost < posts[1].LastPost { - t.Error("Expected posts to be ordered by last_post (newest first)") - } - } - - // First post should be the most recent activity (2 hours ago) - if posts[0].Title != "Strategy Discussion" { - t.Errorf("Expected newest activity to be 'Strategy Discussion', got '%s'", posts[0].Title) - } -} - -func TestThreads(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - threads, err := Threads(db) - if err != nil { - t.Fatalf("Failed to get forum threads: %v", err) - } - - if len(threads) != 3 { - t.Errorf("Expected 3 threads, got %d", len(threads)) - } - - // Verify all are threads (parent = 0) - for _, thread := range threads { - if thread.Parent != 0 { - t.Errorf("Expected thread to have parent 0, got %d", thread.Parent) - } - if !thread.IsThread() { - t.Errorf("Expected IsThread() to return true for thread %d", thread.ID) - } - if thread.IsReply() { - t.Errorf("Expected IsReply() to return false for thread %d", thread.ID) - } - } - - // Check ordering (by last_post DESC) - if len(threads) >= 2 { - if threads[0].LastPost < threads[1].LastPost { - t.Error("Expected threads to be ordered by last activity") - } - } -} - -func TestByParent(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test replies to thread 1 - replies, err := ByParent(db, 1) - if err != nil { - t.Fatalf("Failed to get replies: %v", err) - } - - if len(replies) != 2 { - t.Errorf("Expected 2 replies to thread 1, got %d", len(replies)) - } - - // Verify all are replies to thread 1 - for _, reply := range replies { - if reply.Parent != 1 { - t.Errorf("Expected reply to have parent 1, got %d", reply.Parent) - } - if !reply.IsReply() { - t.Errorf("Expected IsReply() to return true for reply %d", reply.ID) - } - if reply.IsThread() { - t.Errorf("Expected IsThread() to return false for reply %d", reply.ID) - } - } - - // Check ordering (by posted ASC for replies) - if len(replies) == 2 { - if replies[0].Posted > replies[1].Posted { - t.Error("Expected replies to be ordered by posted time (oldest first)") - } - } - - // Test no replies case - noReplies, err := ByParent(db, 6) // Thread 3 has no replies - if err != nil { - t.Fatalf("Failed to get replies for thread with no replies: %v", err) - } - - if len(noReplies) != 0 { - t.Errorf("Expected 0 replies for thread 3, got %d", len(noReplies)) - } -} - -func TestByAuthor(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test posts by author 1 - author1Posts, err := ByAuthor(db, 1) - if err != nil { - t.Fatalf("Failed to get posts by author: %v", err) - } - - if len(author1Posts) != 2 { - t.Errorf("Expected 2 posts by author 1, got %d", len(author1Posts)) - } - - // Verify all posts are by author 1 - for _, post := range author1Posts { - if post.Author != 1 { - t.Errorf("Expected author 1, got %d", post.Author) - } - } - - // Check ordering (by posted DESC) - if len(author1Posts) == 2 { - if author1Posts[0].Posted < author1Posts[1].Posted { - t.Error("Expected posts to be ordered by posted time (newest first)") - } - } - - // Test author with no posts - noPosts, err := ByAuthor(db, 999) - if err != nil { - t.Fatalf("Failed to query non-existent author: %v", err) - } - - if len(noPosts) != 0 { - t.Errorf("Expected 0 posts by non-existent author, got %d", len(noPosts)) - } -} - -func TestRecent(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test getting 3 most recent posts - recentPosts, err := Recent(db, 3) - if err != nil { - t.Fatalf("Failed to get recent forum posts: %v", err) - } - - if len(recentPosts) != 3 { - t.Errorf("Expected 3 recent posts, got %d", len(recentPosts)) - } - - // Check ordering (by last_post DESC) - if len(recentPosts) >= 2 { - if recentPosts[0].LastPost < recentPosts[1].LastPost { - t.Error("Expected recent posts to be ordered by last activity") - } - } - - // Test getting more posts than exist - allRecentPosts, err := Recent(db, 10) - if err != nil { - t.Fatalf("Failed to get recent posts with high limit: %v", err) - } - - if len(allRecentPosts) != 6 { - t.Errorf("Expected 6 posts (all available), got %d", len(allRecentPosts)) - } -} - -func TestSearch(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test searching for "game" - gamePosts, err := Search(db, "game") - if err != nil { - t.Fatalf("Failed to search forum posts: %v", err) - } - - expectedCount := 3 // Welcome thread title, content, and replies containing "game" - if len(gamePosts) != expectedCount { - t.Errorf("Expected %d posts containing 'game', got %d", expectedCount, len(gamePosts)) - } - - // Verify all posts contain the search term - for _, post := range gamePosts { - if !post.Contains("game") { - t.Errorf("Post '%s' does not contain search term 'game'", post.Title) - } - } - - // Test case insensitive search - gamePostsUpper, err := Search(db, "GAME") - if err != nil { - t.Fatalf("Failed to search with uppercase: %v", err) - } - - if len(gamePostsUpper) != expectedCount { - t.Error("Expected case insensitive search to find same results") - } - - // Test search with no results - noResults, err := Search(db, "nonexistentterm") - if err != nil { - t.Fatalf("Failed to search for non-existent term: %v", err) - } - - if len(noResults) != 0 { - t.Errorf("Expected 0 results for non-existent term, got %d", len(noResults)) - } -} - -func TestSince(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test posts with activity since 3 days ago - threeDaysAgo := time.Now().AddDate(0, 0, -3).Unix() - recentPosts, err := Since(db, threeDaysAgo) - if err != nil { - t.Fatalf("Failed to get posts since timestamp: %v", err) - } - - // Should get posts with last_post within last 3 days (includes replies) - expectedCount := 5 // Thread 1 (1 day ago), Reply 2 to Thread 1, Thread 2 (2 days ago), Reply to Thread 2, Thread 3 (2 hours ago) - if len(recentPosts) != expectedCount { - t.Errorf("Expected %d posts with activity since 3 days ago, got %d", expectedCount, len(recentPosts)) - } - - // Verify all posts have last_post since the timestamp - for _, post := range recentPosts { - if post.LastPost < threeDaysAgo { - t.Errorf("Post with last_post %d is before the 'since' timestamp %d", post.LastPost, threeDaysAgo) - } - } - - // Test with future timestamp (should return no posts) - futurePosts, err := Since(db, time.Now().Add(time.Hour).Unix()) - if err != nil { - t.Fatalf("Failed to query future timestamp: %v", err) - } - - if len(futurePosts) != 0 { - t.Errorf("Expected 0 posts since future timestamp, got %d", len(futurePosts)) - } -} - -func TestBuilder(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Create new thread using builder - testTime := time.Now() - post, err := NewBuilder(db). - WithAuthor(5). - WithTitle("Test Thread"). - WithContent("This is a test thread created with the builder"). - WithPostedTime(testTime). - WithLastPostTime(testTime). - AsThread(). - Create() - - if err != nil { - t.Fatalf("Failed to create forum post with builder: %v", err) - } - - if post.ID == 0 { - t.Error("Expected non-zero ID after creation") - } - if post.Author != 5 { - t.Errorf("Expected author 5, got %d", post.Author) - } - if post.Title != "Test Thread" { - t.Errorf("Expected title 'Test Thread', got '%s'", post.Title) - } - if post.Content != "This is a test thread created with the builder" { - t.Errorf("Expected specific content, got '%s'", post.Content) - } - if post.Posted != testTime.Unix() { - t.Errorf("Expected posted time %d, got %d", testTime.Unix(), post.Posted) - } - if post.Parent != 0 { - t.Errorf("Expected parent 0 (thread), got %d", post.Parent) - } - if post.Replies != 0 { - t.Errorf("Expected replies 0, got %d", post.Replies) - } - - // Create reply using builder - reply, err := NewBuilder(db). - WithAuthor(6). - WithTitle("Re: Test Thread"). - WithContent("This is a reply to the test thread"). - AsReply(post.ID). - Create() - - if err != nil { - t.Fatalf("Failed to create reply with builder: %v", err) - } - - if reply.Parent != post.ID { - t.Errorf("Expected parent %d, got %d", post.ID, reply.Parent) - } - if !reply.IsReply() { - t.Error("Expected reply to be identified as reply") - } - - // Verify posts were saved to database - foundPost, err := Find(db, post.ID) - if err != nil { - t.Fatalf("Failed to find created post: %v", err) - } - - if foundPost.Title != "Test Thread" { - t.Errorf("Created post not found in database") - } - - // Test builder with default timestamp - defaultPost, err := NewBuilder(db). - WithAuthor(7). - WithTitle("Default Time Post"). - WithContent("Post with default timestamps"). - Create() - - if err != nil { - t.Fatalf("Failed to create post with default timestamp: %v", err) - } - - // Should have recent timestamps (within last minute) - if time.Since(defaultPost.PostedTime()) > time.Minute { - t.Error("Expected default posted timestamp to be recent") - } - if time.Since(defaultPost.LastPostTime()) > time.Minute { - t.Error("Expected default last_post timestamp to be recent") - } -} - -func TestSave(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - post, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find forum post: %v", err) - } - - // Modify post - post.Title = "Updated Welcome Thread" - post.Content = "This content has been updated by moderator" - post.Replies = 3 - post.UpdateLastPost() - - // Save changes - err = post.Save() - if err != nil { - t.Fatalf("Failed to save forum post: %v", err) - } - - // Verify changes were saved - updatedPost, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find updated post: %v", err) - } - - if updatedPost.Title != "Updated Welcome Thread" { - t.Errorf("Expected updated title 'Updated Welcome Thread', got '%s'", updatedPost.Title) - } - if updatedPost.Content != "This content has been updated by moderator" { - t.Errorf("Expected updated content, got '%s'", updatedPost.Content) - } - if updatedPost.Replies != 3 { - t.Errorf("Expected updated replies 3, got %d", updatedPost.Replies) - } -} - -func TestDelete(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - post, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find forum post: %v", err) - } - - // Delete post - err = post.Delete() - if err != nil { - t.Fatalf("Failed to delete forum post: %v", err) - } - - // Verify post was deleted - _, err = Find(db, 1) - if err == nil { - t.Error("Expected error when finding deleted post") - } -} - -func TestUtilityMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - post, _ := Find(db, 1) - - // Test time methods - postedTime := post.PostedTime() - if postedTime.IsZero() { - t.Error("Expected non-zero posted time") - } - - lastPostTime := post.LastPostTime() - if lastPostTime.IsZero() { - t.Error("Expected non-zero last post time") - } - - // Test SetPostedTime and SetLastPostTime - newTime := time.Now().Add(-2 * time.Hour) - post.SetPostedTime(newTime) - post.SetLastPostTime(newTime) - - if post.Posted != newTime.Unix() { - t.Errorf("Expected posted timestamp %d, got %d", newTime.Unix(), post.Posted) - } - if post.LastPost != newTime.Unix() { - t.Errorf("Expected last_post timestamp %d, got %d", newTime.Unix(), post.LastPost) - } - - // Test activity age methods - activityAge := post.ActivityAge() - if activityAge < 0 { - t.Error("Expected positive activity age") - } - - postAge := post.PostAge() - if postAge < 0 { - t.Error("Expected positive post age") - } - - // Test IsRecentActivity - post.UpdateLastPost() // Set to now - if !post.IsRecentActivity() { - t.Error("Expected post with current timestamp to have recent activity") - } - - // Test IsAuthor - if !post.IsAuthor(post.Author) { - t.Error("Expected IsAuthor to return true for correct author") - } - if post.IsAuthor(999) { - t.Error("Expected IsAuthor to return false for incorrect author") - } - - // Test HasReplies - if !post.HasReplies() { - t.Error("Expected post with replies > 0 to HasReplies") - } - - // Test Preview - longContent := "This is a very long forum post content that should be truncated when preview is called for display purposes" - post.Content = longContent - - preview := post.Preview(20) - if len(preview) > 20 { - t.Errorf("Expected preview length <= 20, got %d", len(preview)) - } - if preview[len(preview)-3:] != "..." { - t.Error("Expected preview to end with ellipsis") - } - - shortPreview := post.Preview(200) // Longer than content - if shortPreview != longContent { - t.Error("Expected short content to not be truncated") - } - - // Test WordCount - post.Content = "This is a test with five words" - wordCount := post.WordCount() - if wordCount != 7 { - t.Errorf("Expected 7 words, got %d", wordCount) - } - - // Test Length - expectedLength := len(post.Content) - if post.Length() != expectedLength { - t.Errorf("Expected length %d, got %d", expectedLength, post.Length()) - } - - // Test Contains - if !post.Contains("test") { - t.Error("Expected post to contain 'test'") - } - if !post.Contains("TEST") { // Case insensitive - t.Error("Expected Contains to be case insensitive") - } - if post.Contains("nonexistent") { - t.Error("Expected post not to contain 'nonexistent'") - } - - // Test reply count methods - originalReplies := post.Replies - post.IncrementReplies() - if post.Replies != originalReplies+1 { - t.Errorf("Expected replies to be incremented to %d, got %d", originalReplies+1, post.Replies) - } - - post.DecrementReplies() - if post.Replies != originalReplies { - t.Errorf("Expected replies to be decremented back to %d, got %d", originalReplies, post.Replies) - } - - // Test DecrementReplies with 0 replies - post.Replies = 0 - post.DecrementReplies() - if post.Replies != 0 { - t.Errorf("Expected replies to stay at 0 when decrementing from 0, got %d", post.Replies) - } -} - -func TestRelationshipMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test GetReplies on a thread - thread, _ := Find(db, 1) // Thread with 2 replies - replies, err := thread.GetReplies() - if err != nil { - t.Fatalf("Failed to get replies: %v", err) - } - - if len(replies) != 2 { - t.Errorf("Expected 2 replies, got %d", len(replies)) - } - - // Test GetThread on a reply - reply, _ := Find(db, 2) // Reply to thread 1 - parentThread, err := reply.GetThread() - if err != nil { - t.Fatalf("Failed to get parent thread: %v", err) - } - - if parentThread.ID != 1 { - t.Errorf("Expected parent thread ID 1, got %d", parentThread.ID) - } - - // Test GetThread on a thread (should return self) - threadSelf, err := thread.GetThread() - if err != nil { - t.Fatalf("Failed to get thread (self): %v", err) - } - - if threadSelf.ID != thread.ID { - t.Errorf("Expected GetThread on thread to return self, got ID %d", threadSelf.ID) - } -} diff --git a/internal/items/items_test.go b/internal/items/items_test.go deleted file mode 100644 index b3020d0..0000000 --- a/internal/items/items_test.go +++ /dev/null @@ -1,272 +0,0 @@ -package items - -import ( - "os" - "testing" - - "dk/internal/database" -) - -func setupTestDB(t *testing.T) *database.DB { - testDB := "test_items.db" - t.Cleanup(func() { - os.Remove(testDB) - }) - - db, err := database.Open(testDB) - if err != nil { - t.Fatalf("Failed to open test database: %v", err) - } - - createTable := `CREATE TABLE items ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - type INTEGER NOT NULL DEFAULT 0, - name TEXT NOT NULL, - value INTEGER NOT NULL DEFAULT 0, - att INTEGER NOT NULL DEFAULT 0, - special TEXT NOT NULL DEFAULT '' - )` - - if err := db.Exec(createTable); err != nil { - t.Fatalf("Failed to create items table: %v", err) - } - - testItems := `INSERT INTO items (type, name, value, att, special) VALUES - (1, 'Test Sword', 100, 10, 'strength,5'), - (2, 'Test Armor', 200, 15, 'maxhp,25'), - (3, 'Test Shield', 150, 8, '')` - - if err := db.Exec(testItems); err != nil { - t.Fatalf("Failed to insert test items: %v", err) - } - - return db -} - -func TestFind(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test finding existing item - item, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find item: %v", err) - } - - if item.ID != 1 { - t.Errorf("Expected ID 1, got %d", item.ID) - } - if item.Name != "Test Sword" { - t.Errorf("Expected name 'Test Sword', got '%s'", item.Name) - } - if item.Type != TypeWeapon { - t.Errorf("Expected type %d, got %d", TypeWeapon, item.Type) - } - if item.Value != 100 { - t.Errorf("Expected value 100, got %d", item.Value) - } - if item.Att != 10 { - t.Errorf("Expected att 10, got %d", item.Att) - } - if item.Special != "strength,5" { - t.Errorf("Expected special 'strength,5', got '%s'", item.Special) - } - - // Test finding non-existent item - _, err = Find(db, 999) - if err == nil { - t.Error("Expected error when finding non-existent item") - } -} - -func TestAll(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - items, err := All(db) - if err != nil { - t.Fatalf("Failed to get all items: %v", err) - } - - if len(items) != 3 { - t.Errorf("Expected 3 items, got %d", len(items)) - } - - // Check first item - if items[0].Name != "Test Sword" { - t.Errorf("Expected first item to be 'Test Sword', got '%s'", items[0].Name) - } -} - -func TestByType(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - weapons, err := ByType(db, TypeWeapon) - if err != nil { - t.Fatalf("Failed to get weapons: %v", err) - } - - if len(weapons) != 1 { - t.Errorf("Expected 1 weapon, got %d", len(weapons)) - } - - if weapons[0].Name != "Test Sword" { - t.Errorf("Expected weapon to be 'Test Sword', got '%s'", weapons[0].Name) - } - - armor, err := ByType(db, TypeArmor) - if err != nil { - t.Fatalf("Failed to get armor: %v", err) - } - - if len(armor) != 1 { - t.Errorf("Expected 1 armor, got %d", len(armor)) - } -} - -func TestBuilder(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Create new item using builder - item, err := NewBuilder(db). - WithType(TypeWeapon). - WithName("Builder Sword"). - WithValue(500). - WithAtt(25). - WithSpecial("dexterity,10"). - Create() - - if err != nil { - t.Fatalf("Failed to create item with builder: %v", err) - } - - if item.ID == 0 { - t.Error("Expected non-zero ID after creation") - } - if item.Name != "Builder Sword" { - t.Errorf("Expected name 'Builder Sword', got '%s'", item.Name) - } - if item.Type != TypeWeapon { - t.Errorf("Expected type %d, got %d", TypeWeapon, item.Type) - } - if item.Value != 500 { - t.Errorf("Expected value 500, got %d", item.Value) - } - if item.Att != 25 { - t.Errorf("Expected att 25, got %d", item.Att) - } - if item.Special != "dexterity,10" { - t.Errorf("Expected special 'dexterity,10', got '%s'", item.Special) - } - - // Verify it was saved to database - foundItem, err := Find(db, item.ID) - if err != nil { - t.Fatalf("Failed to find created item: %v", err) - } - - if foundItem.Name != "Builder Sword" { - t.Errorf("Created item not found in database") - } -} - -func TestSave(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - item, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find item: %v", err) - } - - // Modify item - item.Name = "Updated Sword" - item.Value = 150 - - // Save changes - err = item.Save() - if err != nil { - t.Fatalf("Failed to save item: %v", err) - } - - // Verify changes were saved - updatedItem, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find updated item: %v", err) - } - - if updatedItem.Name != "Updated Sword" { - t.Errorf("Expected updated name 'Updated Sword', got '%s'", updatedItem.Name) - } - if updatedItem.Value != 150 { - t.Errorf("Expected updated value 150, got %d", updatedItem.Value) - } -} - -func TestDelete(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - item, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find item: %v", err) - } - - // Delete item - err = item.Delete() - if err != nil { - t.Fatalf("Failed to delete item: %v", err) - } - - // Verify item was deleted - _, err = Find(db, 1) - if err == nil { - t.Error("Expected error when finding deleted item") - } -} - -func TestItemTypeMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - sword, _ := Find(db, 1) - armor, _ := Find(db, 2) - shield, _ := Find(db, 3) - - // Test IsWeapon - if !sword.IsWeapon() { - t.Error("Expected sword to be weapon") - } - if armor.IsWeapon() { - t.Error("Expected armor not to be weapon") - } - - // Test IsArmor - if !armor.IsArmor() { - t.Error("Expected armor to be armor") - } - if sword.IsArmor() { - t.Error("Expected sword not to be armor") - } - - // Test IsShield - if !shield.IsShield() { - t.Error("Expected shield to be shield") - } - if sword.IsShield() { - t.Error("Expected sword not to be shield") - } - - // Test TypeName - if sword.TypeName() != "Weapon" { - t.Errorf("Expected sword type name 'Weapon', got '%s'", sword.TypeName()) - } - if armor.TypeName() != "Armor" { - t.Errorf("Expected armor type name 'Armor', got '%s'", armor.TypeName()) - } - if shield.TypeName() != "Shield" { - t.Errorf("Expected shield type name 'Shield', got '%s'", shield.TypeName()) - } -} diff --git a/internal/monsters/monsters_test.go b/internal/monsters/monsters_test.go deleted file mode 100644 index 11e6c75..0000000 --- a/internal/monsters/monsters_test.go +++ /dev/null @@ -1,408 +0,0 @@ -package monsters - -import ( - "os" - "testing" - - "dk/internal/database" -) - -func setupTestDB(t *testing.T) *database.DB { - testDB := "test_monsters.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 monsters table - createTable := `CREATE TABLE monsters ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - max_hp INTEGER NOT NULL DEFAULT 0, - max_dmg INTEGER NOT NULL DEFAULT 0, - armor INTEGER NOT NULL DEFAULT 0, - level INTEGER NOT NULL DEFAULT 0, - max_exp INTEGER NOT NULL DEFAULT 0, - max_gold INTEGER NOT NULL DEFAULT 0, - immune INTEGER NOT NULL DEFAULT 0 - )` - - if err := db.Exec(createTable); err != nil { - t.Fatalf("Failed to create monsters table: %v", err) - } - - // Insert test data - testMonsters := `INSERT INTO monsters (name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune) VALUES - ('Blue Slime', 4, 3, 1, 1, 1, 1, 0), - ('Red Slime', 6, 5, 1, 1, 2, 1, 0), - ('Shadow', 10, 9, 3, 2, 6, 2, 1), - ('Silver Slime', 15, 100, 200, 30, 15, 1000, 2), - ('Raven', 16, 13, 5, 4, 18, 6, 0)` - - if err := db.Exec(testMonsters); err != nil { - t.Fatalf("Failed to insert test monsters: %v", err) - } - - return db -} - -func TestFind(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test finding existing monster - monster, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find monster: %v", err) - } - - if monster.ID != 1 { - t.Errorf("Expected ID 1, got %d", monster.ID) - } - if monster.Name != "Blue Slime" { - t.Errorf("Expected name 'Blue Slime', got '%s'", monster.Name) - } - if monster.MaxHP != 4 { - t.Errorf("Expected max_hp 4, got %d", monster.MaxHP) - } - if monster.MaxDmg != 3 { - t.Errorf("Expected max_dmg 3, got %d", monster.MaxDmg) - } - if monster.Armor != 1 { - t.Errorf("Expected armor 1, got %d", monster.Armor) - } - if monster.Level != 1 { - t.Errorf("Expected level 1, got %d", monster.Level) - } - if monster.MaxExp != 1 { - t.Errorf("Expected max_exp 1, got %d", monster.MaxExp) - } - if monster.MaxGold != 1 { - t.Errorf("Expected max_gold 1, got %d", monster.MaxGold) - } - if monster.Immune != ImmuneNone { - t.Errorf("Expected immune %d, got %d", ImmuneNone, monster.Immune) - } - - // Test finding non-existent monster - _, err = Find(db, 999) - if err == nil { - t.Error("Expected error when finding non-existent monster") - } -} - -func TestAll(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - monsters, err := All(db) - if err != nil { - t.Fatalf("Failed to get all monsters: %v", err) - } - - if len(monsters) != 5 { - t.Errorf("Expected 5 monsters, got %d", len(monsters)) - } - - // Check first monster (should be ordered by level, then id) - if monsters[0].Name != "Blue Slime" { - t.Errorf("Expected first monster to be 'Blue Slime', got '%s'", monsters[0].Name) - } -} - -func TestByLevel(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test level 1 monsters - level1Monsters, err := ByLevel(db, 1) - if err != nil { - t.Fatalf("Failed to get level 1 monsters: %v", err) - } - - if len(level1Monsters) != 2 { - t.Errorf("Expected 2 level 1 monsters, got %d", len(level1Monsters)) - } - - for _, monster := range level1Monsters { - if monster.Level != 1 { - t.Errorf("Expected level 1, got %d for monster %s", monster.Level, monster.Name) - } - } - - // Test level that doesn't exist - noMonsters, err := ByLevel(db, 999) - if err != nil { - t.Fatalf("Failed to query non-existent level: %v", err) - } - - if len(noMonsters) != 0 { - t.Errorf("Expected 0 monsters at level 999, got %d", len(noMonsters)) - } -} - -func TestByLevelRange(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test level range 1-2 - monsters, err := ByLevelRange(db, 1, 2) - if err != nil { - t.Fatalf("Failed to get monsters by level range: %v", err) - } - - if len(monsters) != 3 { - t.Errorf("Expected 3 monsters in level range 1-2, got %d", len(monsters)) - } - - // Verify all monsters are within range - for _, monster := range monsters { - if monster.Level < 1 || monster.Level > 2 { - t.Errorf("Monster %s level %d is outside range 1-2", monster.Name, monster.Level) - } - } - - // Verify ordering (by level, then id) - if monsters[0].Level > monsters[len(monsters)-1].Level { - t.Error("Expected monsters to be ordered by level") - } -} - -func TestByImmunity(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test Hurt immune monsters - hurtImmune, err := ByImmunity(db, ImmuneHurt) - if err != nil { - t.Fatalf("Failed to get Hurt immune monsters: %v", err) - } - - if len(hurtImmune) != 1 { - t.Errorf("Expected 1 Hurt immune monster, got %d", len(hurtImmune)) - } - - if len(hurtImmune) > 0 && hurtImmune[0].Name != "Shadow" { - t.Errorf("Expected Hurt immune monster to be 'Shadow', got '%s'", hurtImmune[0].Name) - } - - // Test Sleep immune monsters - sleepImmune, err := ByImmunity(db, ImmuneSleep) - if err != nil { - t.Fatalf("Failed to get Sleep immune monsters: %v", err) - } - - if len(sleepImmune) != 1 { - t.Errorf("Expected 1 Sleep immune monster, got %d", len(sleepImmune)) - } - - // Test no immunity monsters - noImmunity, err := ByImmunity(db, ImmuneNone) - if err != nil { - t.Fatalf("Failed to get non-immune monsters: %v", err) - } - - if len(noImmunity) != 3 { - t.Errorf("Expected 3 non-immune monsters, got %d", len(noImmunity)) - } -} - -func TestBuilder(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Create new monster using builder - monster, err := NewBuilder(db). - WithName("Test Dragon"). - WithMaxHP(100). - WithMaxDmg(25). - WithArmor(10). - WithLevel(15). - WithMaxExp(500). - WithMaxGold(100). - WithImmunity(ImmuneHurt). - Create() - - if err != nil { - t.Fatalf("Failed to create monster with builder: %v", err) - } - - if monster.ID == 0 { - t.Error("Expected non-zero ID after creation") - } - if monster.Name != "Test Dragon" { - t.Errorf("Expected name 'Test Dragon', got '%s'", monster.Name) - } - if monster.MaxHP != 100 { - t.Errorf("Expected max_hp 100, got %d", monster.MaxHP) - } - if monster.MaxDmg != 25 { - t.Errorf("Expected max_dmg 25, got %d", monster.MaxDmg) - } - if monster.Armor != 10 { - t.Errorf("Expected armor 10, got %d", monster.Armor) - } - if monster.Level != 15 { - t.Errorf("Expected level 15, got %d", monster.Level) - } - if monster.MaxExp != 500 { - t.Errorf("Expected max_exp 500, got %d", monster.MaxExp) - } - if monster.MaxGold != 100 { - t.Errorf("Expected max_gold 100, got %d", monster.MaxGold) - } - if monster.Immune != ImmuneHurt { - t.Errorf("Expected immune %d, got %d", ImmuneHurt, monster.Immune) - } - - // Verify it was saved to database - foundMonster, err := Find(db, monster.ID) - if err != nil { - t.Fatalf("Failed to find created monster: %v", err) - } - - if foundMonster.Name != "Test Dragon" { - t.Errorf("Created monster not found in database") - } -} - -func TestSave(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - monster, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find monster: %v", err) - } - - // Modify monster - monster.Name = "Updated Blue Slime" - monster.MaxHP = 8 - monster.Level = 2 - - // Save changes - err = monster.Save() - if err != nil { - t.Fatalf("Failed to save monster: %v", err) - } - - // Verify changes were saved - updatedMonster, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find updated monster: %v", err) - } - - if updatedMonster.Name != "Updated Blue Slime" { - t.Errorf("Expected updated name 'Updated Blue Slime', got '%s'", updatedMonster.Name) - } - if updatedMonster.MaxHP != 8 { - t.Errorf("Expected updated max_hp 8, got %d", updatedMonster.MaxHP) - } - if updatedMonster.Level != 2 { - t.Errorf("Expected updated level 2, got %d", updatedMonster.Level) - } -} - -func TestDelete(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - monster, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find monster: %v", err) - } - - // Delete monster - err = monster.Delete() - if err != nil { - t.Fatalf("Failed to delete monster: %v", err) - } - - // Verify monster was deleted - _, err = Find(db, 1) - if err == nil { - t.Error("Expected error when finding deleted monster") - } -} - -func TestImmunityMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - blueSlime, _ := Find(db, 1) // No immunity - shadow, _ := Find(db, 3) // Hurt immune - silverSlime, _ := Find(db, 4) // Sleep immune - - // Test IsHurtImmune - if blueSlime.IsHurtImmune() { - t.Error("Expected blue slime not to be Hurt immune") - } - if !shadow.IsHurtImmune() { - t.Error("Expected shadow to be Hurt immune") - } - if silverSlime.IsHurtImmune() { - t.Error("Expected silver slime not to be Hurt immune") - } - - // Test IsSleepImmune - if blueSlime.IsSleepImmune() { - t.Error("Expected blue slime not to be Sleep immune") - } - if shadow.IsSleepImmune() { - t.Error("Expected shadow not to be Sleep immune") - } - if !silverSlime.IsSleepImmune() { - t.Error("Expected silver slime to be Sleep immune") - } - - // Test HasImmunity - if blueSlime.HasImmunity() { - t.Error("Expected blue slime to have no immunity") - } - if !shadow.HasImmunity() { - t.Error("Expected shadow to have immunity") - } - if !silverSlime.HasImmunity() { - t.Error("Expected silver slime to have immunity") - } - - // Test ImmunityName - if blueSlime.ImmunityName() != "None" { - t.Errorf("Expected blue slime immunity name 'None', got '%s'", blueSlime.ImmunityName()) - } - if shadow.ImmunityName() != "Hurt Spells" { - t.Errorf("Expected shadow immunity name 'Hurt Spells', got '%s'", shadow.ImmunityName()) - } - if silverSlime.ImmunityName() != "Sleep Spells" { - t.Errorf("Expected silver slime immunity name 'Sleep Spells', got '%s'", silverSlime.ImmunityName()) - } -} - -func TestUtilityMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - blueSlime, _ := Find(db, 1) - - // Test DifficultyRating - expectedDifficulty := float64(4+3+1) / float64(1) // (HP + Damage + Armor) / Level - if blueSlime.DifficultyRating() != expectedDifficulty { - t.Errorf("Expected difficulty rating %.2f, got %.2f", expectedDifficulty, blueSlime.DifficultyRating()) - } - - // Test ExpPerHP - expectedExpPerHP := float64(1) / float64(4) // Exp / HP - if blueSlime.ExpPerHP() != expectedExpPerHP { - t.Errorf("Expected exp per HP %.2f, got %.2f", expectedExpPerHP, blueSlime.ExpPerHP()) - } - - // Test GoldPerHP - expectedGoldPerHP := float64(1) / float64(4) // Gold / HP - if blueSlime.GoldPerHP() != expectedGoldPerHP { - t.Errorf("Expected gold per HP %.2f, got %.2f", expectedGoldPerHP, blueSlime.GoldPerHP()) - } -} \ No newline at end of file diff --git a/internal/news/news_test.go b/internal/news/news_test.go deleted file mode 100644 index 37ef284..0000000 --- a/internal/news/news_test.go +++ /dev/null @@ -1,459 +0,0 @@ -package news - -import ( - "os" - "testing" - "time" - - "dk/internal/database" -) - -func setupTestDB(t *testing.T) *database.DB { - testDB := "test_news.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 news table - createTable := `CREATE TABLE news ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - author INTEGER NOT NULL, - posted INTEGER NOT NULL DEFAULT (unixepoch()), - content TEXT NOT NULL - )` - - if err := db.Exec(createTable); err != nil { - t.Fatalf("Failed to create news table: %v", err) - } - - // Insert test data with specific timestamps for predictable testing - now := time.Now().Unix() - testNews := `INSERT INTO news (author, posted, content) VALUES - (1, ?, 'First news post about game updates'), - (2, ?, 'Second post from different author'), - (1, ?, 'Third post - recent update'), - (3, ?, 'Fourth post from admin'), - (2, ?, 'Fifth post - maintenance notice')` - - timestamps := []any{ - now - 86400*7, // 1 week ago - now - 86400*5, // 5 days ago - now - 86400*2, // 2 days ago - now - 86400*1, // 1 day ago - now - 3600, // 1 hour ago - } - - if err := db.Exec(testNews, timestamps...); err != nil { - t.Fatalf("Failed to insert test news: %v", err) - } - - return db -} - -func TestFind(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test finding existing news - news, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find news: %v", err) - } - - if news.ID != 1 { - t.Errorf("Expected ID 1, got %d", news.ID) - } - if news.Author != 1 { - t.Errorf("Expected author 1, got %d", news.Author) - } - if news.Content != "First news post about game updates" { - t.Errorf("Expected specific content, got '%s'", news.Content) - } - if news.Posted == 0 { - t.Error("Expected non-zero posted timestamp") - } - - // Test finding non-existent news - _, err = Find(db, 999) - if err == nil { - t.Error("Expected error when finding non-existent news") - } -} - -func TestAll(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - newsPosts, err := All(db) - if err != nil { - t.Fatalf("Failed to get all news: %v", err) - } - - if len(newsPosts) != 5 { - t.Errorf("Expected 5 news posts, got %d", len(newsPosts)) - } - - // Check ordering (newest first) - if len(newsPosts) >= 2 { - if newsPosts[0].Posted < newsPosts[1].Posted { - t.Error("Expected news to be ordered by posted time (newest first)") - } - } - - // First post should be the most recent (1 hour ago) - if newsPosts[0].Content != "Fifth post - maintenance notice" { - t.Errorf("Expected newest post first, got '%s'", newsPosts[0].Content) - } -} - -func TestByAuthor(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test posts by author 1 - author1Posts, err := ByAuthor(db, 1) - if err != nil { - t.Fatalf("Failed to get news by author: %v", err) - } - - if len(author1Posts) != 2 { - t.Errorf("Expected 2 posts by author 1, got %d", len(author1Posts)) - } - - // Verify all posts are by author 1 - for _, post := range author1Posts { - if post.Author != 1 { - t.Errorf("Expected author 1, got %d", post.Author) - } - } - - // Check ordering (newest first) - if len(author1Posts) == 2 { - if author1Posts[0].Content != "Third post - recent update" { - t.Errorf("Expected newest post by author 1 first") - } - } - - // Test author with no posts - noPosts, err := ByAuthor(db, 999) - if err != nil { - t.Fatalf("Failed to query non-existent author: %v", err) - } - - if len(noPosts) != 0 { - t.Errorf("Expected 0 posts by non-existent author, got %d", len(noPosts)) - } -} - -func TestRecent(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test getting 3 most recent posts - recentPosts, err := Recent(db, 3) - if err != nil { - t.Fatalf("Failed to get recent news: %v", err) - } - - if len(recentPosts) != 3 { - t.Errorf("Expected 3 recent posts, got %d", len(recentPosts)) - } - - // Check ordering (newest first) - if len(recentPosts) >= 2 { - if recentPosts[0].Posted < recentPosts[1].Posted { - t.Error("Expected recent posts to be ordered newest first") - } - } - - // Test getting more posts than exist - allRecentPosts, err := Recent(db, 10) - if err != nil { - t.Fatalf("Failed to get recent news with high limit: %v", err) - } - - if len(allRecentPosts) != 5 { - t.Errorf("Expected 5 posts (all available), got %d", len(allRecentPosts)) - } -} - -func TestSince(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test posts since 3 days ago - threeDaysAgo := time.Now().AddDate(0, 0, -3).Unix() - recentPosts, err := Since(db, threeDaysAgo) - if err != nil { - t.Fatalf("Failed to get news since timestamp: %v", err) - } - - // Should get posts from 2 days ago, 1 day ago, and 1 hour ago - expectedCount := 3 - if len(recentPosts) != expectedCount { - t.Errorf("Expected %d posts since 3 days ago, got %d", expectedCount, len(recentPosts)) - } - - // Verify all posts are since the timestamp - for _, post := range recentPosts { - if post.Posted < threeDaysAgo { - t.Errorf("Post with timestamp %d is before the 'since' timestamp %d", post.Posted, threeDaysAgo) - } - } - - // Test with future timestamp (should return no posts) - futurePosts, err := Since(db, time.Now().AddDate(0, 0, 1).Unix()) - if err != nil { - t.Fatalf("Failed to query future timestamp: %v", err) - } - - if len(futurePosts) != 0 { - t.Errorf("Expected 0 posts since future timestamp, got %d", len(futurePosts)) - } -} - -func TestBetween(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test posts between 6 days ago and 1 day ago - start := time.Now().AddDate(0, 0, -6).Unix() - end := time.Now().AddDate(0, 0, -1).Unix() - - betweenPosts, err := Between(db, start, end) - if err != nil { - t.Fatalf("Failed to get news between timestamps: %v", err) - } - - // Should get posts from 5 days ago, 2 days ago, and 1 day ago - expectedCount := 3 - if len(betweenPosts) != expectedCount { - t.Errorf("Expected %d posts between timestamps, got %d", expectedCount, len(betweenPosts)) - } - - // Verify all posts are within the range - for _, post := range betweenPosts { - if post.Posted < start || post.Posted > end { - t.Errorf("Post with timestamp %d is outside range [%d, %d]", post.Posted, start, end) - } - } - - // Test with narrow range (should return fewer posts) - narrowStart := time.Now().AddDate(0, 0, -2).Unix() - narrowEnd := time.Now().AddDate(0, 0, -1).Unix() - - narrowPosts, err := Between(db, narrowStart, narrowEnd) - if err != nil { - t.Fatalf("Failed to get news in narrow range: %v", err) - } - - if len(narrowPosts) != 2 { // 2 days ago and 1 day ago - t.Errorf("Expected 2 posts in narrow range, got %d", len(narrowPosts)) - } -} - -func TestBuilder(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Create new news using builder - testTime := time.Now() - news, err := NewBuilder(db). - WithAuthor(5). - WithContent("Test news content from builder"). - WithPostedTime(testTime). - Create() - - if err != nil { - t.Fatalf("Failed to create news with builder: %v", err) - } - - if news.ID == 0 { - t.Error("Expected non-zero ID after creation") - } - if news.Author != 5 { - t.Errorf("Expected author 5, got %d", news.Author) - } - if news.Content != "Test news content from builder" { - t.Errorf("Expected specific content, got '%s'", news.Content) - } - if news.Posted != testTime.Unix() { - t.Errorf("Expected posted time %d, got %d", testTime.Unix(), news.Posted) - } - - // Verify it was saved to database - foundNews, err := Find(db, news.ID) - if err != nil { - t.Fatalf("Failed to find created news: %v", err) - } - - if foundNews.Content != "Test news content from builder" { - t.Errorf("Created news not found in database") - } - - // Test builder with default timestamp - defaultNews, err := NewBuilder(db). - WithAuthor(1). - WithContent("News with default timestamp"). - Create() - - if err != nil { - t.Fatalf("Failed to create news with default timestamp: %v", err) - } - - // Should have recent timestamp (within last minute) - if time.Since(defaultNews.PostedTime()) > time.Minute { - t.Error("Expected default timestamp to be recent") - } -} - -func TestSave(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - news, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find news: %v", err) - } - - // Modify news - news.Author = 999 - news.Content = "Updated content" - news.Posted = time.Now().Unix() - - // Save changes - err = news.Save() - if err != nil { - t.Fatalf("Failed to save news: %v", err) - } - - // Verify changes were saved - updatedNews, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find updated news: %v", err) - } - - if updatedNews.Author != 999 { - t.Errorf("Expected updated author 999, got %d", updatedNews.Author) - } - if updatedNews.Content != "Updated content" { - t.Errorf("Expected updated content, got '%s'", updatedNews.Content) - } -} - -func TestDelete(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - news, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find news: %v", err) - } - - // Delete news - err = news.Delete() - if err != nil { - t.Fatalf("Failed to delete news: %v", err) - } - - // Verify news was deleted - _, err = Find(db, 1) - if err == nil { - t.Error("Expected error when finding deleted news") - } -} - -func TestUtilityMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - news, _ := Find(db, 1) - - // Test PostedTime - postedTime := news.PostedTime() - if postedTime.IsZero() { - t.Error("Expected non-zero posted time") - } - - // Test IsRecent (should be false for old posts initially) - if news.IsRecent() { - t.Error("Expected old news post not to be recent") - } - - // Test SetPostedTime - newTime := time.Now().Add(-2 * time.Hour) - news.SetPostedTime(newTime) - if news.Posted != newTime.Unix() { - t.Errorf("Expected posted timestamp %d, got %d", newTime.Unix(), news.Posted) - } - - // Test IsRecent (should be true after setting to 2 hours ago) - if !news.IsRecent() { - t.Error("Expected news post from 2 hours ago to be recent") - } - - // Create recent post - recentNews, _ := NewBuilder(db). - WithAuthor(1). - WithContent("Recent post"). - Create() - - if !recentNews.IsRecent() { - t.Error("Expected newly created post to be recent") - } - - // Test Age - age := news.Age() - if age < 0 { - t.Error("Expected positive age") - } - - // Test IsAuthor - if !news.IsAuthor(news.Author) { - t.Error("Expected IsAuthor to return true for correct author") - } - if news.IsAuthor(999) { - t.Error("Expected IsAuthor to return false for incorrect author") - } - - // Test Preview - longContent := "This is a very long content that should be truncated when preview is called" - news.Content = longContent - - preview := news.Preview(20) - if len(preview) > 20 { - t.Errorf("Expected preview length <= 20, got %d", len(preview)) - } - if preview[len(preview)-3:] != "..." { - t.Error("Expected preview to end with ellipsis") - } - - shortPreview := news.Preview(100) // Longer than content - if shortPreview != longContent { - t.Error("Expected short content to not be truncated") - } - - // Test WordCount - news.Content = "This is a test with five words" - wordCount := news.WordCount() - if wordCount != 7 { - t.Errorf("Expected 7 words, got %d", wordCount) - } - - news.Content = "" - emptyWordCount := news.WordCount() - if emptyWordCount != 0 { - t.Errorf("Expected 0 words for empty content, got %d", emptyWordCount) - } - - news.Content = "OneWord" - oneWordCount := news.WordCount() - if oneWordCount != 1 { - t.Errorf("Expected 1 word, got %d", oneWordCount) - } -} diff --git a/internal/password/password_test.go b/internal/password/password_test.go deleted file mode 100644 index d61e7a7..0000000 --- a/internal/password/password_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package password - -import ( - "testing" -) - -func TestHashAndVerify(t *testing.T) { - password := "Demo123!" - - // Test hashing - hash, err := Hash(password) - if err != nil { - t.Fatalf("Failed to hash password: %v", err) - } - - // Test that hash is not empty - if hash == "" { - t.Fatal("Hash should not be empty") - } - - // Test that hash contains expected format - if hash[:9] != "$argon2id" { - t.Fatal("Hash should start with $argon2id") - } - - // Test verification with correct password - valid, err := Verify(password, hash) - if err != nil { - t.Fatalf("Failed to verify password: %v", err) - } - if !valid { - t.Fatal("Password should be valid") - } - - // Test verification with incorrect password - valid, err = Verify("wrongpassword", hash) - if err != nil { - t.Fatalf("Failed to verify wrong password: %v", err) - } - if valid { - t.Fatal("Wrong password should not be valid") - } - - // Test that hashing same password twice produces different hashes (due to salt) - hash2, err := Hash(password) - if err != nil { - t.Fatalf("Failed to hash password second time: %v", err) - } - if hash == hash2 { - t.Fatal("Hashing same password twice should produce different hashes due to salt") - } - - // But both hashes should verify correctly - valid, err = Verify(password, hash2) - if err != nil { - t.Fatalf("Failed to verify second hash: %v", err) - } - if !valid { - t.Fatal("Second hash should also be valid") - } -} \ No newline at end of file diff --git a/internal/routes/auth.go b/internal/routes/auth.go index 9a345cf..e5039d3 100644 --- a/internal/routes/auth.go +++ b/internal/routes/auth.go @@ -156,13 +156,13 @@ func processRegister() router.Handler { } // Check if username already exists - if _, err := users.GetByUsername(database.GetDB(), username); err == nil { + if _, err := users.GetByUsername(username); err == nil { showRegisterError(ctx, "Username already exists", username, email) return } // Check if email already exists - if _, err := users.GetByEmail(database.GetDB(), email); err == nil { + if _, err := users.GetByEmail(email); err == nil { showRegisterError(ctx, "Email already registered", username, email) return } @@ -324,7 +324,7 @@ func createUser(user *users.User) error { } // Get the user ID (simplified - in real app you'd return it from insert) - createdUser, err := users.GetByUsername(database.GetDB(), user.Username) + createdUser, err := users.GetByUsername(user.Username) if err != nil { return fmt.Errorf("failed to get created user: %w", err) } diff --git a/internal/spells/spells_test.go b/internal/spells/spells_test.go deleted file mode 100644 index b259bbb..0000000 --- a/internal/spells/spells_test.go +++ /dev/null @@ -1,445 +0,0 @@ -package spells - -import ( - "os" - "testing" - - "dk/internal/database" -) - -func setupTestDB(t *testing.T) *database.DB { - testDB := "test_spells.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 spells table - createTable := `CREATE TABLE spells ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - mp INTEGER NOT NULL DEFAULT 0, - attribute INTEGER NOT NULL DEFAULT 0, - type INTEGER NOT NULL DEFAULT 0 - )` - - if err := db.Exec(createTable); err != nil { - t.Fatalf("Failed to create spells table: %v", err) - } - - // Insert test data - testSpells := `INSERT INTO spells (name, mp, attribute, type) VALUES - ('Heal', 5, 10, 1), - ('Revive', 10, 25, 1), - ('Hurt', 5, 15, 2), - ('Pain', 12, 35, 2), - ('Sleep', 10, 5, 3), - ('Dream', 30, 9, 3), - ('Craze', 10, 10, 4), - ('Ward', 10, 10, 5)` - - if err := db.Exec(testSpells); err != nil { - t.Fatalf("Failed to insert test spells: %v", err) - } - - return db -} - -func TestFind(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test finding existing spell - spell, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find spell: %v", err) - } - - if spell.ID != 1 { - t.Errorf("Expected ID 1, got %d", spell.ID) - } - if spell.Name != "Heal" { - t.Errorf("Expected name 'Heal', got '%s'", spell.Name) - } - if spell.MP != 5 { - t.Errorf("Expected MP 5, got %d", spell.MP) - } - if spell.Attribute != 10 { - t.Errorf("Expected attribute 10, got %d", spell.Attribute) - } - if spell.Type != TypeHealing { - t.Errorf("Expected type %d, got %d", TypeHealing, spell.Type) - } - - // Test finding non-existent spell - _, err = Find(db, 999) - if err == nil { - t.Error("Expected error when finding non-existent spell") - } -} - -func TestAll(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - spells, err := All(db) - if err != nil { - t.Fatalf("Failed to get all spells: %v", err) - } - - if len(spells) != 8 { - t.Errorf("Expected 8 spells, got %d", len(spells)) - } - - // Check ordering (by type, then MP, then ID) - if spells[0].Type > spells[1].Type { - t.Error("Expected spells to be ordered by type first") - } -} - -func TestByType(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test healing spells - healingSpells, err := ByType(db, TypeHealing) - if err != nil { - t.Fatalf("Failed to get healing spells: %v", err) - } - - if len(healingSpells) != 2 { - t.Errorf("Expected 2 healing spells, got %d", len(healingSpells)) - } - - for _, spell := range healingSpells { - if spell.Type != TypeHealing { - t.Errorf("Expected healing spell, got type %d", spell.Type) - } - } - - // Test hurt spells - hurtSpells, err := ByType(db, TypeHurt) - if err != nil { - t.Fatalf("Failed to get hurt spells: %v", err) - } - - if len(hurtSpells) != 2 { - t.Errorf("Expected 2 hurt spells, got %d", len(hurtSpells)) - } - - // Verify ordering within type (by MP) - if hurtSpells[0].MP > hurtSpells[1].MP { - t.Error("Expected spells within type to be ordered by MP") - } -} - -func TestByMaxMP(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test spells with MP <= 10 - lowMPSpells, err := ByMaxMP(db, 10) - if err != nil { - t.Fatalf("Failed to get low MP spells: %v", err) - } - - expectedCount := 6 // Heal(5), Hurt(5), Sleep(10), Craze(10), Revive(10), Ward(10) - if len(lowMPSpells) != expectedCount { - t.Errorf("Expected %d spells with MP <= 10, got %d", expectedCount, len(lowMPSpells)) - } - - // Verify all spells have MP <= 10 - for _, spell := range lowMPSpells { - if spell.MP > 10 { - t.Errorf("Spell %s has MP %d, expected <= 10", spell.Name, spell.MP) - } - } - - // Test very low MP threshold - veryLowMPSpells, err := ByMaxMP(db, 5) - if err != nil { - t.Fatalf("Failed to get very low MP spells: %v", err) - } - - if len(veryLowMPSpells) != 2 { // Only Heal and Hurt - t.Errorf("Expected 2 spells with MP <= 5, got %d", len(veryLowMPSpells)) - } -} - -func TestByTypeAndMaxMP(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test healing spells with MP <= 10 - healingSpells, err := ByTypeAndMaxMP(db, TypeHealing, 10) - if err != nil { - t.Fatalf("Failed to get healing spells with MP <= 10: %v", err) - } - - expectedCount := 2 // Heal(5) and Revive(10) - if len(healingSpells) != expectedCount { - t.Errorf("Expected %d healing spells with MP <= 10, got %d", expectedCount, len(healingSpells)) - } - - // Verify all are healing spells and within MP limit - for _, spell := range healingSpells { - if spell.Type != TypeHealing { - t.Errorf("Expected healing spell, got type %d", spell.Type) - } - if spell.MP > 10 { - t.Errorf("Spell %s has MP %d, expected <= 10", spell.Name, spell.MP) - } - } - - // Test hurt spells with very low MP - lowHurtSpells, err := ByTypeAndMaxMP(db, TypeHurt, 5) - if err != nil { - t.Fatalf("Failed to get hurt spells with MP <= 5: %v", err) - } - - if len(lowHurtSpells) != 1 { // Only Hurt(5) - t.Errorf("Expected 1 hurt spell with MP <= 5, got %d", len(lowHurtSpells)) - } -} - -func TestByName(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test finding existing spell by name - spell, err := ByName(db, "Heal") - if err != nil { - t.Fatalf("Failed to find spell by name: %v", err) - } - - if spell.Name != "Heal" { - t.Errorf("Expected name 'Heal', got '%s'", spell.Name) - } - if spell.Type != TypeHealing { - t.Errorf("Expected healing spell, got type %d", spell.Type) - } - - // Test case insensitivity - spellLower, err := ByName(db, "heal") - if err != nil { - t.Fatalf("Failed to find spell by lowercase name: %v", err) - } - - if spellLower.ID != spell.ID { - t.Error("Case insensitive search should return same spell") - } - - // Test non-existent spell - _, err = ByName(db, "Fireball") - if err == nil { - t.Error("Expected error when finding non-existent spell by name") - } -} - -func TestBuilder(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Create new spell using builder - spell, err := NewBuilder(db). - WithName("Lightning"). - WithMP(25). - WithAttribute(60). - WithType(TypeHurt). - Create() - - if err != nil { - t.Fatalf("Failed to create spell with builder: %v", err) - } - - if spell.ID == 0 { - t.Error("Expected non-zero ID after creation") - } - if spell.Name != "Lightning" { - t.Errorf("Expected name 'Lightning', got '%s'", spell.Name) - } - if spell.MP != 25 { - t.Errorf("Expected MP 25, got %d", spell.MP) - } - if spell.Attribute != 60 { - t.Errorf("Expected attribute 60, got %d", spell.Attribute) - } - if spell.Type != TypeHurt { - t.Errorf("Expected type %d, got %d", TypeHurt, spell.Type) - } - - // Verify it was saved to database - foundSpell, err := Find(db, spell.ID) - if err != nil { - t.Fatalf("Failed to find created spell: %v", err) - } - - if foundSpell.Name != "Lightning" { - t.Errorf("Created spell not found in database") - } -} - -func TestSave(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - spell, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find spell: %v", err) - } - - // Modify spell - spell.Name = "Enhanced Heal" - spell.MP = 7 - spell.Attribute = 15 - - // Save changes - err = spell.Save() - if err != nil { - t.Fatalf("Failed to save spell: %v", err) - } - - // Verify changes were saved - updatedSpell, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find updated spell: %v", err) - } - - if updatedSpell.Name != "Enhanced Heal" { - t.Errorf("Expected updated name 'Enhanced Heal', got '%s'", updatedSpell.Name) - } - if updatedSpell.MP != 7 { - t.Errorf("Expected updated MP 7, got %d", updatedSpell.MP) - } - if updatedSpell.Attribute != 15 { - t.Errorf("Expected updated attribute 15, got %d", updatedSpell.Attribute) - } -} - -func TestDelete(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - spell, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find spell: %v", err) - } - - // Delete spell - err = spell.Delete() - if err != nil { - t.Fatalf("Failed to delete spell: %v", err) - } - - // Verify spell was deleted - _, err = Find(db, 1) - if err == nil { - t.Error("Expected error when finding deleted spell") - } -} - -func TestSpellTypeMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - heal, _ := Find(db, 1) // Healing - hurt, _ := Find(db, 3) // Hurt - sleep, _ := Find(db, 5) // Sleep - craze, _ := Find(db, 7) // Attack boost - ward, _ := Find(db, 8) // Defense boost - - // Test type checking methods - if !heal.IsHealing() { - t.Error("Expected Heal to be healing spell") - } - if heal.IsHurt() { - t.Error("Expected Heal not to be hurt spell") - } - - if !hurt.IsHurt() { - t.Error("Expected Hurt to be hurt spell") - } - if hurt.IsHealing() { - t.Error("Expected Hurt not to be healing spell") - } - - if !sleep.IsSleep() { - t.Error("Expected Sleep to be sleep spell") - } - - if !craze.IsAttackBoost() { - t.Error("Expected Craze to be attack boost spell") - } - - if !ward.IsDefenseBoost() { - t.Error("Expected Ward to be defense boost spell") - } - - // Test TypeName - if heal.TypeName() != "Healing" { - t.Errorf("Expected Heal type name 'Healing', got '%s'", heal.TypeName()) - } - if hurt.TypeName() != "Hurt" { - t.Errorf("Expected Hurt type name 'Hurt', got '%s'", hurt.TypeName()) - } - if sleep.TypeName() != "Sleep" { - t.Errorf("Expected Sleep type name 'Sleep', got '%s'", sleep.TypeName()) - } - if craze.TypeName() != "Attack Boost" { - t.Errorf("Expected Craze type name 'Attack Boost', got '%s'", craze.TypeName()) - } - if ward.TypeName() != "Defense Boost" { - t.Errorf("Expected Ward type name 'Defense Boost', got '%s'", ward.TypeName()) - } -} - -func TestUtilityMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - heal, _ := Find(db, 1) // MP: 5, Attribute: 10 - hurt, _ := Find(db, 3) // MP: 5, Attribute: 15 - sleep, _ := Find(db, 5) // MP: 10, Attribute: 5 - - // Test CanCast - if !heal.CanCast(10) { - t.Error("Expected to be able to cast Heal with 10 MP") - } - if heal.CanCast(3) { - t.Error("Expected not to be able to cast Heal with 3 MP") - } - - // Test Efficiency - expectedHealEff := float64(10) / float64(5) // 2.0 - if heal.Efficiency() != expectedHealEff { - t.Errorf("Expected Heal efficiency %.2f, got %.2f", expectedHealEff, heal.Efficiency()) - } - - expectedHurtEff := float64(15) / float64(5) // 3.0 - if hurt.Efficiency() != expectedHurtEff { - t.Errorf("Expected Hurt efficiency %.2f, got %.2f", expectedHurtEff, hurt.Efficiency()) - } - - // Test IsOffensive - if heal.IsOffensive() { - t.Error("Expected Heal not to be offensive") - } - if !hurt.IsOffensive() { - t.Error("Expected Hurt to be offensive") - } - if !sleep.IsOffensive() { - t.Error("Expected Sleep to be offensive") - } - - // Test IsSupport - if !heal.IsSupport() { - t.Error("Expected Heal to be support spell") - } - if hurt.IsSupport() { - t.Error("Expected Hurt not to be support spell") - } -} \ No newline at end of file diff --git a/internal/template/components/components.go b/internal/template/components/components.go index 8291d90..6ab9fad 100644 --- a/internal/template/components/components.go +++ b/internal/template/components/components.go @@ -6,7 +6,6 @@ import ( "dk/internal/auth" "dk/internal/csrf" - "dk/internal/database" "dk/internal/middleware" "dk/internal/router" "dk/internal/template" @@ -47,26 +46,20 @@ func GenerateLeftSide(ctx router.Ctx) string { return "" } - // Get the full user object from database - db := database.GetDB() - if db == nil { - return "" - } - - user, err := users.Find(db, currentUser.ID) + user, err := users.Find(currentUser.ID) if err != nil { return "" } // Pass the user object directly to the template leftSideData := map[string]any{ - "user": user, + "user": user.ToMap(), } return leftSideTmpl.RenderNamed(leftSideData) } -// GenerateRightSide generates the right sidebar content for authenticated users +// GenerateRightSide generates the right sidebar content for authenticated users func GenerateRightSide(ctx router.Ctx) string { if !middleware.IsAuthenticated(ctx) { return "" @@ -84,20 +77,14 @@ func GenerateRightSide(ctx router.Ctx) string { return "" } - // Get the full user object from database - db := database.GetDB() - if db == nil { - return "" - } - - user, err := users.Find(db, currentUser.ID) + user, err := users.Find(currentUser.ID) if err != nil { return "" } // Pass the user object directly to the template rightSideData := map[string]any{ - "user": user, + "user": user.ToMap(), } return rightSideTmpl.RenderNamed(rightSideData) diff --git a/internal/template/template.go b/internal/template/template.go index 367c683..9c0dd2b 100644 --- a/internal/template/template.go +++ b/internal/template/template.go @@ -199,18 +199,36 @@ func (t *Template) replaceDotNotation(content string, data map[string]any) strin func (t *Template) getNestedValue(data map[string]any, path string) any { keys := strings.Split(path, ".") - current := data + var current any = data for i, key := range keys { if i == len(keys)-1 { - return current[key] + // Final key - handle both maps and structs + switch v := current.(type) { + case map[string]any: + return v[key] + default: + return t.getStructField(current, key) + } } - next, ok := current[key] - if !ok { - return nil + // Intermediate key - get the next value + var next any + switch v := current.(type) { + case map[string]any: + var ok bool + next, ok = v[key] + if !ok { + return nil + } + default: + next = t.getStructField(current, key) + if next == nil { + return nil + } } + // Prepare for next iteration switch v := next.(type) { case map[string]any: current = v @@ -229,7 +247,8 @@ func (t *Template) getNestedValue(data map[string]any, path string) any { } current = newMap } else { - return nil + // For structs, keep the struct value for the next iteration + current = next } } } @@ -237,6 +256,32 @@ func (t *Template) getNestedValue(data map[string]any, path string) any { return nil } +// getStructField gets a field value from a struct using reflection +func (t *Template) getStructField(obj any, fieldName string) any { + if obj == nil { + return nil + } + + rv := reflect.ValueOf(obj) + if rv.Kind() == reflect.Ptr { + if rv.IsNil() { + return nil + } + rv = rv.Elem() + } + + if rv.Kind() != reflect.Struct { + return nil + } + + field := rv.FieldByName(fieldName) + if !field.IsValid() { + return nil + } + + return field.Interface() +} + func (t *Template) WriteTo(ctx *fasthttp.RequestCtx, data any) { t.WriteToWithOptions(ctx, data, RenderOptions{ResolveIncludes: true}) } diff --git a/internal/towns/towns_test.go b/internal/towns/towns_test.go deleted file mode 100644 index 1bbed7c..0000000 --- a/internal/towns/towns_test.go +++ /dev/null @@ -1,440 +0,0 @@ -package towns - -import ( - "os" - "testing" - - "dk/internal/database" -) - -func setupTestDB(t *testing.T) *database.DB { - testDB := "test_towns.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 towns table - createTable := `CREATE TABLE towns ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - x INTEGER NOT NULL DEFAULT 0, - y INTEGER NOT NULL DEFAULT 0, - inn_cost INTEGER NOT NULL DEFAULT 0, - map_cost INTEGER NOT NULL DEFAULT 0, - tp_cost INTEGER NOT NULL DEFAULT 0, - shop_list TEXT NOT NULL DEFAULT '' - )` - - if err := db.Exec(createTable); err != nil { - t.Fatalf("Failed to create towns table: %v", err) - } - - // Insert test data - testTowns := `INSERT INTO towns (name, x, y, inn_cost, map_cost, tp_cost, shop_list) VALUES - ('Midworld', 0, 0, 5, 0, 0, '1,2,3,17,18,19'), - ('Roma', 30, 30, 10, 25, 5, '2,3,4,18,19,29'), - ('Bris', 70, -70, 25, 50, 15, '2,3,4,5,18,19,20'), - ('Kalle', -100, 100, 40, 100, 30, '5,6,8,10,12,21,22,23'), - ('Endworld', -250, -250, 125, 9000, 160, '16,27,33')` - - if err := db.Exec(testTowns); err != nil { - t.Fatalf("Failed to insert test towns: %v", err) - } - - return db -} - -func TestFind(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test finding existing town - town, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find town: %v", err) - } - - if town.ID != 1 { - t.Errorf("Expected ID 1, got %d", town.ID) - } - if town.Name != "Midworld" { - t.Errorf("Expected name 'Midworld', got '%s'", town.Name) - } - if town.X != 0 { - t.Errorf("Expected X 0, got %d", town.X) - } - if town.Y != 0 { - t.Errorf("Expected Y 0, got %d", town.Y) - } - if town.InnCost != 5 { - t.Errorf("Expected inn_cost 5, got %d", town.InnCost) - } - if town.MapCost != 0 { - t.Errorf("Expected map_cost 0, got %d", town.MapCost) - } - if town.TPCost != 0 { - t.Errorf("Expected tp_cost 0, got %d", town.TPCost) - } - if town.ShopList != "1,2,3,17,18,19" { - t.Errorf("Expected shop_list '1,2,3,17,18,19', got '%s'", town.ShopList) - } - - // Test finding non-existent town - _, err = Find(db, 999) - if err == nil { - t.Error("Expected error when finding non-existent town") - } -} - -func TestAll(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - towns, err := All(db) - if err != nil { - t.Fatalf("Failed to get all towns: %v", err) - } - - if len(towns) != 5 { - t.Errorf("Expected 5 towns, got %d", len(towns)) - } - - // Check ordering (by ID) - if towns[0].Name != "Midworld" { - t.Errorf("Expected first town to be 'Midworld', got '%s'", towns[0].Name) - } -} - -func TestByName(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test finding existing town by name - town, err := ByName(db, "Midworld") - if err != nil { - t.Fatalf("Failed to find town by name: %v", err) - } - - if town.Name != "Midworld" { - t.Errorf("Expected name 'Midworld', got '%s'", town.Name) - } - if town.X != 0 || town.Y != 0 { - t.Errorf("Expected coordinates (0,0), got (%d,%d)", town.X, town.Y) - } - - // Test case insensitivity - townLower, err := ByName(db, "midworld") - if err != nil { - t.Fatalf("Failed to find town by lowercase name: %v", err) - } - - if townLower.ID != town.ID { - t.Error("Case insensitive search should return same town") - } - - // Test non-existent town - _, err = ByName(db, "Atlantis") - if err == nil { - t.Error("Expected error when finding non-existent town by name") - } -} - -func TestByMaxInnCost(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test towns with inn cost <= 10 - cheapInns, err := ByMaxInnCost(db, 10) - if err != nil { - t.Fatalf("Failed to get towns by max inn cost: %v", err) - } - - expectedCount := 2 // Midworld(5) and Roma(10) - if len(cheapInns) != expectedCount { - t.Errorf("Expected %d towns with inn cost <= 10, got %d", expectedCount, len(cheapInns)) - } - - // Verify all towns have inn cost <= 10 - for _, town := range cheapInns { - if town.InnCost > 10 { - t.Errorf("Town %s has inn cost %d, expected <= 10", town.Name, town.InnCost) - } - } - - // Verify ordering (by inn cost, then ID) - if cheapInns[0].InnCost > cheapInns[1].InnCost { - t.Error("Expected towns to be ordered by inn cost") - } -} - -func TestByMaxTPCost(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test towns with TP cost <= 15 - cheapTP, err := ByMaxTPCost(db, 15) - if err != nil { - t.Fatalf("Failed to get towns by max TP cost: %v", err) - } - - expectedCount := 3 // Midworld(0), Roma(5), Bris(15) - if len(cheapTP) != expectedCount { - t.Errorf("Expected %d towns with TP cost <= 15, got %d", expectedCount, len(cheapTP)) - } - - // Verify all towns have TP cost <= 15 - for _, town := range cheapTP { - if town.TPCost > 15 { - t.Errorf("Town %s has TP cost %d, expected <= 15", town.Name, town.TPCost) - } - } -} - -func TestByDistance(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Test towns within distance 50 from origin (0,0) - nearbyTowns, err := ByDistance(db, 0, 0, 50) - if err != nil { - t.Fatalf("Failed to get towns by distance: %v", err) - } - - // Midworld (0,0) distance=0, Roma (30,30) distance=sqrt(1800)≈42.4 - expectedCount := 2 - if len(nearbyTowns) != expectedCount { - t.Errorf("Expected %d towns within distance 50, got %d", expectedCount, len(nearbyTowns)) - } - - // Verify distances are within limit - for _, town := range nearbyTowns { - distance := town.DistanceFrom(0, 0) - if distance > 50*50 { // Using squared distance - t.Errorf("Town %s distance %.2f is beyond limit", town.Name, distance) - } - } - - // Verify ordering (by distance) - if len(nearbyTowns) >= 2 { - dist1 := nearbyTowns[0].DistanceFrom(0, 0) - dist2 := nearbyTowns[1].DistanceFrom(0, 0) - if dist1 > dist2 { - t.Error("Expected towns to be ordered by distance") - } - } -} - -func TestBuilder(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - // Create new town using builder - town, err := NewBuilder(db). - WithName("Test City"). - WithCoordinates(100, -50). - WithInnCost(20). - WithMapCost(75). - WithTPCost(25). - WithShopItems([]string{"1", "2", "10", "15"}). - Create() - - if err != nil { - t.Fatalf("Failed to create town with builder: %v", err) - } - - if town.ID == 0 { - t.Error("Expected non-zero ID after creation") - } - if town.Name != "Test City" { - t.Errorf("Expected name 'Test City', got '%s'", town.Name) - } - if town.X != 100 { - t.Errorf("Expected X 100, got %d", town.X) - } - if town.Y != -50 { - t.Errorf("Expected Y -50, got %d", town.Y) - } - if town.InnCost != 20 { - t.Errorf("Expected inn cost 20, got %d", town.InnCost) - } - if town.MapCost != 75 { - t.Errorf("Expected map cost 75, got %d", town.MapCost) - } - if town.TPCost != 25 { - t.Errorf("Expected TP cost 25, got %d", town.TPCost) - } - if town.ShopList != "1,2,10,15" { - t.Errorf("Expected shop list '1,2,10,15', got '%s'", town.ShopList) - } - - // Verify it was saved to database - foundTown, err := Find(db, town.ID) - if err != nil { - t.Fatalf("Failed to find created town: %v", err) - } - - if foundTown.Name != "Test City" { - t.Errorf("Created town not found in database") - } -} - -func TestSave(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - town, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find town: %v", err) - } - - // Modify town - town.Name = "Updated Midworld" - town.X = 5 - town.Y = -5 - town.InnCost = 8 - - // Save changes - err = town.Save() - if err != nil { - t.Fatalf("Failed to save town: %v", err) - } - - // Verify changes were saved - updatedTown, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find updated town: %v", err) - } - - if updatedTown.Name != "Updated Midworld" { - t.Errorf("Expected updated name 'Updated Midworld', got '%s'", updatedTown.Name) - } - if updatedTown.X != 5 { - t.Errorf("Expected updated X 5, got %d", updatedTown.X) - } - if updatedTown.Y != -5 { - t.Errorf("Expected updated Y -5, got %d", updatedTown.Y) - } - if updatedTown.InnCost != 8 { - t.Errorf("Expected updated inn cost 8, got %d", updatedTown.InnCost) - } -} - -func TestDelete(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - town, err := Find(db, 1) - if err != nil { - t.Fatalf("Failed to find town: %v", err) - } - - // Delete town - err = town.Delete() - if err != nil { - t.Fatalf("Failed to delete town: %v", err) - } - - // Verify town was deleted - _, err = Find(db, 1) - if err == nil { - t.Error("Expected error when finding deleted town") - } -} - -func TestShopItemMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - town, _ := Find(db, 1) // Midworld with shop_list "1,2,3,17,18,19" - - // Test GetShopItems - items := town.GetShopItems() - expectedItems := []string{"1", "2", "3", "17", "18", "19"} - if len(items) != len(expectedItems) { - t.Errorf("Expected %d shop items, got %d", len(expectedItems), len(items)) - } - - for i, expected := range expectedItems { - if i < len(items) && items[i] != expected { - t.Errorf("Expected item %s at position %d, got %s", expected, i, items[i]) - } - } - - // Test HasShopItem - if !town.HasShopItem("1") { - t.Error("Expected town to have shop item '1'") - } - if !town.HasShopItem("19") { - t.Error("Expected town to have shop item '19'") - } - if town.HasShopItem("99") { - t.Error("Expected town not to have shop item '99'") - } - - // Test SetShopItems - newItems := []string{"5", "10", "15"} - town.SetShopItems(newItems) - if town.ShopList != "5,10,15" { - t.Errorf("Expected shop list '5,10,15', got '%s'", town.ShopList) - } - - // Test with empty shop list - emptyTown, _ := Find(db, 5) // Create a town with empty shop list - emptyTown.ShopList = "" - emptyItems := emptyTown.GetShopItems() - if len(emptyItems) != 0 { - t.Errorf("Expected 0 items for empty shop list, got %d", len(emptyItems)) - } -} - -func TestUtilityMethods(t *testing.T) { - db := setupTestDB(t) - defer db.Close() - - midworld, _ := Find(db, 1) - roma, _ := Find(db, 2) - - // Test DistanceFrom - distance := roma.DistanceFrom(0, 0) // Roma is at (30,30) - expectedDistance := float64(30*30 + 30*30) // 1800 - if distance != expectedDistance { - t.Errorf("Expected distance %.2f, got %.2f", expectedDistance, distance) - } - - // Test IsStartingTown - if !midworld.IsStartingTown() { - t.Error("Expected Midworld to be starting town") - } - if roma.IsStartingTown() { - t.Error("Expected Roma not to be starting town") - } - - // Test CanAffordInn - if !midworld.CanAffordInn(10) { - t.Error("Expected to afford Midworld inn with 10 gold (cost 5)") - } - if midworld.CanAffordInn(3) { - t.Error("Expected not to afford Midworld inn with 3 gold (cost 5)") - } - - // Test CanAffordMap - if !roma.CanAffordMap(30) { - t.Error("Expected to afford Roma map with 30 gold (cost 25)") - } - if roma.CanAffordMap(20) { - t.Error("Expected not to afford Roma map with 20 gold (cost 25)") - } - - // Test CanAffordTeleport - if !roma.CanAffordTeleport(10) { - t.Error("Expected to afford Roma teleport with 10 gold (cost 5)") - } - if roma.CanAffordTeleport(3) { - t.Error("Expected not to afford Roma teleport with 3 gold (cost 5)") - } -} \ No newline at end of file diff --git a/internal/users/builder.go b/internal/users/builder.go index 2feefe2..c789ac5 100644 --- a/internal/users/builder.go +++ b/internal/users/builder.go @@ -12,42 +12,39 @@ import ( // Builder provides a fluent interface for creating users type Builder struct { user *User - db *database.DB } // NewBuilder creates a new user builder with default values -func NewBuilder(db *database.DB) *Builder { +func NewBuilder() *Builder { now := time.Now().Unix() return &Builder{ user: &User{ - db: db, - Verified: 0, // Default unverified - Token: "", // Empty verification token - Registered: now, // Current time - LastOnline: now, // Current time - Auth: 0, // Default no special permissions - X: 0, // Default starting position - Y: 0, // Default starting position - ClassID: 1, // Default to class 1 - Currently: "In Town", // Default status - Fighting: 0, // Default not fighting - HP: 15, // Default starting HP - MP: 0, // Default starting MP - TP: 10, // Default starting TP - MaxHP: 15, // Default starting max HP - MaxMP: 0, // Default starting max MP - MaxTP: 10, // Default starting max TP - Level: 1, // Default starting level - Gold: 100, // Default starting gold - Exp: 0, // Default starting exp - Strength: 5, // Default starting strength - Dexterity: 5, // Default starting dexterity - Attack: 5, // Default starting attack - Defense: 5, // Default starting defense - Spells: "", // No spells initially - Towns: "", // No towns visited initially + Verified: 0, // Default unverified + Token: "", // Empty verification token + Registered: now, // Current time + LastOnline: now, // Current time + Auth: 0, // Default no special permissions + X: 0, // Default starting position + Y: 0, // Default starting position + ClassID: 1, // Default to class 1 + Currently: "In Town", // Default status + Fighting: 0, // Default not fighting + HP: 15, // Default starting HP + MP: 0, // Default starting MP + TP: 10, // Default starting TP + MaxHP: 15, // Default starting max HP + MaxMP: 0, // Default starting max MP + MaxTP: 10, // Default starting max TP + Level: 1, // Default starting level + Gold: 100, // Default starting gold + Exp: 0, // Default starting exp + Strength: 5, // Default starting strength + Dexterity: 5, // Default starting dexterity + Attack: 5, // Default starting attack + Defense: 5, // Default starting defense + Spells: "", // No spells initially + Towns: "", // No towns visited initially }, - db: db, } } @@ -192,13 +189,13 @@ func (b *Builder) WithTowns(towns []string) *Builder { func (b *Builder) Create() (*User, error) { // Use a transaction to ensure we can get the ID var user *User - err := b.db.Transaction(func(tx *database.Tx) error { + err := database.Transaction(func(tx *database.Tx) error { query := `INSERT INTO users (username, password, email, verified, token, registered, last_online, auth, x, y, class_id, currently, fighting, monster_id, monster_hp, monster_sleep, monster_immune, uber_damage, uber_defense, hp, mp, tp, max_hp, max_mp, max_tp, level, gold, exp, gold_bonus, exp_bonus, strength, dexterity, attack, defense, weapon_id, armor_id, shield_id, slot_1_id, slot_2_id, slot_3_id, weapon_name, armor_name, shield_name, - slot_1_name, slot_2_name, slot_3_name, drop_code, spells, towns) + slot_1_name, slot_2_name, slot_3_name, drop_code, spells, towns) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` if err := tx.Exec(query, b.user.Username, b.user.Password, b.user.Email, b.user.Verified, b.user.Token, @@ -232,4 +229,4 @@ func (b *Builder) Create() (*User, error) { } return user, nil -} \ No newline at end of file +} diff --git a/internal/users/users.go b/internal/users/users.go index 859e2bb..b5e16f8 100644 --- a/internal/users/users.go +++ b/internal/users/users.go @@ -62,8 +62,6 @@ type User struct { DropCode int `json:"drop_code"` Spells string `json:"spells"` Towns string `json:"towns"` - - db *database.DB } // userColumns returns the column list for user queries (excluding id for inserts) @@ -77,7 +75,7 @@ func userColumns() string { } // scanUser populates a User struct from a sqlite.Stmt -func scanUser(stmt *sqlite.Stmt, db *database.DB) *User { +func scanUser(stmt *sqlite.Stmt) *User { return &User{ ID: stmt.ColumnInt(0), Username: stmt.ColumnText(1), @@ -129,18 +127,17 @@ func scanUser(stmt *sqlite.Stmt, db *database.DB) *User { DropCode: stmt.ColumnInt(47), Spells: stmt.ColumnText(48), Towns: stmt.ColumnText(49), - db: db, } } // Find retrieves a user by ID -func Find(db *database.DB, id int) (*User, error) { +func Find(id int) (*User, error) { var user *User query := `SELECT ` + userColumns() + ` FROM users WHERE id = ?` - err := db.Query(query, func(stmt *sqlite.Stmt) error { - user = scanUser(stmt, db) + err := database.Query(query, func(stmt *sqlite.Stmt) error { + user = scanUser(stmt) return nil }, id) @@ -156,13 +153,13 @@ func Find(db *database.DB, id int) (*User, error) { } // All retrieves all users ordered by registration date (newest first) -func All(db *database.DB) ([]*User, error) { +func All() ([]*User, error) { var users []*User query := `SELECT ` + userColumns() + ` FROM users ORDER BY registered DESC, id DESC` - err := db.Query(query, func(stmt *sqlite.Stmt) error { - user := scanUser(stmt, db) + err := database.Query(query, func(stmt *sqlite.Stmt) error { + user := scanUser(stmt) users = append(users, user) return nil }) @@ -175,13 +172,13 @@ func All(db *database.DB) ([]*User, error) { } // ByUsername retrieves a user by username (case-insensitive) -func ByUsername(db *database.DB, username string) (*User, error) { +func ByUsername(username string) (*User, error) { var user *User query := `SELECT ` + userColumns() + ` FROM users WHERE LOWER(username) = LOWER(?) LIMIT 1` - err := db.Query(query, func(stmt *sqlite.Stmt) error { - user = scanUser(stmt, db) + err := database.Query(query, func(stmt *sqlite.Stmt) error { + user = scanUser(stmt) return nil }, username) @@ -197,13 +194,13 @@ func ByUsername(db *database.DB, username string) (*User, error) { } // ByEmail retrieves a user by email address -func ByEmail(db *database.DB, email string) (*User, error) { +func ByEmail(email string) (*User, error) { var user *User query := `SELECT ` + userColumns() + ` FROM users WHERE email = ? LIMIT 1` - err := db.Query(query, func(stmt *sqlite.Stmt) error { - user = scanUser(stmt, db) + err := database.Query(query, func(stmt *sqlite.Stmt) error { + user = scanUser(stmt) return nil }, email) @@ -219,13 +216,13 @@ func ByEmail(db *database.DB, email string) (*User, error) { } // ByLevel retrieves users at a specific level -func ByLevel(db *database.DB, level int) ([]*User, error) { +func ByLevel(level int) ([]*User, error) { var users []*User query := `SELECT ` + userColumns() + ` FROM users WHERE level = ? ORDER BY exp DESC, id ASC` - err := db.Query(query, func(stmt *sqlite.Stmt) error { - user := scanUser(stmt, db) + err := database.Query(query, func(stmt *sqlite.Stmt) error { + user := scanUser(stmt) users = append(users, user) return nil }, level) @@ -238,14 +235,14 @@ func ByLevel(db *database.DB, level int) ([]*User, error) { } // Online retrieves users who have been online within the specified duration -func Online(db *database.DB, within time.Duration) ([]*User, error) { +func Online(within time.Duration) ([]*User, error) { var users []*User cutoff := time.Now().Add(-within).Unix() query := `SELECT ` + userColumns() + ` FROM users WHERE last_online >= ? ORDER BY last_online DESC, id ASC` - err := db.Query(query, func(stmt *sqlite.Stmt) error { - user := scanUser(stmt, db) + err := database.Query(query, func(stmt *sqlite.Stmt) error { + user := scanUser(stmt) users = append(users, user) return nil }, cutoff) @@ -273,7 +270,7 @@ func (u *User) Save() error { slot_1_name = ?, slot_2_name = ?, slot_3_name = ?, drop_code = ?, spells = ?, towns = ? WHERE id = ?` - return u.db.Exec(query, u.Username, u.Password, u.Email, u.Verified, u.Token, + return database.Exec(query, u.Username, u.Password, u.Email, u.Verified, u.Token, u.Registered, u.LastOnline, u.Auth, u.X, u.Y, u.ClassID, u.Currently, u.Fighting, u.MonsterID, u.MonsterHP, u.MonsterSleep, u.MonsterImmune, u.UberDamage, u.UberDefense, u.HP, u.MP, u.TP, u.MaxHP, u.MaxMP, u.MaxTP, @@ -289,8 +286,7 @@ func (u *User) Delete() error { return fmt.Errorf("cannot delete user without ID") } - query := "DELETE FROM users WHERE id = ?" - return u.db.Exec(query, u.ID) + return database.Exec("DELETE FROM users WHERE id = ?", u.ID) } // RegisteredTime returns the registration timestamp as a time.Time @@ -424,13 +420,13 @@ func (u *User) SetPosition(x, y int) { } // GetByUsername retrieves a user by username -func GetByUsername(db *database.DB, username string) (*User, error) { +func GetByUsername(username string) (*User, error) { var user *User query := `SELECT ` + userColumns() + ` FROM users WHERE LOWER(username) = LOWER(?) LIMIT 1` - err := db.Query(query, func(stmt *sqlite.Stmt) error { - user = scanUser(stmt, db) + err := database.Query(query, func(stmt *sqlite.Stmt) error { + user = scanUser(stmt) return nil }, username) @@ -446,13 +442,13 @@ func GetByUsername(db *database.DB, username string) (*User, error) { } // GetByEmail retrieves a user by email -func GetByEmail(db *database.DB, email string) (*User, error) { +func GetByEmail(email string) (*User, error) { var user *User query := `SELECT ` + userColumns() + ` FROM users WHERE LOWER(email) = LOWER(?) LIMIT 1` - err := db.Query(query, func(stmt *sqlite.Stmt) error { - user = scanUser(stmt, db) + err := database.Query(query, func(stmt *sqlite.Stmt) error { + user = scanUser(stmt) return nil }, email) @@ -466,3 +462,72 @@ func GetByEmail(db *database.DB, email string) (*User, error) { return user, nil } + +// ToMap converts the user to a map for efficient template rendering +func (u *User) ToMap() map[string]any { + return map[string]any{ + "ID": u.ID, + "Username": u.Username, + "Email": u.Email, + "Verified": u.Verified, + "Token": u.Token, + "Registered": u.Registered, + "LastOnline": u.LastOnline, + "Auth": u.Auth, + "X": u.X, + "Y": u.Y, + "ClassID": u.ClassID, + "Currently": u.Currently, + "Fighting": u.Fighting, + "MonsterID": u.MonsterID, + "MonsterHP": u.MonsterHP, + "MonsterSleep": u.MonsterSleep, + "MonsterImmune": u.MonsterImmune, + "UberDamage": u.UberDamage, + "UberDefense": u.UberDefense, + "HP": u.HP, + "MP": u.MP, + "TP": u.TP, + "MaxHP": u.MaxHP, + "MaxMP": u.MaxMP, + "MaxTP": u.MaxTP, + "Level": u.Level, + "Gold": u.Gold, + "Exp": u.Exp, + "GoldBonus": u.GoldBonus, + "ExpBonus": u.ExpBonus, + "Strength": u.Strength, + "Dexterity": u.Dexterity, + "Attack": u.Attack, + "Defense": u.Defense, + "WeaponID": u.WeaponID, + "ArmorID": u.ArmorID, + "ShieldID": u.ShieldID, + "Slot1ID": u.Slot1ID, + "Slot2ID": u.Slot2ID, + "Slot3ID": u.Slot3ID, + "WeaponName": u.WeaponName, + "ArmorName": u.ArmorName, + "ShieldName": u.ShieldName, + "Slot1Name": u.Slot1Name, + "Slot2Name": u.Slot2Name, + "Slot3Name": u.Slot3Name, + "DropCode": u.DropCode, + "Spells": u.Spells, + "Towns": u.Towns, + + // Computed values + "IsVerified": u.IsVerified(), + "IsAdmin": u.IsAdmin(), + "IsModerator": u.IsModerator(), + "IsFighting": u.IsFighting(), + "IsAlive": u.IsAlive(), + "RegisteredTime": u.RegisteredTime(), + "LastOnlineTime": u.LastOnlineTime(), + "Equipment": u.GetEquipment(), + "Stats": u.GetStats(), + "Position": map[string]int{"X": u.X, "Y": u.Y}, + "SpellIDs": u.GetSpellIDs(), + "TownIDs": u.GetTownIDs(), + } +} diff --git a/internal/users/users_test.go b/internal/users/users_test.go deleted file mode 100644 index 00b52bc..0000000 --- a/internal/users/users_test.go +++ /dev/null @@ -1,670 +0,0 @@ -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"]) - } -} diff --git a/templates/leftside.html b/templates/leftside.html index 55fae29..a8e3018 100644 --- a/templates/leftside.html +++ b/templates/leftside.html @@ -4,9 +4,9 @@