From 56dc421bd0282bf40c198ef59defda3fc2146edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Sun, 26 Feb 2023 08:42:02 +0100 Subject: [PATCH] refactor(tw): improve test quality & refactor ParseError --- internal/tw/client.go | 76 ++- internal/tw/client_test.go | 1057 ++++++++++++++++++------------- internal/tw/parse_error.go | 37 +- internal/tw/parse_error_test.go | 22 + 4 files changed, 684 insertions(+), 508 deletions(-) create mode 100644 internal/tw/parse_error_test.go diff --git a/internal/tw/client.go b/internal/tw/client.go index 72b7a3e..7d0ce28 100644 --- a/internal/tw/client.go +++ b/internal/tw/client.go @@ -126,7 +126,7 @@ func (c *Client) GetServerConfig(ctx context.Context, baseURLRaw string) (Server var cfg ServerConfig - if err := c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryConfig), &cfg); err != nil { + if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryConfig), &cfg); err != nil { return ServerConfig{}, err } @@ -141,7 +141,7 @@ func (c *Client) GetBuildingInfo(ctx context.Context, baseURLRaw string) (Buildi var info BuildingInfo - if err := c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryBuildingInfo), &info); err != nil { + if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryBuildingInfo), &info); err != nil { return BuildingInfo{}, err } @@ -156,7 +156,7 @@ func (c *Client) GetUnitInfo(ctx context.Context, baseURLRaw string) (UnitInfo, var info UnitInfo - if err := c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryUnitInfo), &info); err != nil { + if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryUnitInfo), &info); err != nil { return UnitInfo{}, err } @@ -373,10 +373,12 @@ func (t tribeCSVParser) parse() ([]Tribe, error) { if err != nil { return nil, err } + tribe, err := t.parseRecord(rec) if err != nil { return nil, err } + tribes = append(tribes, tribe) } @@ -393,42 +395,42 @@ func (t tribeCSVParser) parseRecord(record []string) (Tribe, error) { tribe.ID, err = strconv.ParseInt(record[0], 10, 64) if err != nil { - return Tribe{}, NewParseError(err, record, "tribe.ID") + return Tribe{}, ParseError{Err: err, Str: record[0], Field: "Tribe.ID"} } tribe.Name, err = urlpkg.QueryUnescape(record[1]) if err != nil { - return Tribe{}, NewParseError(err, record, "tribe.Name") + return Tribe{}, ParseError{Err: err, Str: record[1], Field: "Tribe.Name"} } tribe.Tag, err = urlpkg.QueryUnescape(record[2]) if err != nil { - return Tribe{}, NewParseError(err, record, "tribe.Tag") + return Tribe{}, ParseError{Err: err, Str: record[2], Field: "Tribe.Tag"} } tribe.NumMembers, err = strconv.ParseInt(record[3], 10, 64) if err != nil { - return Tribe{}, NewParseError(err, record, "tribe.NumMembers") + return Tribe{}, ParseError{Err: err, Str: record[3], Field: "Tribe.NumMembers"} } tribe.NumVillages, err = strconv.ParseInt(record[4], 10, 64) if err != nil { - return Tribe{}, NewParseError(err, record, "tribe.NumVillages") + return Tribe{}, ParseError{Err: err, Str: record[4], Field: "Tribe.NumVillages"} } tribe.Points, err = strconv.ParseInt(record[5], 10, 64) if err != nil { - return Tribe{}, NewParseError(err, record, "tribe.Points") + return Tribe{}, ParseError{Err: err, Str: record[5], Field: "Tribe.Points"} } tribe.AllPoints, err = strconv.ParseInt(record[6], 10, 64) if err != nil { - return Tribe{}, NewParseError(err, record, "tribe.AllPoints") + return Tribe{}, ParseError{Err: err, Str: record[6], Field: "Tribe.AllPoints"} } tribe.Rank, err = strconv.ParseInt(record[7], 10, 64) if err != nil { - return Tribe{}, NewParseError(err, record, "tribe.Rank") + return Tribe{}, ParseError{Err: err, Str: record[7], Field: "Tribe.Rank"} } tribe.OpponentsDefeated = t.od[tribe.ID] @@ -456,10 +458,12 @@ func (p playerCSVParser) parse() ([]Player, error) { if err != nil { return nil, err } + player, err := p.parseRecord(rec) if err != nil { return nil, err } + players = append(players, player) } @@ -476,32 +480,32 @@ func (p playerCSVParser) parseRecord(record []string) (Player, error) { player.ID, err = strconv.ParseInt(record[0], 10, 64) if err != nil { - return Player{}, NewParseError(err, record, "player.ID") + return Player{}, ParseError{Err: err, Str: record[0], Field: "Player.ID"} } player.Name, err = urlpkg.QueryUnescape(record[1]) if err != nil { - return Player{}, NewParseError(err, record, "player.Name") + return Player{}, ParseError{Err: err, Str: record[1], Field: "Player.Name"} } player.TribeID, err = strconv.ParseInt(record[2], 10, 64) if err != nil { - return Player{}, NewParseError(err, record, "player.TribeID") + return Player{}, ParseError{Err: err, Str: record[2], Field: "Player.TribeID"} } player.NumVillages, err = strconv.ParseInt(record[3], 10, 64) if err != nil { - return Player{}, NewParseError(err, record, "player.NumVillages") + return Player{}, ParseError{Err: err, Str: record[3], Field: "Player.NumVillages"} } player.Points, err = strconv.ParseInt(record[4], 10, 64) if err != nil { - return Player{}, NewParseError(err, record, "player.Points") + return Player{}, ParseError{Err: err, Str: record[4], Field: "Player.Points"} } player.Rank, err = strconv.ParseInt(record[5], 10, 64) if err != nil { - return Player{}, NewParseError(err, record, "player.Rank") + return Player{}, ParseError{Err: err, Str: record[5], Field: "Player.Rank"} } player.OpponentsDefeated = p.od[player.ID] @@ -548,37 +552,37 @@ func (v villageCSVParser) parseRecord(record []string) (Village, error) { village.ID, err = strconv.ParseInt(record[0], 10, 64) if err != nil { - return Village{}, NewParseError(err, record, "village.ID") + return Village{}, ParseError{Err: err, Str: record[0], Field: "Village.ID"} } village.Name, err = urlpkg.QueryUnescape(record[1]) if err != nil { - return Village{}, NewParseError(err, record, "village.Name") + return Village{}, ParseError{Err: err, Str: record[1], Field: "Village.Name"} } village.X, err = strconv.ParseInt(record[2], 10, 64) if err != nil { - return Village{}, NewParseError(err, record, "village.X") + return Village{}, ParseError{Err: err, Str: record[2], Field: "Village.X"} } village.Y, err = strconv.ParseInt(record[3], 10, 64) if err != nil { - return Village{}, NewParseError(err, record, "village.Y") + return Village{}, ParseError{Err: err, Str: record[3], Field: "Village.Y"} } village.PlayerID, err = strconv.ParseInt(record[4], 10, 64) if err != nil { - return Village{}, NewParseError(err, record, "village.PlayerID") + return Village{}, ParseError{Err: err, Str: record[4], Field: "Village.PlayerID"} } village.Points, err = strconv.ParseInt(record[5], 10, 64) if err != nil { - return Village{}, NewParseError(err, record, "village.Points") + return Village{}, ParseError{Err: err, Str: record[5], Field: "Village.Points"} } village.Bonus, err = strconv.ParseInt(record[6], 10, 64) if err != nil { - return Village{}, NewParseError(err, record, "village.Bonus") + return Village{}, ParseError{Err: err, Str: record[6], Field: "Village.Bonus"} } village.Continent = "K" + string(record[3][0]) + string(record[2][0]) @@ -610,10 +614,12 @@ func (o odCSVParser) parse() ([]odRecord, error) { if err != nil { return nil, err } + od, err := o.parseRecord(rec) if err != nil { return nil, err } + odRecords = append(odRecords, od) } @@ -626,17 +632,17 @@ func (o odCSVParser) parseRecord(record []string) (odRecord, error) { rec.Rank, err = strconv.ParseInt(record[0], 10, 64) if err != nil { - return odRecord{}, NewParseError(err, record, "odRecord.Rank") + return odRecord{}, ParseError{Err: err, Str: record[0], Field: "odRecord.Rank"} } rec.ID, err = strconv.ParseInt(record[1], 10, 64) if err != nil { - return odRecord{}, NewParseError(err, record, "odRecord.ID") + return odRecord{}, ParseError{Err: err, Str: record[1], Field: "odRecord.ID"} } rec.Score, err = strconv.ParseInt(record[2], 10, 64) if err != nil { - return odRecord{}, NewParseError(err, record, "odRecord.Score") + return odRecord{}, ParseError{Err: err, Str: record[2], Field: "odRecord.Score"} } return rec, nil @@ -664,9 +670,11 @@ func (e ennoblementCSVParser) parse() ([]Ennoblement, error) { if err != nil { return nil, err } + if ennoblement.CreatedAt.Before(e.since) { continue } + ennoblements = append(ennoblements, ennoblement) } @@ -683,37 +691,37 @@ func (e ennoblementCSVParser) parseRecord(record []string) (Ennoblement, error) ennoblement.VillageID, err = strconv.ParseInt(record[0], 10, 64) if err != nil { - return Ennoblement{}, NewParseError(err, record, "ennoblement.VillageID") + return Ennoblement{}, ParseError{Err: err, Str: record[0], Field: "Ennoblement.VillageID"} } ennoblement.CreatedAt, err = e.parseTimestamp(record[1]) if err != nil { - return Ennoblement{}, NewParseError(err, record, "ennoblement.CreatedAt") + return Ennoblement{}, ParseError{Err: err, Str: record[1], Field: "Ennoblement.CreatedAt"} } ennoblement.NewOwnerID, err = strconv.ParseInt(record[2], 10, 64) if err != nil { - return Ennoblement{}, NewParseError(err, record, "ennoblement.NewOwnerID") + return Ennoblement{}, ParseError{Err: err, Str: record[2], Field: "Ennoblement.NewOwnerID"} } ennoblement.OldOwnerID, err = strconv.ParseInt(record[3], 10, 64) if err != nil { - return Ennoblement{}, NewParseError(err, record, "ennoblement.OldOwnerID") + return Ennoblement{}, ParseError{Err: err, Str: record[3], Field: "Ennoblement.OldOwnerID"} } ennoblement.OldTribeID, err = strconv.ParseInt(record[4], 10, 64) if err != nil { - return Ennoblement{}, NewParseError(err, record, "ennoblement.OldTribeID") + return Ennoblement{}, ParseError{Err: err, Str: record[4], Field: "Ennoblement.OldTribeID"} } ennoblement.NewTribeID, err = strconv.ParseInt(record[5], 10, 64) if err != nil { - return Ennoblement{}, NewParseError(err, record, "ennoblement.NewTribeID") + return Ennoblement{}, ParseError{Err: err, Str: record[5], Field: "Ennoblement.NewTribeID"} } ennoblement.Points, err = strconv.ParseInt(record[6], 10, 64) if err != nil { - return Ennoblement{}, NewParseError(err, record, "ennoblement.Points") + return Ennoblement{}, ParseError{Err: err, Str: record[6], Field: "Ennoblement.Points"} } return ennoblement, nil diff --git a/internal/tw/client_test.go b/internal/tw/client_test.go index 8f202c1..7ae8245 100644 --- a/internal/tw/client_test.go +++ b/internal/tw/client_test.go @@ -12,8 +12,6 @@ import ( "testing" "time" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" "gitea.dwysokinski.me/twhelp/core/internal/tw" @@ -561,7 +559,7 @@ func TestClient_GetTribes(t *testing.T) { respODA string respODD string checkErr func(err error) bool - expectedTribes []tw.Tribe + expectedTribes func(url string) []tw.Tribe }{ { name: "OK", @@ -569,212 +567,218 @@ func TestClient_GetTribes(t *testing.T) { 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: []tw.Tribe{ - { - ID: 1, - Name: "name 1", - Tag: "tag 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, + expectedTribes: func(url string) []tw.Tribe { + return []tw.Tribe{ + { + ID: 1, + Name: "name 1", + Tag: "tag 1", + ProfileURL: url + "/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", - 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: 2, + Name: "name2", + Tag: "tag2", + ProfileURL: url + "/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", - 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: 3, + Name: "name3", + Tag: "tag3", + ProfileURL: url + "/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", - NumMembers: 400, - NumVillages: 404, - Points: 402, - AllPoints: 403, - Rank: 404, - OpponentsDefeated: tw.OpponentsDefeated{}, - }, + { + ID: 4, + Name: "name4", + Tag: "tag4", + ProfileURL: url + "/game.php?screen=info_ally&id=4", + NumMembers: 400, + NumVillages: 404, + Points: 402, + AllPoints: 403, + Rank: 404, + OpponentsDefeated: tw.OpponentsDefeated{}, + }, + } }, }, { - name: "ERR: invalid value - OD.Rank", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - OD.ID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - OD.Score", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODA.Rank", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODA.ID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODA.Score", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODD.Rank", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODD.ID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODD.Score", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: incorrect number of fields 1", + 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: incorrect number of fields 2", + 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: invalid value - Tribe.ID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Tribe.ID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Tribe.NumMembers", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Tribe.NumMembers" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Tribe.NumVillages", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Tribe.NumVillages" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Tribe.Points", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Tribe.Points" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Tribe.AllPoints", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Tribe.AllPoints" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Tribe.Rank", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Tribe.Rank" && errors.Is(parseErr, strconv.ErrSyntax) }, }, } @@ -797,25 +801,21 @@ func TestClient_GetTribes(t *testing.T) { tribes, err := newTestClient(srv.Client()).GetTribes(context.Background(), srv.URL) checkErr := func(err error) bool { - return err == nil + return errors.Is(err, nil) } if tt.checkErr != nil { checkErr = tt.checkErr } + var expectedTribes []tw.Tribe + if tt.expectedTribes != nil { + expectedTribes = tt.expectedTribes(srv.URL) + } + assert.True(t, checkErr(err)) - require.Len(t, tribes, len(tt.expectedTribes)) + require.Len(t, tribes, len(expectedTribes)) for i, tribe := range tribes { - assert.Empty(t, cmp.Diff( - tt.expectedTribes[i], - tribe, - cmpopts.IgnoreFields(tw.Tribe{}, "ProfileURL"), - )) - assert.Equal( - t, - fmt.Sprintf("%s/game.php?screen=info_ally&id=%d", srv.URL, tribe.ID), - tribe.ProfileURL, - ) + assert.Equal(t, expectedTribes[i], tribe) } }) } @@ -832,7 +832,7 @@ func TestClient_GetPlayers(t *testing.T) { respODD string respODS string checkErr func(err error) bool - expectedPlayers []tw.Player + expectedPlayers func(url string) []tw.Player }{ { name: "OK", @@ -841,220 +841,226 @@ func TestClient_GetPlayers(t *testing.T) { 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: []tw.Player{ - { - ID: 1, - Name: "name 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, + expectedPlayers: func(url string) []tw.Player { + return []tw.Player{ + { + ID: 1, + Name: "name 1", + ProfileURL: url + "/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", - 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: 2, + Name: "name2", + ProfileURL: url + "/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", - 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: 3, + Name: "name3", + ProfileURL: url + "/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", - TribeID: 456, - NumVillages: 457, - Points: 458, - Rank: 459, - OpponentsDefeated: tw.OpponentsDefeated{}, - }, + { + ID: 4, + Name: "name4", + ProfileURL: url + "/game.php?screen=info_player&id=4", + TribeID: 456, + NumVillages: 457, + Points: 458, + Rank: 459, + OpponentsDefeated: tw.OpponentsDefeated{}, + }, + } }, }, { - name: "ERR: invalid value - OD.Rank", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - OD.ID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - OD.Score", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODA.Rank", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODA.ID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODA.Score", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODD.Rank", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODD.ID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODD.Score", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODS.Rank", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Rank" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODS.ID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.ID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - ODS.Score", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "odRecord.Score" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: incorrect number of fields 1", + 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: incorrect number of fields 2", + 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: invalid value - Player.ID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Player.ID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Player.TribeID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Player.TribeID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Player.NumVillages", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Player.NumVillages" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Player.Points", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Player.Points" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Player.Rank", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Player.Rank" && errors.Is(parseErr, strconv.ErrSyntax) }, }, } @@ -1078,25 +1084,21 @@ func TestClient_GetPlayers(t *testing.T) { players, err := newTestClient(srv.Client()).GetPlayers(context.Background(), srv.URL) checkErr := func(err error) bool { - return err == nil + return errors.Is(err, nil) } if tt.checkErr != nil { checkErr = tt.checkErr } + var expectedPlayers []tw.Player + if tt.expectedPlayers != nil { + expectedPlayers = tt.expectedPlayers(srv.URL) + } + assert.True(t, checkErr(err)) - require.Len(t, players, len(tt.expectedPlayers)) + require.Len(t, players, len(expectedPlayers)) for i, player := range players { - assert.Empty(t, cmp.Diff( - tt.expectedPlayers[i], - player, - cmpopts.IgnoreFields(tw.Player{}, "ProfileURL"), - )) - assert.Equal( - t, - fmt.Sprintf("%s/game.php?screen=info_player&id=%d", srv.URL, player.ID), - player.ProfileURL, - ) + assert.Equal(t, expectedPlayers[i], player) } }) } @@ -1109,94 +1111,98 @@ func TestClient_GetVillages(t *testing.T) { name string resp string checkErr func(err error) bool - expectedVillages []tw.Village + expectedVillages func(url string) []tw.Village }{ { name: "OK", resp: "123,village%201,500,501,502,503,504\n124,village 2,100,201,102,103,104", - expectedVillages: []tw.Village{ - { - ID: 123, - Name: "village 1", - X: 500, - Y: 501, - Continent: "K55", - PlayerID: 502, - Points: 503, - Bonus: 504, - }, - { - ID: 124, - Name: "village 2", - X: 100, - Y: 201, - Continent: "K21", - PlayerID: 102, - Points: 103, - Bonus: 104, - }, + expectedVillages: func(url string) []tw.Village { + return []tw.Village{ + { + ID: 123, + Name: "village 1", + ProfileURL: url + "/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: url + "/game.php?screen=info_village&id=124", + X: 100, + Y: 201, + Continent: "K21", + PlayerID: 102, + Points: 103, + Bonus: 104, + }, + } }, }, { - name: "ERR: incorrect number of fields 1", + 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: incorrect number of fields 2", + 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: invalid value - Village.ID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Village.ID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Village.X", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Village.X" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Village.Y", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Village.Y" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Village.PlayerID", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Village.PlayerID" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Village.Points", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Village.Points" && errors.Is(parseErr, strconv.ErrSyntax) }, }, { - name: "ERR: invalid value - Village.Bonus", + 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) + return errors.As(err, &parseErr) && parseErr.Field == "Village.Bonus" && errors.Is(parseErr, strconv.ErrSyntax) }, }, } @@ -1216,25 +1222,21 @@ func TestClient_GetVillages(t *testing.T) { villages, err := newTestClient(srv.Client()).GetVillages(context.Background(), srv.URL) checkErr := func(err error) bool { - return err == nil + return errors.Is(err, nil) } if tt.checkErr != nil { checkErr = tt.checkErr } + var expectedVillages []tw.Village + if tt.expectedVillages != nil { + expectedVillages = tt.expectedVillages(srv.URL) + } + assert.True(t, checkErr(err)) - require.Len(t, villages, len(tt.expectedVillages)) + require.Len(t, villages, len(expectedVillages)) for i, village := range villages { - assert.Empty(t, cmp.Diff( - tt.expectedVillages[i], - village, - cmpopts.IgnoreFields(tw.Village{}, "ProfileURL"), - )) - assert.Equal( - t, - fmt.Sprintf("%s/game.php?screen=info_village&id=%d", srv.URL, village.ID), - village.ProfileURL, - ) + assert.Equal(t, expectedVillages[i], village) } }) } @@ -1243,195 +1245,366 @@ func TestClient_GetVillages(t *testing.T) { func TestClient_GetEnnoblements(t *testing.T) { t.Parallel() - now := time.Now() - tests := []struct { - name string - since time.Time - resp string - checkErr func(err error) bool - expectedEnnoblements []tw.Ennoblement - }{ - { - name: "OK: /map/conquer_extended.txt", - 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), + 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: "OK: /interface.php?func=get_conquer_extended", - 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: "ERR: too few fields", + resp: "1424,1657842406,698953084,699589674,0,122", + checkErr: func(err error) bool { + return errors.Is(err, csv.ErrFieldCount) }, }, - }, - { - name: "ERR: incorrect number of fields 1", - 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: incorrect number of fields 2", - 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: invalid value - Ennoblement.VillageID", - 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: invalid value - Ennoblement.VillageID", - 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: invalid value - Ennoblement.NewOwnerID", - 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: invalid value - Ennoblement.OldOwnerID", - 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: invalid value - Ennoblement.OldTribeID", - 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: invalid value - Ennoblement.NewTribeID", - 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) + }, }, - }, - { - name: "ERR: invalid value - Ennoblement.Points", - 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 { - tt := tt + for _, tt := range tests { + tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() + t.Run(tt.name, func(t *testing.T) { + t.Parallel() - mux := http.NewServeMux() - mux.HandleFunc("/interface.php", newInterfacePlainTextHandlerString("get_conquer_extended", tt.resp)) - mux.HandleFunc("/map/conquer_extended.txt", newPlainTextHandlerString(tt.resp)) + mux := http.NewServeMux() + mux.HandleFunc("/map/conquer_extended.txt", newPlainTextHandlerString(tt.resp)) - srv := httptest.NewTLSServer(mux) - t.Cleanup(srv.Close) + srv := httptest.NewTLSServer(mux) + t.Cleanup(srv.Close) - ennoblements, err := newTestClient(srv.Client()).GetEnnoblements(context.Background(), srv.URL, tt.since) + ennoblements, err := newTestClient(srv.Client()).GetEnnoblements(context.Background(), srv.URL, tt.since) - checkErr := func(err error) bool { - return errors.Is(err, nil) - } - if tt.checkErr != nil { - checkErr = tt.checkErr - } + 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) - } - }) - } + 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 { + tt := tt + + 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) + + ennoblements, err := newTestClient(srv.Client(), tt.clientOpts...).GetEnnoblements(context.Background(), 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 newTestClient(hc *http.Client) *tw.Client { - return tw.NewClient(tw.WithHTTPClient(hc), tw.WithUserAgent(testUserAgent)) +// user agent and http client can't be overridden via opts +func newTestClient(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) { diff --git a/internal/tw/parse_error.go b/internal/tw/parse_error.go index fae38a3..ee919e5 100644 --- a/internal/tw/parse_error.go +++ b/internal/tw/parse_error.go @@ -1,42 +1,15 @@ package tw -import ( - "strings" -) - -const ( - recordLenLimit = 100 -) - type ParseError struct { - record []string - field string - err error + Field string + Str string + Err error } -func NewParseError(err error, record []string, field string) ParseError { - return ParseError{ - err: err, - record: record, - field: field, - } -} func (e ParseError) Error() string { - recordStr := strings.Join(e.record, ",") - if len(recordStr) > recordLenLimit { - recordStr = recordStr[0:recordLenLimit-3] + "..." - } - return "parse error (field=" + e.field + " record=" + recordStr + "): " + e.err.Error() -} - -func (e ParseError) Field() string { - return e.field -} - -func (e ParseError) Record() []string { - return e.record + return e.Field + ": parsing \"" + e.Str + "\": " + e.Err.Error() } func (e ParseError) Unwrap() error { - return e.err + return e.Err } diff --git a/internal/tw/parse_error_test.go b/internal/tw/parse_error_test.go new file mode 100644 index 0000000..e67ace2 --- /dev/null +++ b/internal/tw/parse_error_test.go @@ -0,0 +1,22 @@ +package tw_test + +import ( + "errors" + "fmt" + "testing" + + "gitea.dwysokinski.me/twhelp/core/internal/tw" + "github.com/stretchr/testify/assert" +) + +func TestParseError(t *testing.T) { + t.Parallel() + + err := tw.ParseError{ + Field: "test", + Str: "anything", + Err: errors.New("err"), + } + assert.Equal(t, fmt.Sprintf("%s: parsing \"%s\": %s", err.Field, err.Str, err.Err.Error()), err.Error()) + assert.ErrorIs(t, err, err.Err) +}