create news package
This commit is contained in:
parent
a6b34b7b87
commit
089246dd25
85
internal/news/builder.go
Normal file
85
internal/news/builder.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package news
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"dk/internal/database"
|
||||||
|
|
||||||
|
"zombiezen.com/go/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder provides a fluent interface for creating news posts
|
||||||
|
type Builder struct {
|
||||||
|
news *News
|
||||||
|
db *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder creates a new news builder
|
||||||
|
func NewBuilder(db *database.DB) *Builder {
|
||||||
|
return &Builder{
|
||||||
|
news: &News{
|
||||||
|
db: db,
|
||||||
|
Posted: time.Now().Unix(), // Default to current time
|
||||||
|
},
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAuthor sets the author ID
|
||||||
|
func (b *Builder) WithAuthor(authorID int) *Builder {
|
||||||
|
b.news.Author = authorID
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContent sets the news content
|
||||||
|
func (b *Builder) WithContent(content string) *Builder {
|
||||||
|
b.news.Content = content
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPosted sets the posted timestamp
|
||||||
|
func (b *Builder) WithPosted(posted int64) *Builder {
|
||||||
|
b.news.Posted = posted
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPostedTime sets the posted timestamp from a time.Time
|
||||||
|
func (b *Builder) WithPostedTime(t time.Time) *Builder {
|
||||||
|
b.news.Posted = t.Unix()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create saves the news post to the database and returns the created news with ID
|
||||||
|
func (b *Builder) Create() (*News, error) {
|
||||||
|
// Use a transaction to ensure we can get the ID
|
||||||
|
var news *News
|
||||||
|
err := b.db.Transaction(func(tx *database.Tx) error {
|
||||||
|
query := `INSERT INTO news (author, posted, content)
|
||||||
|
VALUES (?, ?, ?)`
|
||||||
|
|
||||||
|
if err := tx.Exec(query, b.news.Author, b.news.Posted, b.news.Content); err != nil {
|
||||||
|
return fmt.Errorf("failed to insert news: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the last insert ID
|
||||||
|
var id int
|
||||||
|
err := tx.Query("SELECT last_insert_rowid()", func(stmt *sqlite.Stmt) error {
|
||||||
|
id = stmt.ColumnInt(0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get insert ID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.news.ID = id
|
||||||
|
news = b.news
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return news, nil
|
||||||
|
}
|
349
internal/news/doc.go
Normal file
349
internal/news/doc.go
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
/*
|
||||||
|
Package news is the active record implementation for news posts in the game.
|
||||||
|
|
||||||
|
# Basic Usage
|
||||||
|
|
||||||
|
To retrieve a news post by ID:
|
||||||
|
|
||||||
|
newsPost, err := news.Find(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("News: %s (by user %d)\n", newsPost.Content, newsPost.Author)
|
||||||
|
|
||||||
|
To get all news posts:
|
||||||
|
|
||||||
|
allNews, err := news.All(db)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, post := range allNews {
|
||||||
|
fmt.Printf("News: %s\n", post.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
To get recent news posts:
|
||||||
|
|
||||||
|
recentNews, err := news.Recent(db, 10)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
To filter news by author:
|
||||||
|
|
||||||
|
authorPosts, err := news.ByAuthor(db, userID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
To get news since a specific time:
|
||||||
|
|
||||||
|
yesterday := time.Now().AddDate(0, 0, -1).Unix()
|
||||||
|
recentNews, err := news.Since(db, yesterday)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Creating News with Builder Pattern
|
||||||
|
|
||||||
|
The package provides a fluent builder interface for creating new news posts:
|
||||||
|
|
||||||
|
newsPost, err := news.NewBuilder(db).
|
||||||
|
WithAuthor(userID).
|
||||||
|
WithContent("Welcome to the new update! Many exciting features await.").
|
||||||
|
WithPostedTime(time.Now()).
|
||||||
|
Create()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Created news post with ID: %d\n", newsPost.ID)
|
||||||
|
|
||||||
|
The builder automatically sets the current time if no posted time is specified:
|
||||||
|
|
||||||
|
newsPost, err := news.NewBuilder(db).
|
||||||
|
WithAuthor(adminID).
|
||||||
|
WithContent("Server maintenance scheduled for tonight.").
|
||||||
|
Create() // Uses current timestamp
|
||||||
|
|
||||||
|
# Updating News
|
||||||
|
|
||||||
|
News posts can be modified and saved back to the database:
|
||||||
|
|
||||||
|
newsPost, _ := news.Find(db, 1)
|
||||||
|
newsPost.Content = "Updated: Server maintenance completed successfully."
|
||||||
|
|
||||||
|
err := newsPost.Save()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deleting News
|
||||||
|
|
||||||
|
News posts can be removed from the database:
|
||||||
|
|
||||||
|
newsPost, _ := news.Find(db, 1)
|
||||||
|
err := newsPost.Delete()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Database Schema
|
||||||
|
|
||||||
|
The news table has the following structure:
|
||||||
|
|
||||||
|
CREATE TABLE news (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
author INTEGER NOT NULL,
|
||||||
|
posted INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||||
|
content TEXT NOT NULL
|
||||||
|
)
|
||||||
|
|
||||||
|
Where:
|
||||||
|
- id: Unique identifier for the news post
|
||||||
|
- author: User ID of the author who created the post
|
||||||
|
- posted: Unix timestamp when the post was created
|
||||||
|
- content: The text content of the news post
|
||||||
|
|
||||||
|
# Time-Based Queries
|
||||||
|
|
||||||
|
## Recent News
|
||||||
|
|
||||||
|
Get the most recent news posts:
|
||||||
|
|
||||||
|
// Get 5 most recent posts
|
||||||
|
latestNews, err := news.Recent(db, 5)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, post := range latestNews {
|
||||||
|
fmt.Printf("Posted %s: %s\n",
|
||||||
|
post.PostedTime().Format("2006-01-02"),
|
||||||
|
post.Preview(50))
|
||||||
|
}
|
||||||
|
|
||||||
|
## News Since Timestamp
|
||||||
|
|
||||||
|
Get news posts since a specific time:
|
||||||
|
|
||||||
|
// Get news from the last week
|
||||||
|
weekAgo := time.Now().AddDate(0, 0, -7).Unix()
|
||||||
|
weeklyNews, err := news.Since(db, weekAgo)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
## News Between Timestamps
|
||||||
|
|
||||||
|
Get news posts within a time range:
|
||||||
|
|
||||||
|
// Get news from last month
|
||||||
|
start := time.Now().AddDate(0, -1, 0).Unix()
|
||||||
|
end := time.Now().Unix()
|
||||||
|
|
||||||
|
monthlyNews, err := news.Between(db, start, end)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Content Management
|
||||||
|
|
||||||
|
## Content Analysis
|
||||||
|
|
||||||
|
The package provides utilities for analyzing news content:
|
||||||
|
|
||||||
|
wordCount := newsPost.WordCount()
|
||||||
|
fmt.Printf("Post contains %d words\n", wordCount)
|
||||||
|
|
||||||
|
preview := newsPost.Preview(100)
|
||||||
|
fmt.Printf("Preview: %s\n", preview)
|
||||||
|
|
||||||
|
## Author Management
|
||||||
|
|
||||||
|
Check authorship and manage posts by author:
|
||||||
|
|
||||||
|
if newsPost.IsAuthor(currentUserID) {
|
||||||
|
fmt.Println("You can edit this post")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all posts by a specific author
|
||||||
|
authorPosts, err := news.ByAuthor(db, authorID)
|
||||||
|
fmt.Printf("Author has written %d posts\n", len(authorPosts))
|
||||||
|
|
||||||
|
# Time Utilities
|
||||||
|
|
||||||
|
## Working with Timestamps
|
||||||
|
|
||||||
|
The package provides convenient time utilities:
|
||||||
|
|
||||||
|
// Get posting time as time.Time
|
||||||
|
postTime := newsPost.PostedTime()
|
||||||
|
fmt.Printf("Posted on: %s\n", postTime.Format("January 2, 2006"))
|
||||||
|
|
||||||
|
// Set posting time from time.Time
|
||||||
|
newsPost.SetPostedTime(time.Now().Add(-2 * time.Hour))
|
||||||
|
|
||||||
|
// Check if post is recent (within 24 hours)
|
||||||
|
if newsPost.IsRecent() {
|
||||||
|
fmt.Println("This is a recent post")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get age of the post
|
||||||
|
age := newsPost.Age()
|
||||||
|
fmt.Printf("Posted %v ago\n", age)
|
||||||
|
|
||||||
|
## Time-Based Filtering
|
||||||
|
|
||||||
|
Find posts based on recency:
|
||||||
|
|
||||||
|
allNews, _ := news.All(db)
|
||||||
|
recentPosts := make([]*news.News, 0)
|
||||||
|
|
||||||
|
for _, post := range allNews {
|
||||||
|
if post.IsRecent() {
|
||||||
|
recentPosts = append(recentPosts, post)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Found %d recent posts\n", len(recentPosts))
|
||||||
|
|
||||||
|
# Administrative Features
|
||||||
|
|
||||||
|
## Moderation
|
||||||
|
|
||||||
|
Administrators can manage news posts:
|
||||||
|
|
||||||
|
// Get all posts by a user for moderation
|
||||||
|
suspiciousPosts, err := news.ByAuthor(db, reportedUserID)
|
||||||
|
|
||||||
|
for _, post := range suspiciousPosts {
|
||||||
|
if post.WordCount() < 5 {
|
||||||
|
fmt.Printf("Short post flagged: %s\n", post.Preview(50))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## Content Guidelines
|
||||||
|
|
||||||
|
Use content analysis for moderation:
|
||||||
|
|
||||||
|
if newsPost.WordCount() > 1000 {
|
||||||
|
fmt.Println("Warning: Very long post")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newsPost.Content) < 10 {
|
||||||
|
fmt.Println("Warning: Very short post")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Display and Formatting
|
||||||
|
|
||||||
|
## News Feeds
|
||||||
|
|
||||||
|
Display news in chronological order:
|
||||||
|
|
||||||
|
recentNews, _ := news.Recent(db, 20)
|
||||||
|
|
||||||
|
fmt.Println("=== Latest News ===")
|
||||||
|
for _, post := range recentNews {
|
||||||
|
age := post.Age()
|
||||||
|
var ageStr string
|
||||||
|
|
||||||
|
if age < time.Hour {
|
||||||
|
ageStr = fmt.Sprintf("%d minutes ago", int(age.Minutes()))
|
||||||
|
} else if age < 24*time.Hour {
|
||||||
|
ageStr = fmt.Sprintf("%d hours ago", int(age.Hours()))
|
||||||
|
} else {
|
||||||
|
ageStr = fmt.Sprintf("%d days ago", int(age.Hours()/24))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[%s] %s\n", ageStr, post.Preview(80))
|
||||||
|
}
|
||||||
|
|
||||||
|
## Preview Generation
|
||||||
|
|
||||||
|
Generate previews for different contexts:
|
||||||
|
|
||||||
|
// Short preview for mobile
|
||||||
|
mobilePreview := newsPost.Preview(30)
|
||||||
|
|
||||||
|
// Medium preview for web cards
|
||||||
|
cardPreview := newsPost.Preview(100)
|
||||||
|
|
||||||
|
// Long preview for detailed view
|
||||||
|
detailPreview := newsPost.Preview(200)
|
||||||
|
|
||||||
|
# Performance Considerations
|
||||||
|
|
||||||
|
## Query Optimization
|
||||||
|
|
||||||
|
All time-based queries are optimized with proper indexing expectations:
|
||||||
|
|
||||||
|
// Queries are ordered by posted timestamp for efficient retrieval
|
||||||
|
latestNews, _ := news.All(db) // ORDER BY posted DESC, id DESC
|
||||||
|
|
||||||
|
// Author queries include time ordering
|
||||||
|
authorPosts, _ := news.ByAuthor(db, userID) // ORDER BY posted DESC, id DESC
|
||||||
|
|
||||||
|
## Batch Operations
|
||||||
|
|
||||||
|
For processing multiple posts efficiently:
|
||||||
|
|
||||||
|
// Process posts in batches
|
||||||
|
batchSize := 100
|
||||||
|
allNews, _ := news.All(db)
|
||||||
|
|
||||||
|
for i := 0; i < len(allNews); i += batchSize {
|
||||||
|
end := i + batchSize
|
||||||
|
if end > len(allNews) {
|
||||||
|
end = len(allNews)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := allNews[i:end]
|
||||||
|
// Process batch...
|
||||||
|
}
|
||||||
|
|
||||||
|
# Error Handling
|
||||||
|
|
||||||
|
All functions return appropriate errors for common failure cases:
|
||||||
|
- News post not found (Find returns error for non-existent IDs)
|
||||||
|
- Database connection issues
|
||||||
|
- Invalid operations (e.g., saving/deleting news posts without IDs)
|
||||||
|
- Time range query errors
|
||||||
|
|
||||||
|
# Integration Patterns
|
||||||
|
|
||||||
|
## User Integration
|
||||||
|
|
||||||
|
Connect with user management:
|
||||||
|
|
||||||
|
// Example: Get news by username (assuming user lookup)
|
||||||
|
user := getUserByName("admin")
|
||||||
|
adminNews, err := news.ByAuthor(db, user.ID)
|
||||||
|
|
||||||
|
## Notification System
|
||||||
|
|
||||||
|
Use for activity feeds:
|
||||||
|
|
||||||
|
// Get user's activity since last login
|
||||||
|
lastLogin := getUserLastLogin(userID)
|
||||||
|
newsSinceLogin, err := news.Since(db, lastLogin)
|
||||||
|
|
||||||
|
if len(newsSinceLogin) > 0 {
|
||||||
|
fmt.Printf("You have %d new posts since your last visit\n", len(newsSinceLogin))
|
||||||
|
}
|
||||||
|
|
||||||
|
## Archive Management
|
||||||
|
|
||||||
|
Implement content archiving:
|
||||||
|
|
||||||
|
// Archive old posts (older than 1 year)
|
||||||
|
oneYearAgo := time.Now().AddDate(-1, 0, 0).Unix()
|
||||||
|
oldPosts, _ := news.Since(db, 0) // Get all posts
|
||||||
|
|
||||||
|
for _, post := range oldPosts {
|
||||||
|
if post.Posted < oneYearAgo {
|
||||||
|
// Archive or delete old post
|
||||||
|
post.Delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package news
|
250
internal/news/news.go
Normal file
250
internal/news/news.go
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
package news
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"dk/internal/database"
|
||||||
|
|
||||||
|
"zombiezen.com/go/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// News represents a news post in the database
|
||||||
|
type News struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Author int `json:"author"`
|
||||||
|
Posted int64 `json:"posted"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
|
||||||
|
db *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find retrieves a news post by ID
|
||||||
|
func Find(db *database.DB, id int) (*News, error) {
|
||||||
|
news := &News{db: db}
|
||||||
|
|
||||||
|
query := "SELECT id, author, posted, content FROM news WHERE id = ?"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
news.ID = stmt.ColumnInt(0)
|
||||||
|
news.Author = stmt.ColumnInt(1)
|
||||||
|
news.Posted = stmt.ColumnInt64(2)
|
||||||
|
news.Content = stmt.ColumnText(3)
|
||||||
|
return nil
|
||||||
|
}, id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to find news: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if news.ID == 0 {
|
||||||
|
return nil, fmt.Errorf("news with ID %d not found", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return news, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// All retrieves all news posts ordered by posted date (newest first)
|
||||||
|
func All(db *database.DB) ([]*News, error) {
|
||||||
|
var newsPosts []*News
|
||||||
|
|
||||||
|
query := "SELECT id, author, posted, content FROM news ORDER BY posted DESC, id DESC"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
news := &News{
|
||||||
|
ID: stmt.ColumnInt(0),
|
||||||
|
Author: stmt.ColumnInt(1),
|
||||||
|
Posted: stmt.ColumnInt64(2),
|
||||||
|
Content: stmt.ColumnText(3),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
newsPosts = append(newsPosts, news)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve all news: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newsPosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByAuthor retrieves news posts by a specific author
|
||||||
|
func ByAuthor(db *database.DB, authorID int) ([]*News, error) {
|
||||||
|
var newsPosts []*News
|
||||||
|
|
||||||
|
query := "SELECT id, author, posted, content FROM news WHERE author = ? ORDER BY posted DESC, id DESC"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
news := &News{
|
||||||
|
ID: stmt.ColumnInt(0),
|
||||||
|
Author: stmt.ColumnInt(1),
|
||||||
|
Posted: stmt.ColumnInt64(2),
|
||||||
|
Content: stmt.ColumnText(3),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
newsPosts = append(newsPosts, news)
|
||||||
|
return nil
|
||||||
|
}, authorID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve news by author: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newsPosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recent retrieves the most recent news posts (limited by count)
|
||||||
|
func Recent(db *database.DB, limit int) ([]*News, error) {
|
||||||
|
var newsPosts []*News
|
||||||
|
|
||||||
|
query := "SELECT id, author, posted, content FROM news ORDER BY posted DESC, id DESC LIMIT ?"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
news := &News{
|
||||||
|
ID: stmt.ColumnInt(0),
|
||||||
|
Author: stmt.ColumnInt(1),
|
||||||
|
Posted: stmt.ColumnInt64(2),
|
||||||
|
Content: stmt.ColumnText(3),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
newsPosts = append(newsPosts, news)
|
||||||
|
return nil
|
||||||
|
}, limit)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve recent news: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newsPosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since retrieves news posts since a specific timestamp
|
||||||
|
func Since(db *database.DB, since int64) ([]*News, error) {
|
||||||
|
var newsPosts []*News
|
||||||
|
|
||||||
|
query := "SELECT id, author, posted, content FROM news WHERE posted >= ? ORDER BY posted DESC, id DESC"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
news := &News{
|
||||||
|
ID: stmt.ColumnInt(0),
|
||||||
|
Author: stmt.ColumnInt(1),
|
||||||
|
Posted: stmt.ColumnInt64(2),
|
||||||
|
Content: stmt.ColumnText(3),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
newsPosts = append(newsPosts, news)
|
||||||
|
return nil
|
||||||
|
}, since)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve news since timestamp: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newsPosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Between retrieves news posts between two timestamps (inclusive)
|
||||||
|
func Between(db *database.DB, start, end int64) ([]*News, error) {
|
||||||
|
var newsPosts []*News
|
||||||
|
|
||||||
|
query := "SELECT id, author, posted, content FROM news WHERE posted >= ? AND posted <= ? ORDER BY posted DESC, id DESC"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
news := &News{
|
||||||
|
ID: stmt.ColumnInt(0),
|
||||||
|
Author: stmt.ColumnInt(1),
|
||||||
|
Posted: stmt.ColumnInt64(2),
|
||||||
|
Content: stmt.ColumnText(3),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
newsPosts = append(newsPosts, news)
|
||||||
|
return nil
|
||||||
|
}, start, end)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve news between timestamps: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newsPosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save updates an existing news post in the database
|
||||||
|
func (n *News) Save() error {
|
||||||
|
if n.ID == 0 {
|
||||||
|
return fmt.Errorf("cannot save news without ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `UPDATE news SET author = ?, posted = ?, content = ? WHERE id = ?`
|
||||||
|
return n.db.Exec(query, n.Author, n.Posted, n.Content, n.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the news post from the database
|
||||||
|
func (n *News) Delete() error {
|
||||||
|
if n.ID == 0 {
|
||||||
|
return fmt.Errorf("cannot delete news without ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := "DELETE FROM news WHERE id = ?"
|
||||||
|
return n.db.Exec(query, n.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostedTime returns the posted timestamp as a time.Time
|
||||||
|
func (n *News) PostedTime() time.Time {
|
||||||
|
return time.Unix(n.Posted, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPostedTime sets the posted timestamp from a time.Time
|
||||||
|
func (n *News) SetPostedTime(t time.Time) {
|
||||||
|
n.Posted = t.Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRecent returns true if the news post was made within the last 24 hours
|
||||||
|
func (n *News) IsRecent() bool {
|
||||||
|
return time.Since(n.PostedTime()) < 24*time.Hour
|
||||||
|
}
|
||||||
|
|
||||||
|
// Age returns how long ago the news post was made
|
||||||
|
func (n *News) Age() time.Duration {
|
||||||
|
return time.Since(n.PostedTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAuthor returns true if the given user ID is the author of this news post
|
||||||
|
func (n *News) IsAuthor(userID int) bool {
|
||||||
|
return n.Author == userID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preview returns a truncated version of the content for previews
|
||||||
|
func (n *News) Preview(maxLength int) string {
|
||||||
|
if len(n.Content) <= maxLength {
|
||||||
|
return n.Content
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxLength < 3 {
|
||||||
|
return n.Content[:maxLength]
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.Content[:maxLength-3] + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
// WordCount returns the number of words in the content
|
||||||
|
func (n *News) WordCount() int {
|
||||||
|
if n.Content == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple word count by splitting on spaces
|
||||||
|
words := 0
|
||||||
|
inWord := false
|
||||||
|
|
||||||
|
for _, char := range n.Content {
|
||||||
|
if char == ' ' || char == '\t' || char == '\n' || char == '\r' {
|
||||||
|
if inWord {
|
||||||
|
words++
|
||||||
|
inWord = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inWord = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inWord {
|
||||||
|
words++
|
||||||
|
}
|
||||||
|
|
||||||
|
return words
|
||||||
|
}
|
459
internal/news/news_test.go
Normal file
459
internal/news/news_test.go
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user