Dragon-Knight/internal/towns/towns_test.go
2025-08-08 23:33:16 -05:00

440 lines
11 KiB
Go

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