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