Dragon-Knight/internal/babble/babble_test.go

625 lines
16 KiB
Go

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 := []interface{}{
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))
}
}