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