core/internal/tw/client_test.go

1718 lines
57 KiB
Go

package tw_test
import (
"context"
"encoding/csv"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"os"
"slices"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/require"
"gitea.dwysokinski.me/twhelp/corev3/internal/tw"
"github.com/stretchr/testify/assert"
)
const testUserAgent = "tribalwarshelp.com/test"
func TestClient_GetOpenServers(t *testing.T) {
t.Parallel()
resp, err := os.ReadFile("./testdata/get_servers_resp.txt")
require.NoError(t, err)
mux := http.NewServeMux()
mux.HandleFunc("/backend/get_servers.php", newPlainTextHandler(resp))
srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close)
expectedServerKeys := []string{
"plp8",
"pl170",
"pl171",
"pl172",
"pl173",
"plc1",
"plp7",
"pl168",
"pl174",
"pl159",
"pl167",
"pl165",
"pl169",
"pls1",
"pl161",
"pl164",
}
servers, err := newClient(srv.Client()).GetOpenServers(context.Background(), parseURL(t, srv.URL))
require.NoError(t, err)
assert.Len(t, servers, len(expectedServerKeys))
for _, key := range expectedServerKeys {
assert.True(t, slices.ContainsFunc(servers, func(server tw.Server) bool {
return server.Key == key && *server.URL == url.URL{
Scheme: "https",
Host: key + ".plemiona.pl",
}
}), "key '%s' not found", key)
}
}
func TestClient_GetServerConfig(t *testing.T) {
t.Parallel()
resp, err := os.ReadFile("./testdata/server_config.xml")
require.NoError(t, err)
mux := http.NewServeMux()
mux.HandleFunc("/interface.php", newInterfaceXMLHandler("get_config", resp))
srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close)
cfg, err := newClient(srv.Client()).GetServerConfig(context.Background(), parseURL(t, srv.URL))
require.NoError(t, err)
assert.InDelta(t, 4.5, cfg.Speed, 0.01)
assert.InDelta(t, 0.5, cfg.UnitSpeed, 0.01)
assert.Equal(t, 1, cfg.Moral)
assert.Equal(t, 1, cfg.Build.Destroy)
assert.Equal(t, 2, cfg.Misc.KillRanking)
assert.Equal(t, 0, cfg.Misc.Tutorial)
assert.Equal(t, 300, cfg.Misc.TradeCancelTime)
assert.Equal(t, 0, cfg.Commands.MillisArrival)
assert.Equal(t, 600, cfg.Commands.CommandCancelTime)
assert.Equal(t, 3, cfg.Newbie.Days)
assert.Equal(t, 15, cfg.Newbie.RatioDays)
assert.Equal(t, 10, cfg.Newbie.Ratio)
assert.Equal(t, 1, cfg.Newbie.RemoveNewbieVillages)
assert.Equal(t, 2, cfg.Game.BuildtimeFormula)
assert.Equal(t, 0, cfg.Game.Knight)
assert.Equal(t, 1, cfg.Game.KnightNewItems.Int())
assert.Equal(t, 1, cfg.Game.Archer)
assert.Equal(t, 2, cfg.Game.Tech)
assert.Equal(t, 0, cfg.Game.FarmLimit)
assert.Equal(t, 0, cfg.Game.Church)
assert.Equal(t, 0, cfg.Game.Watchtower)
assert.Equal(t, 0, cfg.Game.Stronghold)
assert.InDelta(t, 0.5, cfg.Game.FakeLimit, 0.01)
assert.InDelta(t, 0.001, cfg.Game.BarbarianRise, 0)
assert.Equal(t, 1, cfg.Game.BarbarianShrink)
assert.Equal(t, 800, cfg.Game.BarbarianMaxPoints)
assert.Equal(t, 1, cfg.Game.Scavenging)
assert.Equal(t, 2, cfg.Game.Hauls)
assert.Equal(t, 5000, cfg.Game.HaulsBase)
assert.Equal(t, 1000000, cfg.Game.HaulsMax)
assert.Equal(t, 0, cfg.Game.Event)
assert.Equal(t, 1, cfg.Game.SuppressEvents)
assert.Equal(t, -1, cfg.Buildings.CustomMain)
assert.Equal(t, -1, cfg.Buildings.CustomFarm)
assert.Equal(t, -1, cfg.Buildings.CustomStorage)
assert.Equal(t, -1, cfg.Buildings.CustomPlace)
assert.Equal(t, -1, cfg.Buildings.CustomBarracks)
assert.Equal(t, -1, cfg.Buildings.CustomChurch)
assert.Equal(t, -1, cfg.Buildings.CustomSmith)
assert.Equal(t, -1, cfg.Buildings.CustomWood)
assert.Equal(t, -1, cfg.Buildings.CustomStone)
assert.Equal(t, -1, cfg.Buildings.CustomIron)
assert.Equal(t, -1, cfg.Buildings.CustomMarket)
assert.Equal(t, -1, cfg.Buildings.CustomStable)
assert.Equal(t, -1, cfg.Buildings.CustomWall)
assert.Equal(t, -1, cfg.Buildings.CustomGarage)
assert.Equal(t, -1, cfg.Buildings.CustomHide)
assert.Equal(t, -1, cfg.Buildings.CustomSnob)
assert.Equal(t, -1, cfg.Buildings.CustomStatue)
assert.Equal(t, -1, cfg.Buildings.CustomWatchtower)
assert.Equal(t, 1, cfg.Snob.Gold)
assert.Equal(t, 0, cfg.Snob.CheapRebuild)
assert.Equal(t, 2, cfg.Snob.Rise)
assert.Equal(t, 50, cfg.Snob.MaxDist)
assert.InDelta(t, 1.0, cfg.Snob.Factor, 0.01)
assert.Equal(t, 28000, cfg.Snob.CoinWood)
assert.Equal(t, 30000, cfg.Snob.CoinStone)
assert.Equal(t, 25000, cfg.Snob.CoinIron)
assert.Equal(t, 0, cfg.Snob.NoBarbConquer)
assert.Equal(t, 0, cfg.Ally.NoHarm)
assert.Equal(t, 0, cfg.Ally.NoOtherSupport)
assert.Equal(t, 0, cfg.Ally.NoOtherSupportType)
assert.Equal(t, 0, cfg.Ally.AllytimeSupport)
assert.Equal(t, 0, cfg.Ally.NoLeave)
assert.Equal(t, 0, cfg.Ally.NoJoin)
assert.Equal(t, 15, cfg.Ally.Limit)
assert.Equal(t, 0, cfg.Ally.FixedAllies)
assert.Equal(t, 5, cfg.Ally.WarsMemberRequirement)
assert.Equal(t, 15000, cfg.Ally.WarsPointsRequirement)
assert.Equal(t, 7, cfg.Ally.WarsAutoacceptDays)
assert.Equal(t, 1, cfg.Ally.Levels)
assert.Equal(t, "v1", cfg.Ally.XpRequirements)
assert.Equal(t, 1000, cfg.Coord.MapSize)
assert.Equal(t, 4, cfg.Coord.Func)
assert.Equal(t, 11, cfg.Coord.EmptyVillages)
assert.Equal(t, 1, cfg.Coord.BonusVillages)
assert.Equal(t, 550, cfg.Coord.Inner)
assert.Equal(t, 0, cfg.Coord.SelectStart)
assert.Equal(t, 336, cfg.Coord.VillageMoveWait)
assert.Equal(t, 1, cfg.Coord.NobleRestart)
assert.Equal(t, 1, cfg.Coord.StartVillages)
assert.Equal(t, 1, cfg.Sitter.Allow)
assert.Equal(t, 0, cfg.Sleep.Active)
assert.Equal(t, 60, cfg.Sleep.Delay)
assert.Equal(t, 6, cfg.Sleep.Min)
assert.Equal(t, 10, cfg.Sleep.Max)
assert.Equal(t, 12, cfg.Sleep.MinAwake)
assert.Equal(t, 36, cfg.Sleep.MaxAwake)
assert.Equal(t, 10, cfg.Sleep.WarnTime)
assert.Equal(t, 2, cfg.Night.Active)
assert.Equal(t, 23, cfg.Night.StartHour)
assert.Equal(t, 7, cfg.Night.EndHour)
assert.InDelta(t, 3.5, cfg.Night.DefFactor, 0.01)
assert.Equal(t, 14, cfg.Night.Duration)
assert.Equal(t, 3, cfg.Win.Check)
}
func TestClient_GetUnitInfo(t *testing.T) {
t.Parallel()
resp, err := os.ReadFile("./testdata/unit_info.xml")
require.NoError(t, err)
mux := http.NewServeMux()
mux.HandleFunc("/interface.php", newInterfaceXMLHandler("get_unit_info", resp))
srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close)
unitInfo, err := newClient(srv.Client()).GetUnitInfo(context.Background(), parseURL(t, srv.URL))
require.NoError(t, err)
assert.InDelta(t, 226.66666666667, unitInfo.Spear.BuildTime, 0.01)
assert.Equal(t, 1, unitInfo.Spear.Pop)
assert.InDelta(t, 8.0, unitInfo.Spear.Speed, 0.01)
assert.Equal(t, 10, unitInfo.Spear.Attack)
assert.Equal(t, 15, unitInfo.Spear.Defense)
assert.Equal(t, 45, unitInfo.Spear.DefenseCavalry)
assert.Equal(t, 20, unitInfo.Spear.DefenseArcher)
assert.Equal(t, 25, unitInfo.Spear.Carry)
assert.InDelta(t, 333.33333333333, unitInfo.Sword.BuildTime, 0.01)
assert.Equal(t, 1, unitInfo.Sword.Pop)
assert.InDelta(t, 9.7777777777778, unitInfo.Sword.Speed, 0.01)
assert.Equal(t, 25, unitInfo.Sword.Attack)
assert.Equal(t, 50, unitInfo.Sword.Defense)
assert.Equal(t, 15, unitInfo.Sword.DefenseCavalry)
assert.Equal(t, 40, unitInfo.Sword.DefenseArcher)
assert.Equal(t, 15, unitInfo.Sword.Carry)
assert.InDelta(t, 293.33333333333, unitInfo.Axe.BuildTime, 0.01)
assert.Equal(t, 1, unitInfo.Axe.Pop)
assert.InDelta(t, 8.0, unitInfo.Axe.Speed, 0.01)
assert.Equal(t, 40, unitInfo.Axe.Attack)
assert.Equal(t, 10, unitInfo.Axe.Defense)
assert.Equal(t, 5, unitInfo.Axe.DefenseCavalry)
assert.Equal(t, 10, unitInfo.Axe.DefenseArcher)
assert.Equal(t, 10, unitInfo.Axe.Carry)
assert.InDelta(t, 200.0, unitInfo.Spy.BuildTime, 0.01)
assert.Equal(t, 2, unitInfo.Spy.Pop)
assert.InDelta(t, 4.0, unitInfo.Spy.Speed, 0.01)
assert.Equal(t, 0, unitInfo.Spy.Attack)
assert.Equal(t, 2, unitInfo.Spy.Defense)
assert.Equal(t, 1, unitInfo.Spy.DefenseCavalry)
assert.Equal(t, 2, unitInfo.Spy.DefenseArcher)
assert.Equal(t, 0, unitInfo.Spy.Carry)
assert.InDelta(t, 400.0, unitInfo.Light.BuildTime, 0.01)
assert.Equal(t, 4, unitInfo.Light.Pop)
assert.InDelta(t, 4.4444444444444, unitInfo.Light.Speed, 0.01)
assert.Equal(t, 130, unitInfo.Light.Attack)
assert.Equal(t, 30, unitInfo.Light.Defense)
assert.Equal(t, 40, unitInfo.Light.DefenseCavalry)
assert.Equal(t, 30, unitInfo.Light.DefenseArcher)
assert.Equal(t, 80, unitInfo.Light.Carry)
assert.InDelta(t, 600.0, unitInfo.Marcher.BuildTime, 0.01)
assert.Equal(t, 5, unitInfo.Marcher.Pop)
assert.InDelta(t, 4.4444444444444, unitInfo.Marcher.Speed, 0.01)
assert.Equal(t, 120, unitInfo.Marcher.Attack)
assert.Equal(t, 40, unitInfo.Marcher.Defense)
assert.Equal(t, 30, unitInfo.Marcher.DefenseCavalry)
assert.Equal(t, 50, unitInfo.Marcher.DefenseArcher)
assert.Equal(t, 50, unitInfo.Marcher.Carry)
assert.InDelta(t, 800.0, unitInfo.Heavy.BuildTime, 0.01)
assert.Equal(t, 6, unitInfo.Heavy.Pop)
assert.InDelta(t, 4.8888888888889, unitInfo.Heavy.Speed, 0.01)
assert.Equal(t, 150, unitInfo.Heavy.Attack)
assert.Equal(t, 200, unitInfo.Heavy.Defense)
assert.Equal(t, 80, unitInfo.Heavy.DefenseCavalry)
assert.Equal(t, 180, unitInfo.Heavy.DefenseArcher)
assert.Equal(t, 50, unitInfo.Heavy.Carry)
assert.InDelta(t, 1066.6666666667, unitInfo.Ram.BuildTime, 0.01)
assert.Equal(t, 5, unitInfo.Ram.Pop)
assert.InDelta(t, 13.333333333333, unitInfo.Ram.Speed, 0.01)
assert.Equal(t, 2, unitInfo.Ram.Attack)
assert.Equal(t, 20, unitInfo.Ram.Defense)
assert.Equal(t, 50, unitInfo.Ram.DefenseCavalry)
assert.Equal(t, 20, unitInfo.Ram.DefenseArcher)
assert.Equal(t, 0, unitInfo.Ram.Carry)
assert.InDelta(t, 1600.0, unitInfo.Catapult.BuildTime, 0.01)
assert.Equal(t, 8, unitInfo.Catapult.Pop)
assert.InDelta(t, 13.333333333333, unitInfo.Catapult.Speed, 0.01)
assert.Equal(t, 100, unitInfo.Catapult.Attack)
assert.Equal(t, 100, unitInfo.Catapult.Defense)
assert.Equal(t, 50, unitInfo.Catapult.DefenseCavalry)
assert.Equal(t, 100, unitInfo.Catapult.DefenseArcher)
assert.Equal(t, 0, unitInfo.Catapult.Carry)
assert.InDelta(t, 4000.0, unitInfo.Snob.BuildTime, 0.01)
assert.Equal(t, 100, unitInfo.Snob.Pop)
assert.InDelta(t, 15.555555555556, unitInfo.Snob.Speed, 0.01)
assert.Equal(t, 30, unitInfo.Snob.Attack)
assert.Equal(t, 100, unitInfo.Snob.Defense)
assert.Equal(t, 50, unitInfo.Snob.DefenseCavalry)
assert.Equal(t, 100, unitInfo.Snob.DefenseArcher)
assert.Equal(t, 0, unitInfo.Snob.Carry)
assert.InDelta(t, 1.0, unitInfo.Militia.BuildTime, 0.01)
assert.Equal(t, 0, unitInfo.Militia.Pop)
assert.InDelta(t, 0.016666666666667, unitInfo.Militia.Speed, 0.01)
assert.Equal(t, 0, unitInfo.Militia.Attack)
assert.Equal(t, 15, unitInfo.Militia.Defense)
assert.Equal(t, 45, unitInfo.Militia.DefenseCavalry)
assert.Equal(t, 25, unitInfo.Militia.DefenseArcher)
assert.Equal(t, 0, unitInfo.Militia.Carry)
}
func TestClient_GetBuildingInfo(t *testing.T) {
t.Parallel()
resp, err := os.ReadFile("./testdata/building_info.xml")
require.NoError(t, err)
mux := http.NewServeMux()
mux.HandleFunc("/interface.php", newInterfaceXMLHandler("get_building_info", resp))
srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close)
buildingInfo, err := newClient(srv.Client()).GetBuildingInfo(context.Background(), parseURL(t, srv.URL))
require.NoError(t, err)
assert.Equal(t, 30, buildingInfo.Main.MaxLevel)
assert.Equal(t, 1, buildingInfo.Main.MinLevel)
assert.Equal(t, 90, buildingInfo.Main.Wood)
assert.Equal(t, 80, buildingInfo.Main.Stone)
assert.Equal(t, 70, buildingInfo.Main.Iron)
assert.Equal(t, 5, buildingInfo.Main.Pop)
assert.InDelta(t, 1.26, buildingInfo.Main.WoodFactor, 0.01)
assert.InDelta(t, 1.275, buildingInfo.Main.StoneFactor, 0.01)
assert.InDelta(t, 1.26, buildingInfo.Main.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Main.PopFactor, 0.01)
assert.InDelta(t, 900.0, buildingInfo.Main.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Main.BuildTimeFactor, 0.01)
assert.Equal(t, 25, buildingInfo.Barracks.MaxLevel)
assert.Equal(t, 0, buildingInfo.Barracks.MinLevel)
assert.Equal(t, 200, buildingInfo.Barracks.Wood)
assert.Equal(t, 170, buildingInfo.Barracks.Stone)
assert.Equal(t, 90, buildingInfo.Barracks.Iron)
assert.Equal(t, 7, buildingInfo.Barracks.Pop)
assert.InDelta(t, 1.26, buildingInfo.Barracks.WoodFactor, 0.01)
assert.InDelta(t, 1.28, buildingInfo.Barracks.StoneFactor, 0.01)
assert.InDelta(t, 1.26, buildingInfo.Barracks.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Barracks.PopFactor, 0.01)
assert.InDelta(t, 1800.0, buildingInfo.Barracks.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Barracks.BuildTimeFactor, 0.01)
assert.Equal(t, 20, buildingInfo.Stable.MaxLevel)
assert.Equal(t, 0, buildingInfo.Stable.MinLevel)
assert.Equal(t, 270, buildingInfo.Stable.Wood)
assert.Equal(t, 240, buildingInfo.Stable.Stone)
assert.Equal(t, 260, buildingInfo.Stable.Iron)
assert.Equal(t, 8, buildingInfo.Stable.Pop)
assert.InDelta(t, 1.26, buildingInfo.Stable.WoodFactor, 0.01)
assert.InDelta(t, 1.28, buildingInfo.Stable.StoneFactor, 0.01)
assert.InDelta(t, 1.26, buildingInfo.Stable.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Stable.PopFactor, 0.01)
assert.InDelta(t, 6000.0, buildingInfo.Stable.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Stable.BuildTimeFactor, 0.01)
assert.Equal(t, 15, buildingInfo.Garage.MaxLevel)
assert.Equal(t, 0, buildingInfo.Garage.MinLevel)
assert.Equal(t, 300, buildingInfo.Garage.Wood)
assert.Equal(t, 240, buildingInfo.Garage.Stone)
assert.Equal(t, 260, buildingInfo.Garage.Iron)
assert.Equal(t, 8, buildingInfo.Garage.Pop)
assert.InDelta(t, 1.26, buildingInfo.Garage.WoodFactor, 0.01)
assert.InDelta(t, 1.28, buildingInfo.Garage.StoneFactor, 0.01)
assert.InDelta(t, 1.26, buildingInfo.Garage.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Garage.PopFactor, 0.01)
assert.InDelta(t, 6000.0, buildingInfo.Garage.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Garage.BuildTimeFactor, 0.01)
assert.Equal(t, 20, buildingInfo.Watchtower.MaxLevel)
assert.Equal(t, 0, buildingInfo.Watchtower.MinLevel)
assert.Equal(t, 12000, buildingInfo.Watchtower.Wood)
assert.Equal(t, 14000, buildingInfo.Watchtower.Stone)
assert.Equal(t, 10000, buildingInfo.Watchtower.Iron)
assert.Equal(t, 500, buildingInfo.Watchtower.Pop)
assert.InDelta(t, 1.17, buildingInfo.Watchtower.WoodFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Watchtower.StoneFactor, 0.01)
assert.InDelta(t, 1.18, buildingInfo.Watchtower.IronFactor, 0.01)
assert.InDelta(t, 1.18, buildingInfo.Watchtower.PopFactor, 0.01)
assert.InDelta(t, 13200.0, buildingInfo.Watchtower.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Watchtower.BuildTimeFactor, 0.01)
assert.Equal(t, 1, buildingInfo.Snob.MaxLevel)
assert.Equal(t, 0, buildingInfo.Snob.MinLevel)
assert.Equal(t, 15000, buildingInfo.Snob.Wood)
assert.Equal(t, 25000, buildingInfo.Snob.Stone)
assert.Equal(t, 10000, buildingInfo.Snob.Iron)
assert.Equal(t, 80, buildingInfo.Snob.Pop)
assert.InDelta(t, 2.0, buildingInfo.Snob.WoodFactor, 0.01)
assert.InDelta(t, 2.0, buildingInfo.Snob.StoneFactor, 0.01)
assert.InDelta(t, 2.0, buildingInfo.Snob.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Snob.PopFactor, 0.01)
assert.InDelta(t, 586800.0, buildingInfo.Snob.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Snob.BuildTimeFactor, 0.01)
assert.Equal(t, 20, buildingInfo.Smith.MaxLevel)
assert.Equal(t, 0, buildingInfo.Smith.MinLevel)
assert.Equal(t, 220, buildingInfo.Smith.Wood)
assert.Equal(t, 180, buildingInfo.Smith.Stone)
assert.Equal(t, 240, buildingInfo.Smith.Iron)
assert.Equal(t, 20, buildingInfo.Smith.Pop)
assert.InDelta(t, 1.26, buildingInfo.Smith.WoodFactor, 0.01)
assert.InDelta(t, 1.275, buildingInfo.Smith.StoneFactor, 0.01)
assert.InDelta(t, 1.26, buildingInfo.Smith.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Smith.PopFactor, 0.01)
assert.InDelta(t, 6000.0, buildingInfo.Smith.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Smith.BuildTimeFactor, 0.01)
assert.Equal(t, 1, buildingInfo.Place.MaxLevel)
assert.Equal(t, 0, buildingInfo.Place.MinLevel)
assert.Equal(t, 10, buildingInfo.Place.Wood)
assert.Equal(t, 40, buildingInfo.Place.Stone)
assert.Equal(t, 30, buildingInfo.Place.Iron)
assert.Equal(t, 0, buildingInfo.Place.Pop)
assert.InDelta(t, 1.26, buildingInfo.Place.WoodFactor, 0.01)
assert.InDelta(t, 1.275, buildingInfo.Place.StoneFactor, 0.01)
assert.InDelta(t, 1.26, buildingInfo.Place.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Place.PopFactor, 0.01)
assert.InDelta(t, 10860.0, buildingInfo.Place.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Place.BuildTimeFactor, 0.01)
assert.Equal(t, 1, buildingInfo.Statue.MaxLevel)
assert.Equal(t, 0, buildingInfo.Statue.MinLevel)
assert.Equal(t, 220, buildingInfo.Statue.Wood)
assert.Equal(t, 220, buildingInfo.Statue.Stone)
assert.Equal(t, 220, buildingInfo.Statue.Iron)
assert.Equal(t, 10, buildingInfo.Statue.Pop)
assert.InDelta(t, 1.26, buildingInfo.Statue.WoodFactor, 0.01)
assert.InDelta(t, 1.275, buildingInfo.Statue.StoneFactor, 0.01)
assert.InDelta(t, 1.26, buildingInfo.Statue.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Statue.PopFactor, 0.01)
assert.InDelta(t, 1500.0, buildingInfo.Statue.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Statue.BuildTimeFactor, 0.01)
assert.Equal(t, 25, buildingInfo.Market.MaxLevel)
assert.Equal(t, 0, buildingInfo.Market.MinLevel)
assert.Equal(t, 100, buildingInfo.Market.Wood)
assert.Equal(t, 100, buildingInfo.Market.Stone)
assert.Equal(t, 100, buildingInfo.Market.Iron)
assert.Equal(t, 20, buildingInfo.Market.Pop)
assert.InDelta(t, 1.26, buildingInfo.Market.WoodFactor, 0.01)
assert.InDelta(t, 1.275, buildingInfo.Market.StoneFactor, 0.01)
assert.InDelta(t, 1.26, buildingInfo.Market.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Market.PopFactor, 0.01)
assert.InDelta(t, 2700.0, buildingInfo.Market.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Market.BuildTimeFactor, 0.01)
assert.Equal(t, 30, buildingInfo.Wood.MaxLevel)
assert.Equal(t, 0, buildingInfo.Wood.MinLevel)
assert.Equal(t, 50, buildingInfo.Wood.Wood)
assert.Equal(t, 60, buildingInfo.Wood.Stone)
assert.Equal(t, 40, buildingInfo.Wood.Iron)
assert.Equal(t, 5, buildingInfo.Wood.Pop)
assert.InDelta(t, 1.25, buildingInfo.Wood.WoodFactor, 0.01)
assert.InDelta(t, 1.275, buildingInfo.Wood.StoneFactor, 0.01)
assert.InDelta(t, 1.245, buildingInfo.Wood.IronFactor, 0.01)
assert.InDelta(t, 1.155, buildingInfo.Wood.PopFactor, 0.01)
assert.InDelta(t, 900.0, buildingInfo.Wood.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Wood.BuildTimeFactor, 0.01)
assert.Equal(t, 30, buildingInfo.Stone.MaxLevel)
assert.Equal(t, 0, buildingInfo.Stone.MinLevel)
assert.Equal(t, 65, buildingInfo.Stone.Wood)
assert.Equal(t, 50, buildingInfo.Stone.Stone)
assert.Equal(t, 40, buildingInfo.Stone.Iron)
assert.Equal(t, 10, buildingInfo.Stone.Pop)
assert.InDelta(t, 1.27, buildingInfo.Stone.WoodFactor, 0.01)
assert.InDelta(t, 1.265, buildingInfo.Stone.StoneFactor, 0.01)
assert.InDelta(t, 1.24, buildingInfo.Stone.IronFactor, 0.01)
assert.InDelta(t, 1.14, buildingInfo.Stone.PopFactor, 0.01)
assert.InDelta(t, 900.0, buildingInfo.Stone.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Stone.BuildTimeFactor, 0.01)
assert.Equal(t, 30, buildingInfo.Iron.MaxLevel)
assert.Equal(t, 0, buildingInfo.Iron.MinLevel)
assert.Equal(t, 75, buildingInfo.Iron.Wood)
assert.Equal(t, 65, buildingInfo.Iron.Stone)
assert.Equal(t, 70, buildingInfo.Iron.Iron)
assert.Equal(t, 10, buildingInfo.Iron.Pop)
assert.InDelta(t, 1.252, buildingInfo.Iron.WoodFactor, 0.01)
assert.InDelta(t, 1.275, buildingInfo.Iron.StoneFactor, 0.01)
assert.InDelta(t, 1.24, buildingInfo.Iron.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Iron.PopFactor, 0.01)
assert.InDelta(t, 1080.0, buildingInfo.Iron.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Iron.BuildTimeFactor, 0.01)
assert.Equal(t, 30, buildingInfo.Farm.MaxLevel)
assert.Equal(t, 1, buildingInfo.Farm.MinLevel)
assert.Equal(t, 45, buildingInfo.Farm.Wood)
assert.Equal(t, 40, buildingInfo.Farm.Stone)
assert.Equal(t, 30, buildingInfo.Farm.Iron)
assert.Equal(t, 0, buildingInfo.Farm.Pop)
assert.InDelta(t, 1.3, buildingInfo.Farm.WoodFactor, 0.01)
assert.InDelta(t, 1.32, buildingInfo.Farm.StoneFactor, 0.01)
assert.InDelta(t, 1.29, buildingInfo.Farm.IronFactor, 0.01)
assert.InDelta(t, 1.0, buildingInfo.Farm.PopFactor, 0.01)
assert.InDelta(t, 1200.0, buildingInfo.Farm.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Farm.BuildTimeFactor, 0.01)
assert.Equal(t, 30, buildingInfo.Storage.MaxLevel)
assert.Equal(t, 1, buildingInfo.Storage.MinLevel)
assert.Equal(t, 60, buildingInfo.Storage.Wood)
assert.Equal(t, 50, buildingInfo.Storage.Stone)
assert.Equal(t, 40, buildingInfo.Storage.Iron)
assert.Equal(t, 0, buildingInfo.Storage.Pop)
assert.InDelta(t, 1.265, buildingInfo.Storage.WoodFactor, 0.01)
assert.InDelta(t, 1.27, buildingInfo.Storage.StoneFactor, 0.01)
assert.InDelta(t, 1.245, buildingInfo.Storage.IronFactor, 0.01)
assert.InDelta(t, 1.15, buildingInfo.Storage.PopFactor, 0.01)
assert.InDelta(t, 1020.0, buildingInfo.Storage.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Storage.BuildTimeFactor, 0.01)
assert.Equal(t, 10, buildingInfo.Hide.MaxLevel)
assert.Equal(t, 0, buildingInfo.Hide.MinLevel)
assert.Equal(t, 50, buildingInfo.Hide.Wood)
assert.Equal(t, 60, buildingInfo.Hide.Stone)
assert.Equal(t, 50, buildingInfo.Hide.Iron)
assert.Equal(t, 2, buildingInfo.Hide.Pop)
assert.InDelta(t, 1.25, buildingInfo.Hide.WoodFactor, 0.01)
assert.InDelta(t, 1.25, buildingInfo.Hide.StoneFactor, 0.01)
assert.InDelta(t, 1.25, buildingInfo.Hide.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Hide.PopFactor, 0.01)
assert.InDelta(t, 1800.0, buildingInfo.Hide.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Hide.BuildTimeFactor, 0.01)
assert.Equal(t, 20, buildingInfo.Wall.MaxLevel)
assert.Equal(t, 0, buildingInfo.Wall.MinLevel)
assert.Equal(t, 50, buildingInfo.Wall.Wood)
assert.Equal(t, 100, buildingInfo.Wall.Stone)
assert.Equal(t, 20, buildingInfo.Wall.Iron)
assert.Equal(t, 5, buildingInfo.Wall.Pop)
assert.InDelta(t, 1.26, buildingInfo.Wall.WoodFactor, 0.01)
assert.InDelta(t, 1.275, buildingInfo.Wall.StoneFactor, 0.01)
assert.InDelta(t, 1.26, buildingInfo.Wall.IronFactor, 0.01)
assert.InDelta(t, 1.17, buildingInfo.Wall.PopFactor, 0.01)
assert.InDelta(t, 3600.0, buildingInfo.Wall.BuildTime, 0.01)
assert.InDelta(t, 1.2, buildingInfo.Wall.BuildTimeFactor, 0.01)
}
func TestClient_GetTribes(t *testing.T) {
t.Parallel()
tests := []struct {
name string
respTribe string
respOD string
respODA string
respODD string
checkErr func(err error) bool
expectedTribes func(t *testing.T, url string) []tw.Tribe
}{
{
name: "OK",
respTribe: "1,name%201,tag%201,100,101,102,103,104\n2,name2,tag2,200,202,202,203,204\n3,name3,tag3,300,303,302,303,304\n4,name4,tag4,400,404,402,403,404", //nolint:lll
respOD: "1,1,1\n2,2,2\n3,3,3",
respODA: "1,1,1\n2,2,2\n3,3,3",
respODD: "1,1,1\n2,2,2\n3,3,3",
expectedTribes: func(t *testing.T, baseURL string) []tw.Tribe {
t.Helper()
return []tw.Tribe{
{
ID: 1,
Name: "name 1",
Tag: "tag 1",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=1"),
NumMembers: 100,
NumVillages: 101,
Points: 102,
AllPoints: 103,
Rank: 104,
OpponentsDefeated: tw.OpponentsDefeated{
RankAtt: 1,
ScoreAtt: 1,
RankDef: 1,
ScoreDef: 1,
RankSup: 0,
ScoreSup: 0,
RankTotal: 1,
ScoreTotal: 1,
},
},
{
ID: 2,
Name: "name2",
Tag: "tag2",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=2"),
NumMembers: 200,
NumVillages: 202,
Points: 202,
AllPoints: 203,
Rank: 204,
OpponentsDefeated: tw.OpponentsDefeated{
RankAtt: 2,
ScoreAtt: 2,
RankDef: 2,
ScoreDef: 2,
RankSup: 0,
ScoreSup: 0,
RankTotal: 2,
ScoreTotal: 2,
},
},
{
ID: 3,
Name: "name3",
Tag: "tag3",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=3"),
NumMembers: 300,
NumVillages: 303,
Points: 302,
AllPoints: 303,
Rank: 304,
OpponentsDefeated: tw.OpponentsDefeated{
RankAtt: 3,
ScoreAtt: 3,
RankDef: 3,
ScoreDef: 3,
RankSup: 0,
ScoreSup: 0,
RankTotal: 3,
ScoreTotal: 3,
},
},
{
ID: 4,
Name: "name4",
Tag: "tag4",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=4"),
NumMembers: 400,
NumVillages: 404,
Points: 402,
AllPoints: 403,
Rank: 404,
OpponentsDefeated: tw.OpponentsDefeated{},
},
}
},
},
{
name: "ERR: OD.Rank can't be converted to int64",
respOD: "test,1,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: OD.ID -can't be converted to int64",
respOD: "1,test,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: OD.Score can't be converted to int64",
respOD: "1,1,test",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODA.Rank can't be converted to int64",
respODA: "test,1,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODA.ID can't be converted to int64",
respODA: "1,test,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODA.Score can't be converted to int64",
respODA: "1,1,test",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODD.Rank can't be converted to int64",
respODD: "test,1,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODD.ID can't be converted to int64",
respODD: "1,test,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODD.Score can't be converted to int64",
respODD: "1,1,test",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: too few fields",
respTribe: "1,name%201,tag%201,100,101,102,103",
checkErr: func(err error) bool {
return errors.Is(err, csv.ErrFieldCount)
},
},
{
name: "ERR: too many fields",
respTribe: "1,name%201,tag%201,100,101,102,103,104,105",
checkErr: func(err error) bool {
return errors.Is(err, csv.ErrFieldCount)
},
},
{
name: "ERR: Tribe.ID can't be converted to int64",
respTribe: "1.23,name1,tag1,100,101,102,103,104",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Tribe.ID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Tribe.NumMembers can't be converted to int64",
respTribe: "1,name1,tag1,100.23,101,102,103,104",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Tribe.NumMembers" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Tribe.NumVillages can't be converted to int64",
respTribe: "1,name1,tag1,100,101.23,102,103,104",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Tribe.NumVillages" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Tribe.Points can't be converted to int64",
respTribe: "1,name1,tag1,100,101,102.23,103,104",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Tribe.Points" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Tribe.AllPoints can't be converted to int64",
respTribe: "1,name1,tag1,100,101,102,103.23,104",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Tribe.AllPoints" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Tribe.Rank can't be converted to int64",
respTribe: "1,name1,tag1,100,101,102,103,104.23",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Tribe.Rank" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
mux := http.NewServeMux()
mux.HandleFunc("/map/kill_all_tribe.txt", newPlainTextHandlerString(tt.respOD))
mux.HandleFunc("/map/kill_att_tribe.txt", newPlainTextHandlerString(tt.respODA))
mux.HandleFunc("/map/kill_def_tribe.txt", newPlainTextHandlerString(tt.respODD))
mux.HandleFunc("/map/ally.txt", newPlainTextHandlerString(tt.respTribe))
srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close)
tribes, err := newClient(srv.Client()).GetTribes(context.Background(), parseURL(t, srv.URL))
checkErr := func(err error) bool {
return errors.Is(err, nil)
}
if tt.checkErr != nil {
checkErr = tt.checkErr
}
var expectedTribes []tw.Tribe
if tt.expectedTribes != nil {
expectedTribes = tt.expectedTribes(t, srv.URL)
}
assert.True(t, checkErr(err))
require.Len(t, tribes, len(expectedTribes))
for i, tribe := range tribes {
assert.Equal(t, expectedTribes[i], tribe)
}
})
}
}
func TestClient_GetPlayers(t *testing.T) {
t.Parallel()
tests := []struct {
name string
respPlayer string
respOD string
respODA string
respODD string
respODS string
checkErr func(err error) bool
expectedPlayers func(t *testing.T, url string) []tw.Player
}{
{
name: "OK",
respPlayer: "1,name%201,123,124,125,126\n2,name2,256,257,258,259\n3,name3,356,357,358,359\n4,name4,456,457,458,459",
respOD: "1,1,1001\n2,2,252\n3,3,125",
respODA: "1,1,1000\n2,2,253\n3,3,100",
respODD: "1,1,1002\n2,2,251\n3,3,155",
respODS: "1,1,1003\n2,2,250\n3,3,166",
expectedPlayers: func(t *testing.T, baseURL string) []tw.Player {
t.Helper()
return []tw.Player{
{
ID: 1,
Name: "name 1",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=1"),
TribeID: 123,
NumVillages: 124,
Points: 125,
Rank: 126,
OpponentsDefeated: tw.OpponentsDefeated{
RankAtt: 1,
ScoreAtt: 1000,
RankDef: 1,
ScoreDef: 1002,
RankSup: 1,
ScoreSup: 1003,
RankTotal: 1,
ScoreTotal: 1001,
},
},
{
ID: 2,
Name: "name2",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=2"),
TribeID: 256,
NumVillages: 257,
Points: 258,
Rank: 259,
OpponentsDefeated: tw.OpponentsDefeated{
RankAtt: 2,
ScoreAtt: 253,
RankDef: 2,
ScoreDef: 251,
RankSup: 2,
ScoreSup: 250,
RankTotal: 2,
ScoreTotal: 252,
},
},
{
ID: 3,
Name: "name3",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=3"),
TribeID: 356,
NumVillages: 357,
Points: 358,
Rank: 359,
OpponentsDefeated: tw.OpponentsDefeated{
RankAtt: 3,
ScoreAtt: 100,
RankDef: 3,
ScoreDef: 155,
RankSup: 3,
ScoreSup: 166,
RankTotal: 3,
ScoreTotal: 125,
},
},
{
ID: 4,
Name: "name4",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=4"),
TribeID: 456,
NumVillages: 457,
Points: 458,
Rank: 459,
OpponentsDefeated: tw.OpponentsDefeated{},
},
}
},
},
{
name: "ERR: OD.Rank can't be converted to int64",
respOD: "test,1,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: OD.ID can't be converted to int64",
respOD: "1,test,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: OD.Score can't be converted to int64",
respOD: "1,1,test",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODA.Rank can't be converted to int64",
respODA: "test,1,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODA.ID can't be converted to int64",
respODA: "1,test,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODA.Score can't be converted to int64",
respODA: "1,1,test",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODD.Rank can't be converted to int64",
respODD: "test,1,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODD.ID can't be converted to int64",
respODD: "1,test,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODD.Score can't be converted to int64",
respODD: "1,1,test",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODS.Rank can't be converted to int64",
respODS: "test,1,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODS.ID can't be converted to int64",
respODS: "1,test,1001",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: ODS.Score can't be converted to int64",
respODS: "1,1,test",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: too few fields",
respPlayer: "1,name%201,123,124,125",
checkErr: func(err error) bool {
return errors.Is(err, csv.ErrFieldCount)
},
},
{
name: "ERR: too many fields",
respPlayer: "1,name%201,123,124,125,126,127",
checkErr: func(err error) bool {
return errors.Is(err, csv.ErrFieldCount)
},
},
{
name: "ERR: Player.ID can't be converted to int64",
respPlayer: "1.25,name,123,124,125,126",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Player.ID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Player.TribeID can't be converted to int64",
respPlayer: "1,name,1.23,124,125,126",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Player.TribeID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Player.NumVillages can't be converted to int64",
respPlayer: "1,name,123,1.24,125,126",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Player.NumVillages" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Player.Points can't be converted to int64",
respPlayer: "1,name,123,124,1.25,126",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Player.Points" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Player.Rank can't be converted to int64",
respPlayer: "1,name,123,124,125,1.26",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Player.Rank" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
mux := http.NewServeMux()
mux.HandleFunc("/map/kill_all.txt", newPlainTextHandlerString(tt.respOD))
mux.HandleFunc("/map/kill_att.txt", newPlainTextHandlerString(tt.respODA))
mux.HandleFunc("/map/kill_def.txt", newPlainTextHandlerString(tt.respODD))
mux.HandleFunc("/map/kill_sup.txt", newPlainTextHandlerString(tt.respODS))
mux.HandleFunc("/map/player.txt", newPlainTextHandlerString(tt.respPlayer))
srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close)
players, err := newClient(srv.Client()).GetPlayers(context.Background(), parseURL(t, srv.URL))
checkErr := func(err error) bool {
return errors.Is(err, nil)
}
if tt.checkErr != nil {
checkErr = tt.checkErr
}
var expectedPlayers []tw.Player
if tt.expectedPlayers != nil {
expectedPlayers = tt.expectedPlayers(t, srv.URL)
}
assert.True(t, checkErr(err))
require.Len(t, players, len(expectedPlayers))
for i, player := range players {
assert.Equal(t, expectedPlayers[i], player)
}
})
}
}
func TestClient_GetVillages(t *testing.T) {
t.Parallel()
tests := []struct {
name string
resp string
checkErr func(err error) bool
expectedVillages func(t *testing.T, baseURL string) []tw.Village
}{
{
name: "OK",
//nolint:lll
resp: "123,village%201,500,501,502,503,504\n124,village 2,100,201,102,103,104\n125,village%203,1,501,502,503,504\n126,village%204,1,1,502,503,504\n127,village%205,103,1,502,503,504",
expectedVillages: func(t *testing.T, baseURL string) []tw.Village {
t.Helper()
return []tw.Village{
{
ID: 123,
Name: "village 1",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=123"),
X: 500,
Y: 501,
Continent: "K55",
PlayerID: 502,
Points: 503,
Bonus: 504,
},
{
ID: 124,
Name: "village 2",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=124"),
X: 100,
Y: 201,
Continent: "K21",
PlayerID: 102,
Points: 103,
Bonus: 104,
},
{
ID: 125,
Name: "village 3",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=125"),
X: 1,
Y: 501,
Continent: "K50",
PlayerID: 502,
Points: 503,
Bonus: 504,
},
{
ID: 126,
Name: "village 4",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=126"),
X: 1,
Y: 1,
Continent: "K0",
PlayerID: 502,
Points: 503,
Bonus: 504,
},
{
ID: 127,
Name: "village 5",
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=127"),
X: 103,
Y: 1,
Continent: "K1",
PlayerID: 502,
Points: 503,
Bonus: 504,
},
}
},
},
{
name: "ERR: too few fields",
resp: "123,village 1,500,501,502,503",
checkErr: func(err error) bool {
return errors.Is(err, csv.ErrFieldCount)
},
},
{
name: "ERR: too many fields",
resp: "123,village 1,500,501,502,503,504,505",
checkErr: func(err error) bool {
return errors.Is(err, csv.ErrFieldCount)
},
},
{
name: "ERR: Village.ID can't be converted to int64",
resp: "123.23,village 1,500,501,502,503,504",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Village.ID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Village.X can't be converted to int64",
resp: "123,village 1,123.23,501,502,503,504",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Village.X" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Village.Y can't be converted to int64",
resp: "123,village 1,500,123.23,502,503,504",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Village.Y" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Village.PlayerID can't be converted to int64",
resp: "123,village 1,500,501,123.23,503,504",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Village.PlayerID" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Village.Points can't be converted to int64",
resp: "123,village 1,500,501,502,123.23,504",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Village.Points" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Village.Bonus can't be converted to int64",
resp: "123,village 1,500,501,502,503,123.23",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) && parseErr.Field == "Village.Bonus" && errors.Is(parseErr, strconv.ErrSyntax)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
mux := http.NewServeMux()
mux.HandleFunc("/map/village.txt", newPlainTextHandlerString(tt.resp))
srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close)
villages, err := newClient(srv.Client()).GetVillages(context.Background(), parseURL(t, srv.URL))
checkErr := func(err error) bool {
return errors.Is(err, nil)
}
if tt.checkErr != nil {
checkErr = tt.checkErr
}
var expectedVillages []tw.Village
if tt.expectedVillages != nil {
expectedVillages = tt.expectedVillages(t, srv.URL)
}
assert.True(t, checkErr(err))
require.Len(t, villages, len(expectedVillages))
for i, village := range villages {
assert.Equal(t, expectedVillages[i], village)
}
})
}
}
func TestClient_GetEnnoblements(t *testing.T) {
t.Parallel()
t.Run("map/conquer_extended.txt", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
since time.Time
resp string
checkErr func(err error) bool
expectedEnnoblements []tw.Ennoblement
}{
{
name: "OK",
since: time.Unix(1657842401, 0),
//nolint:lll
resp: "1424,1657842406,698953084,699589674,0,122,380\n3150,1657882879,6461674,0,0,179,111\n1025,1657947400,9157005,698942601,0,52,461\n1025,1657842400,9157005,698942601,0,52,461",
checkErr: nil,
expectedEnnoblements: []tw.Ennoblement{ // the last one should be skipped (1657842401 > 1657842400)
{
VillageID: 1424,
NewOwnerID: 698953084,
NewTribeID: 122,
OldOwnerID: 699589674,
OldTribeID: 0,
Points: 380,
CreatedAt: time.Unix(1657842406, 0),
},
{
VillageID: 3150,
NewOwnerID: 6461674,
NewTribeID: 179,
OldOwnerID: 0,
OldTribeID: 0,
Points: 111,
CreatedAt: time.Unix(1657882879, 0),
},
{
VillageID: 1025,
NewOwnerID: 9157005,
NewTribeID: 52,
OldOwnerID: 698942601,
OldTribeID: 0,
Points: 461,
CreatedAt: time.Unix(1657947400, 0),
},
},
},
{
name: "ERR: too few fields",
resp: "1424,1657842406,698953084,699589674,0,122",
checkErr: func(err error) bool {
return errors.Is(err, csv.ErrFieldCount)
},
},
{
name: "ERR: too many fields",
resp: "1424,1657842406,698953084,699589674,0,122,380,0",
checkErr: func(err error) bool {
return errors.Is(err, csv.ErrFieldCount)
},
},
{
name: "ERR: Ennoblement.VillageID can't be converted to int64",
resp: "asd,1657842406,698953084,699589674,0,122,380",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.VillageID" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.VillageID can't be converted to int64",
resp: "1424,asd,698953084,699589674,0,122,380",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.CreatedAt" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.NewOwnerID can't be converted to int64",
resp: "1424,1657842406,asd,699589674,0,122,380",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.NewOwnerID" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.OldOwnerID can't be converted to int64",
resp: "1424,1657842406,698953084,asd,0,122,380",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.OldOwnerID" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.OldTribeID can't be converted to int64",
resp: "1424,1657842406,698953084,699589674,asd,122,380",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.OldTribeID" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.NewTribeID can't be converted to int64",
resp: "1424,1657842406,698953084,699589674,0,asd,380",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.NewTribeID" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.Points can't be converted to int64",
resp: "1424,1657842406,698953084,699589674,0,122,asd",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.Points" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
mux := http.NewServeMux()
mux.HandleFunc("/map/conquer_extended.txt", newPlainTextHandlerString(tt.resp))
srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close)
ennoblements, err := newClient(srv.Client()).GetEnnoblements(context.Background(), parseURL(t, srv.URL), tt.since)
checkErr := func(err error) bool {
return errors.Is(err, nil)
}
if tt.checkErr != nil {
checkErr = tt.checkErr
}
assert.True(t, checkErr(err))
require.Len(t, ennoblements, len(tt.expectedEnnoblements))
for i, ennoblement := range ennoblements {
assert.Equal(t, tt.expectedEnnoblements[i], ennoblement)
}
})
}
})
t.Run("interface.php?func=get_conquer_extended", func(t *testing.T) {
t.Parallel()
now := time.Now()
tests := []struct {
name string
since time.Time
resp string
checkErr func(err error) bool
clientOpts []tw.ClientOption
expectedEnnoblements []tw.Ennoblement
}{
{
name: "OK: without custom ennoblementsUseInterfaceFunc",
since: now.Add(-22 * time.Hour),
resp: fmt.Sprintf(
"1424,%d,698953084,699589674,0,122,380\n3150,%d,6461674,0,0,179,111\n1025,%d,9157005,698942601,0,52,461",
now.Add(-21*time.Hour).Unix(),
now.Add(-15*time.Minute).Unix(),
now.Add(-15*time.Second).Unix(),
),
checkErr: nil,
expectedEnnoblements: []tw.Ennoblement{
{
VillageID: 1424,
NewOwnerID: 698953084,
NewTribeID: 122,
OldOwnerID: 699589674,
OldTribeID: 0,
Points: 380,
CreatedAt: time.Unix(now.Add(-21*time.Hour).Unix(), 0),
},
{
VillageID: 3150,
NewOwnerID: 6461674,
NewTribeID: 179,
OldOwnerID: 0,
OldTribeID: 0,
Points: 111,
CreatedAt: time.Unix(now.Add(-15*time.Minute).Unix(), 0),
},
{
VillageID: 1025,
NewOwnerID: 9157005,
NewTribeID: 52,
OldOwnerID: 698942601,
OldTribeID: 0,
Points: 461,
CreatedAt: time.Unix(now.Add(-15*time.Second).Unix(), 0),
},
},
},
{
name: "OK: with custom ennoblementsUseInterfaceFunc",
since: now.Add(-47 * time.Hour),
resp: fmt.Sprintf(
"1424,%d,698953084,699589674,0,122,380\n3150,%d,6461674,0,0,179,111\n1025,%d,9157005,698942601,0,52,461",
now.Add(-44*time.Hour).Unix(),
now.Add(-40*time.Minute).Unix(),
now.Add(-40*time.Second).Unix(),
),
checkErr: nil,
clientOpts: []tw.ClientOption{
tw.WithEnnoblementsUseInterfaceFunc(func(since time.Time) bool {
return since.After(time.Now().Add(-48 * time.Hour))
}),
},
expectedEnnoblements: []tw.Ennoblement{
{
VillageID: 1424,
NewOwnerID: 698953084,
NewTribeID: 122,
OldOwnerID: 699589674,
OldTribeID: 0,
Points: 380,
CreatedAt: time.Unix(now.Add(-44*time.Hour).Unix(), 0),
},
{
VillageID: 3150,
NewOwnerID: 6461674,
NewTribeID: 179,
OldOwnerID: 0,
OldTribeID: 0,
Points: 111,
CreatedAt: time.Unix(now.Add(-40*time.Minute).Unix(), 0),
},
{
VillageID: 1025,
NewOwnerID: 9157005,
NewTribeID: 52,
OldOwnerID: 698942601,
OldTribeID: 0,
Points: 461,
CreatedAt: time.Unix(now.Add(-40*time.Second).Unix(), 0),
},
},
},
{
name: "ERR: too few fields",
since: now.Add(-22 * time.Hour),
resp: fmt.Sprintf("1424,%d,698953084,699589674,0,122", now.Add(-21*time.Hour).Unix()),
checkErr: func(err error) bool {
return errors.Is(err, csv.ErrFieldCount)
},
},
{
name: "ERR: too many fields",
since: now.Add(-22 * time.Hour),
resp: fmt.Sprintf("1424,%d,698953084,699589674,0,122,380,0", now.Add(-21*time.Hour).Unix()),
checkErr: func(err error) bool {
return errors.Is(err, csv.ErrFieldCount)
},
},
{
name: "ERR: Ennoblement.VillageID can't be converted to int64",
since: now.Add(-22 * time.Hour),
resp: fmt.Sprintf("asd,%d,698953084,699589674,0,122,380", now.Add(-21*time.Hour).Unix()),
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.VillageID" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.CreatedAt can't be converted to int64",
since: now.Add(-22 * time.Hour),
resp: "1424,asd,698953084,699589674,0,122,380",
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.CreatedAt" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.NewOwnerID can't be converted to int64",
since: now.Add(-22 * time.Hour),
resp: fmt.Sprintf("1424,%d,asd,699589674,0,122,380", now.Add(-21*time.Hour).Unix()),
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.NewOwnerID" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.OldOwnerID can't be converted to int64",
since: now.Add(-22 * time.Hour),
resp: fmt.Sprintf("1424,%d,698953084,asd,0,122,380", now.Add(-21*time.Hour).Unix()),
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.OldOwnerID" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.OldTribeID can't be converted to int64",
since: now.Add(-22 * time.Hour),
resp: fmt.Sprintf("1424,%d,698953084,699589674,asd,122,380", now.Add(-21*time.Hour).Unix()),
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.OldTribeID" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.NewTribeID can't be converted to int64",
since: now.Add(-22 * time.Hour),
resp: fmt.Sprintf("1424,%d,698953084,699589674,0,asd,380", now.Add(-21*time.Hour).Unix()),
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.NewTribeID" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
{
name: "ERR: Ennoblement.Points can't be converted to int64",
since: now.Add(-22 * time.Hour),
resp: fmt.Sprintf("1424,%d,698953084,699589674,0,122,asd", now.Add(-21*time.Hour).Unix()),
checkErr: func(err error) bool {
var parseErr tw.ParseError
return errors.As(err, &parseErr) &&
parseErr.Field == "Ennoblement.Points" &&
errors.Is(parseErr, strconv.ErrSyntax)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
mux := http.NewServeMux()
mux.HandleFunc("/interface.php", newInterfacePlainTextHandlerString("get_conquer_extended", tt.resp))
srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close)
client := newClient(srv.Client(), tt.clientOpts...)
ennoblements, err := client.GetEnnoblements(context.Background(), parseURL(t, srv.URL), tt.since)
checkErr := func(err error) bool {
return errors.Is(err, nil)
}
if tt.checkErr != nil {
checkErr = tt.checkErr
}
assert.True(t, checkErr(err))
require.Len(t, ennoblements, len(tt.expectedEnnoblements))
for i, ennoblement := range ennoblements {
assert.Equal(t, tt.expectedEnnoblements[i], ennoblement)
}
})
}
})
}
func parseURL(tb testing.TB, rawURL string) *url.URL {
tb.Helper()
u, err := url.ParseRequestURI(rawURL)
require.NoError(tb, err)
return u
}
// user agent and http client can't be overridden via opts
func newClient(hc *http.Client, opts ...tw.ClientOption) *tw.Client {
return tw.NewClient(append(opts, tw.WithHTTPClient(hc), tw.WithUserAgent(testUserAgent))...)
}
func newInterfaceXMLHandler(funcName string, result []byte) func(w http.ResponseWriter, r *http.Request) {
return newInterfaceHandler(funcName, "text/xml;charset=utf-8", result)
}
func newInterfacePlainTextHandler(funcName string, result []byte) func(w http.ResponseWriter, r *http.Request) {
return newInterfaceHandler(funcName, "text/plain", result)
}
func newInterfacePlainTextHandlerString(funcName, result string) func(w http.ResponseWriter, r *http.Request) {
return newInterfacePlainTextHandler(funcName, []byte(result))
}
func newInterfaceHandler(funcName, contentType string, result []byte) func(w http.ResponseWriter, r *http.Request) {
next := newHandler(contentType, result)
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("func") != funcName {
w.WriteHeader(http.StatusNotFound)
return
}
next(w, r)
}
}
func newPlainTextHandler(result []byte) func(w http.ResponseWriter, r *http.Request) {
return newHandler("text/plain", result)
}
func newPlainTextHandlerString(result string) func(w http.ResponseWriter, r *http.Request) {
return newPlainTextHandler([]byte(result))
}
func newHandler(contentType string, result []byte) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
if r.Header.Get("User-Agent") != testUserAgent {
w.WriteHeader(http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", contentType)
_, _ = w.Write(result)
}
}