ToMap method for users
This commit is contained in:
parent
69e0ab5645
commit
80700149f8
@ -37,10 +37,10 @@ func (am *AuthManager) Authenticate(usernameOrEmail, plainPassword string) (*Use
|
||||
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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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'")
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"dk/internal/auth"
|
||||
"dk/internal/csrf"
|
||||
"dk/internal/database"
|
||||
"dk/internal/middleware"
|
||||
"dk/internal/router"
|
||||
"dk/internal/template"
|
||||
@ -47,20 +46,14 @@ 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)
|
||||
@ -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)
|
||||
|
@ -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]
|
||||
// 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})
|
||||
}
|
||||
|
@ -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)")
|
||||
}
|
||||
}
|
@ -12,15 +12,13 @@ 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
|
||||
@ -47,7 +45,6 @@ func NewBuilder(db *database.DB) *Builder {
|
||||
Spells: "", // No spells initially
|
||||
Towns: "", // No towns visited initially
|
||||
},
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,7 +189,7 @@ 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,
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
@ -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"])
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Currently: {user.currentaction}<br>
|
||||
Longitude: {user.longitude}<br>
|
||||
Latitude: {user.latitude}<br>
|
||||
Currently: {user.Currently}<br>
|
||||
Longitude: {user.X}<br>
|
||||
Latitude: {user.Y}<br>
|
||||
<a href="javascript:openmappopup()">View Map</a>
|
||||
<br><br>
|
||||
<form action="/move" method="post">
|
||||
|
@ -4,13 +4,13 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>{user.username}</b><br>
|
||||
Level: {user.level}<br>
|
||||
Exp: {user.experience}<br>
|
||||
Gold: {user.gold}<br>
|
||||
HP: {user.currenthp}<br>
|
||||
MP: {user.currentmp}<br>
|
||||
TP: {user.currenttp}<br>
|
||||
<b>{user.Username}</b><br>
|
||||
Level: {user.Level}<br>
|
||||
Exp: {user.Exp}<br>
|
||||
Gold: {user.Gold}<br>
|
||||
HP: {user.HP}<br>
|
||||
MP: {user.MP}<br>
|
||||
TP: {user.TP}<br>
|
||||
{statbars}<br>
|
||||
<a href="javascript:opencharpopup()">Extended Stats</a>
|
||||
</td>
|
||||
|
Loading…
x
Reference in New Issue
Block a user