Dragon-Knight/internal/news/news_test.go
2025-08-08 23:37:08 -05:00

459 lines
11 KiB
Go

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