diff --git a/.golangci.yml b/.golangci.yml index 86a9a4f..a36aaba 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -109,3 +109,6 @@ issues: - linters: - lll source: "^// @Param" + - path: internal/domain/(server_config|unit_info|opponents_defeated|building_info).go # TODO: https://github.com/dominikh/go-tools/issues/1360 needs to be released + linters: + - unused diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 831b6f7..c3b9470 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,6 +6,6 @@ repos: stages: [commit-msg] additional_dependencies: ['@commitlint/config-conventional'] - repo: https://github.com/golangci/golangci-lint - rev: v1.51.0 + rev: v1.51.1 hooks: - id: golangci-lint diff --git a/internal/bundb/player.go b/internal/bundb/player.go index b0f3632..16ea78c 100644 --- a/internal/bundb/player.go +++ b/internal/bundb/player.go @@ -162,6 +162,10 @@ func (l listPlayersParamsApplier) applyFilters(q *bun.SelectQuery) *bun.SelectQu q = q.Where("player.server_key IN (?)", bun.In(l.params.ServerKeys)) } + if l.params.TribeID > 0 { + q = q.Where("player.tribe_id = ?", l.params.TribeID) + } + if l.params.Deleted.Valid { if l.params.Deleted.Bool { q = q.Where("player.deleted_at IS NOT NULL") diff --git a/internal/bundb/player_test.go b/internal/bundb/player_test.go index 626336f..24d43d4 100644 --- a/internal/bundb/player_test.go +++ b/internal/bundb/player_test.go @@ -212,6 +212,7 @@ func TestPlayer_List_ListCountWithRelations(t *testing.T) { fixture := loadFixtures(t, db) repo := bundb.NewPlayer(db) players := fixture.players(t) + tribeCSA := fixture.tribe(t, "pl169-csa") type expectedPlayer struct { id int64 @@ -222,6 +223,7 @@ func TestPlayer_List_ListCountWithRelations(t *testing.T) { //nolint:prealloc var playersIT70 []expectedPlayer var playersPL []expectedPlayer + var playersPL169CSA []expectedPlayer var playersExceptPL169 []expectedPlayer for _, p := range players { allPlayers = append(allPlayers, expectedPlayer{ @@ -236,6 +238,13 @@ func TestPlayer_List_ListCountWithRelations(t *testing.T) { }) } + if p.ServerKey == "pl169" && p.TribeID == tribeCSA.ID { + playersPL169CSA = append(playersPL169CSA, expectedPlayer{ + id: p.ID, + serverKey: p.ServerKey, + }) + } + if fixture.server(t, p.ServerKey).VersionCode == "pl" { playersPL = append(playersPL, expectedPlayer{ id: p.ID, @@ -572,6 +581,16 @@ func TestPlayer_List_ListCountWithRelations(t *testing.T) { }, expectedCount: 2, }, + { + name: "ServerKey=[pl169],TribeID=28,Sort=[{By=ID,Direction=ASC}]", + params: domain.ListPlayersParams{ + ServerKeys: []string{"pl169"}, + TribeID: tribeCSA.ID, + Sort: []domain.PlayerSort{{By: domain.PlayerSortByID, Direction: domain.SortDirectionASC}}, + }, + expectedPlayers: playersPL169CSA, + expectedCount: int64(len(playersPL169CSA)), + }, { name: "VersionCodes=[pl],Sort=[{By=ID,Direction=ASC}]", params: domain.ListPlayersParams{ diff --git a/internal/domain/player.go b/internal/domain/player.go index 3aba32e..ea8396e 100644 --- a/internal/domain/player.go +++ b/internal/domain/player.go @@ -137,6 +137,7 @@ type ListPlayersParams struct { ServerKeysNIN []string VersionCodes []string Deleted NullBool + TribeID int64 Pagination Pagination Sort []PlayerSort } diff --git a/internal/router/rest/player.go b/internal/router/rest/player.go index 2a1b308..8095863 100644 --- a/internal/router/rest/player.go +++ b/internal/router/rest/player.go @@ -142,6 +142,56 @@ func (p *player) listOtherServers(w http.ResponseWriter, r *http.Request) { renderJSON(w, http.StatusOK, model.NewListPlayerOtherServersResp(players)) } +// @ID listTribeMembers +// @Summary List tribe members +// @Description List all tribe members +// @Tags versions,servers,players,tribes +// @Produce json +// @Success 200 {object} model.ListPlayersResp +// @Success 400 {object} model.ErrorResp +// @Success 404 {object} model.ErrorResp +// @Failure 500 {object} model.ErrorResp +// @Header 200 {integer} X-Total-Count "Total number of records" +// @Param versionCode path string true "Version code" +// @Param serverKey path string true "Server key" +// @Param tribeId path integer true "Tribe ID" +// @Param offset query int false "specifies where to start a page" minimum(0) default(0) +// @Param limit query int false "page size" minimum(1) maximum(200) default(200) +// @Param sort query []string false "format: field:direction, default: [id:asc]" Enums(id:asc,id:desc,scoreAtt:asc,scoreAtt:desc,scoreDef:asc,scoreDef:desc,scoreSup:asc,scoreSup:desc,scoreTotal:asc,scoreTotal:desc,points:asc,points:desc,deletedAt:asc,deletedAt:desc) +// @Router /versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}/members [get] +func (p *player) listTribeMembers(w http.ResponseWriter, r *http.Request) { + var err error + ctx := r.Context() + srv, _ := serverFromContext(ctx) + trb, _ := tribeFromContext(ctx) + params := domain.ListPlayersParams{ + ServerKeys: []string{srv.Key}, + TribeID: trb.ID, + } + query := queryParams[domain.PlayerSort]{r.URL.Query()} + + params.Pagination, err = query.pagination(playerDefaultOffset, playerDefaultLimit) + if err != nil { + renderErr(w, err) + return + } + + params.Sort, err = query.sort(domain.NewPlayerSort) + if err != nil { + renderErr(w, err) + return + } + + players, count, err := p.svc.ListCountWithRelations(ctx, params) + if err != nil { + renderErr(w, err) + return + } + + setTotalCountHeader(w.Header(), count) + renderJSON(w, http.StatusOK, model.NewListPlayersResp(players)) +} + func playerMiddleware(svc PlayerService) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/router/rest/player_test.go b/internal/router/rest/player_test.go index bb5b84c..feee81b 100644 --- a/internal/router/rest/player_test.go +++ b/internal/router/rest/player_test.go @@ -905,7 +905,7 @@ func TestPlayer_listOtherServers(t *testing.T) { setup func(versionSvc *mock.FakeVersionService, serverSvc *mock.FakeServerService, playerSvc *mock.FakePlayerService) versionCode string serverKey string - id string + playerID string queryParams url.Values expectedStatus int target any @@ -1017,7 +1017,7 @@ func TestPlayer_listOtherServers(t *testing.T) { }, versionCode: version.Code, serverKey: serverPL151.Key, - id: playerIdStr, + playerID: playerIdStr, expectedStatus: http.StatusOK, target: &model.ListPlayerOtherServersResp{}, expectedResponse: &model.ListPlayerOtherServersResp{ @@ -1161,7 +1161,7 @@ func TestPlayer_listOtherServers(t *testing.T) { }, versionCode: version.Code, serverKey: serverPL151.Key, - id: playerIdStr, + playerID: playerIdStr, queryParams: url.Values{ "limit": []string{"1"}, "offset": []string{"1"}, @@ -1221,7 +1221,7 @@ func TestPlayer_listOtherServers(t *testing.T) { }, versionCode: version.Code, serverKey: serverPL151.Key, - id: playerIdStr, + playerID: playerIdStr, queryParams: url.Values{ "limit": []string{"asd"}, }, @@ -1244,7 +1244,7 @@ func TestPlayer_listOtherServers(t *testing.T) { }, versionCode: version.Code, serverKey: serverPL151.Key, - id: playerIdStr, + playerID: playerIdStr, queryParams: url.Values{ "offset": []string{"asd"}, }, @@ -1265,7 +1265,7 @@ func TestPlayer_listOtherServers(t *testing.T) { }, versionCode: version.Code + "2", serverKey: serverPL151.Key, - id: playerIdStr, + playerID: playerIdStr, expectedStatus: http.StatusNotFound, target: &model.ErrorResp{}, expectedResponse: &model.ErrorResp{ @@ -1284,7 +1284,7 @@ func TestPlayer_listOtherServers(t *testing.T) { }, versionCode: version.Code, serverKey: serverPL151.Key + "2", - id: playerIdStr, + playerID: playerIdStr, expectedStatus: http.StatusNotFound, target: &model.ErrorResp{}, expectedResponse: &model.ErrorResp{ @@ -1304,7 +1304,7 @@ func TestPlayer_listOtherServers(t *testing.T) { }, versionCode: version.Code, serverKey: serverPL151.Key, - id: "12345551", + playerID: "12345551", expectedStatus: http.StatusNotFound, target: &model.ErrorResp{}, expectedResponse: &model.ErrorResp{ @@ -1337,7 +1337,636 @@ func TestPlayer_listOtherServers(t *testing.T) { "/v1/versions/%s/servers/%s/players/%s/other-servers?%s", tt.versionCode, tt.serverKey, - tt.id, + tt.playerID, + tt.queryParams.Encode(), + ) + resp := doRequest(router, http.MethodGet, target, nil) + defer resp.Body.Close() + assertTotalCount(t, resp.Header, tt.expectedTotalCount) + assertJSONResponse(t, resp, tt.expectedStatus, tt.expectedResponse, tt.target) + }) + } +} + +func TestPlayer_listTribeMembers(t *testing.T) { + t.Parallel() + + now := time.Now() + version := domain.Version{ + Code: "pl", + Name: "Poland", + Host: "plemiona.pl", + Timezone: "Europe/Warsaw", + } + server := domain.Server{ + Key: "pl151", + URL: "https://pl151.plemiona.pl", + Open: true, + Special: false, + VersionCode: "pl", + } + tribe := domain.Tribe{ + ID: 124, + Name: "tribe 124", + Tag: "tag 124", + ProfileURL: "tribe-124", + } + tribeID := strconv.FormatInt(tribe.ID, 10) + + tests := []struct { + name string + setup func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) + versionCode string + serverKey string + tribeID string + queryParams url.Values + expectedStatus int + target any + expectedResponse any + expectedTotalCount string + }{ + { + name: "OK: without params", + setup: func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) { + versionSvc.GetByCodeReturns(version, nil) + serverSvc.GetNormalByVersionCodeAndKeyReturns(server, nil) + tribeSvc.GetByServerKeyAndIDReturns(tribe, nil) + playerSvc.ListCountWithRelationsCalls(func( + _ context.Context, + params domain.ListPlayersParams, + ) ([]domain.PlayerWithRelations, int64, error) { + expectedParams := domain.ListPlayersParams{ + ServerKeys: []string{server.Key}, + TribeID: tribe.ID, + Pagination: domain.Pagination{ + Limit: 200, + Offset: 0, + }, + } + + if diff := cmp.Diff(params, expectedParams); diff != "" { + return nil, 0, fmt.Errorf("validation failed: %s", diff) + } + + players := []domain.PlayerWithRelations{ + { + Player: domain.Player{ + OpponentsDefeated: domain.OpponentsDefeated{ + RankAtt: 8, + ScoreAtt: 7, + RankDef: 6, + ScoreDef: 5, + RankSup: 4, + ScoreSup: 3, + RankTotal: 2, + ScoreTotal: 1, + }, + ID: 997, + Name: "name 997", + NumVillages: 5, + Points: 4, + Rank: 2, + TribeID: tribe.ID, + ProfileURL: "profile-997", + BestRank: 111, + BestRankAt: now, + MostPoints: 1112, + MostPointsAt: now.Add(-5 * time.Minute), + MostVillages: 1113, + MostVillagesAt: now.Add(time.Minute), + LastActivityAt: now.Add(time.Second), + ServerKey: server.Key, + CreatedAt: now, + }, + Tribe: domain.NullTribeMeta{ + Valid: true, + Tribe: domain.TribeMeta{ + ID: tribe.ID, + Name: tribe.Name, + Tag: tribe.Tag, + ProfileURL: tribe.ProfileURL, + }, + }, + }, + { + Player: domain.Player{ + OpponentsDefeated: domain.OpponentsDefeated{ + RankAtt: 1, + ScoreAtt: 2, + RankDef: 3, + ScoreDef: 4, + RankSup: 5, + ScoreSup: 6, + RankTotal: 7, + ScoreTotal: 8, + }, + ID: 998, + Name: "name 998", + NumVillages: 2, + Points: 3, + Rank: 4, + TribeID: tribe.ID, + ProfileURL: "profile-998", + BestRank: 1117, + BestRankAt: now, + MostPoints: 1115, + MostPointsAt: now.Add(-5 * time.Second), + MostVillages: 1114, + MostVillagesAt: now.Add(time.Hour), + LastActivityAt: now.Add(time.Minute), + ServerKey: server.Key, + CreatedAt: now, + }, + Tribe: domain.NullTribeMeta{ + Valid: true, + Tribe: domain.TribeMeta{ + ID: tribe.ID, + Name: tribe.Name, + Tag: tribe.Tag, + ProfileURL: tribe.ProfileURL, + }, + }, + }, + } + return players, int64(len(players)), nil + }) + }, + versionCode: version.Code, + serverKey: server.Key, + tribeID: tribeID, + expectedStatus: http.StatusOK, + target: &model.ListPlayersResp{}, + expectedResponse: &model.ListPlayersResp{ + Data: []model.Player{ + { + RankAtt: 8, + ScoreAtt: 7, + RankDef: 6, + ScoreDef: 5, + RankSup: 4, + ScoreSup: 3, + RankTotal: 2, + ScoreTotal: 1, + ID: 997, + Name: "name 997", + NumVillages: 5, + Points: 4, + Rank: 2, + ProfileURL: "profile-997", + BestRank: 111, + BestRankAt: now, + MostPoints: 1112, + MostPointsAt: now.Add(-5 * time.Minute), + MostVillages: 1113, + MostVillagesAt: now.Add(time.Minute), + LastActivityAt: now.Add(time.Second), + Tribe: model.NullTribeMeta{ + Valid: true, + Tribe: model.TribeMeta{ + ID: tribe.ID, + Name: tribe.Name, + Tag: tribe.Tag, + ProfileURL: tribe.ProfileURL, + }, + }, + CreatedAt: now, + }, + { + RankAtt: 1, + ScoreAtt: 2, + RankDef: 3, + ScoreDef: 4, + RankSup: 5, + ScoreSup: 6, + RankTotal: 7, + ScoreTotal: 8, + ID: 998, + Name: "name 998", + NumVillages: 2, + Points: 3, + Rank: 4, + ProfileURL: "profile-998", + BestRank: 1117, + BestRankAt: now, + MostPoints: 1115, + MostPointsAt: now.Add(-5 * time.Second), + MostVillages: 1114, + MostVillagesAt: now.Add(time.Hour), + LastActivityAt: now.Add(time.Minute), + Tribe: model.NullTribeMeta{ + Valid: true, + Tribe: model.TribeMeta{ + ID: tribe.ID, + Name: tribe.Name, + Tag: tribe.Tag, + ProfileURL: tribe.ProfileURL, + }, + }, + CreatedAt: now, + }, + }, + }, + expectedTotalCount: "2", + }, + { + name: "OK: limit=1,sort=[id:asc,points:desc,scoreTotal:asc]", + setup: func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) { + versionSvc.GetByCodeReturns(version, nil) + serverSvc.GetNormalByVersionCodeAndKeyReturns(server, nil) + tribeSvc.GetByServerKeyAndIDReturns(tribe, nil) + playerSvc.ListCountWithRelationsCalls(func( + _ context.Context, + params domain.ListPlayersParams, + ) ([]domain.PlayerWithRelations, int64, error) { + expectedParams := domain.ListPlayersParams{ + ServerKeys: []string{server.Key}, + Pagination: domain.Pagination{ + Limit: 1, + Offset: 0, + }, + TribeID: tribe.ID, + Sort: []domain.PlayerSort{ + {By: domain.PlayerSortByID, Direction: domain.SortDirectionASC}, + {By: domain.PlayerSortByPoints, Direction: domain.SortDirectionDESC}, + {By: domain.PlayerSortByScoreTotal, Direction: domain.SortDirectionASC}, + }, + } + + if diff := cmp.Diff(params, expectedParams); diff != "" { + return nil, 0, fmt.Errorf("validation failed: %s", diff) + } + + players := []domain.PlayerWithRelations{ + { + Player: domain.Player{ + OpponentsDefeated: domain.OpponentsDefeated{ + RankAtt: 1, + ScoreAtt: 2, + RankDef: 3, + ScoreDef: 4, + RankSup: 5, + ScoreSup: 6, + RankTotal: 7, + ScoreTotal: 8, + }, + ID: 998, + Name: "name 998", + NumVillages: 2, + Points: 3, + Rank: 4, + TribeID: 0, + ProfileURL: "profile-998", + BestRank: 1117, + BestRankAt: now, + MostPoints: 1115, + MostPointsAt: now.Add(-5 * time.Second), + MostVillages: 1114, + MostVillagesAt: now.Add(time.Hour), + LastActivityAt: now.Add(time.Minute), + ServerKey: server.Key, + CreatedAt: now, + }, + Tribe: domain.NullTribeMeta{ + Valid: true, + Tribe: domain.TribeMeta{ + ID: tribe.ID, + Name: tribe.Name, + Tag: tribe.Tag, + ProfileURL: tribe.ProfileURL, + }, + }, + }, + } + return players, int64(len(players)) + 49, nil + }) + }, + versionCode: version.Code, + serverKey: server.Key, + tribeID: tribeID, + queryParams: url.Values{ + "limit": []string{"1"}, + "sort": []string{"id:asc", "points:desc", "scoreTotal:asc"}, + }, + expectedStatus: http.StatusOK, + target: &model.ListPlayersResp{}, + expectedResponse: &model.ListPlayersResp{ + Data: []model.Player{ + { + RankAtt: 1, + ScoreAtt: 2, + RankDef: 3, + ScoreDef: 4, + RankSup: 5, + ScoreSup: 6, + RankTotal: 7, + ScoreTotal: 8, + ID: 998, + Name: "name 998", + NumVillages: 2, + Points: 3, + Rank: 4, + ProfileURL: "profile-998", + BestRank: 1117, + BestRankAt: now, + MostPoints: 1115, + MostPointsAt: now.Add(-5 * time.Second), + MostVillages: 1114, + MostVillagesAt: now.Add(time.Hour), + LastActivityAt: now.Add(time.Minute), + Tribe: model.NullTribeMeta{ + Valid: true, + Tribe: model.TribeMeta{ + ID: tribe.ID, + Name: tribe.Name, + Tag: tribe.Tag, + ProfileURL: tribe.ProfileURL, + }, + }, + CreatedAt: now, + }, + }, + }, + expectedTotalCount: "50", + }, + { + name: "ERR: limit is not a valid int32", + setup: func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) { + versionSvc.GetByCodeReturns(version, nil) + serverSvc.GetNormalByVersionCodeAndKeyReturns(server, nil) + tribeSvc.GetByServerKeyAndIDReturns(tribe, nil) + }, + versionCode: version.Code, + serverKey: server.Key, + tribeID: tribeID, + queryParams: url.Values{ + "limit": []string{"asd"}, + }, + expectedStatus: http.StatusBadRequest, + target: &model.ErrorResp{}, + expectedResponse: &model.ErrorResp{ + Error: model.APIError{ + Code: domain.ErrorCodeValidationError.String(), + Message: "limit: strconv.ParseInt: parsing \"asd\": invalid syntax", + }, + }, + expectedTotalCount: "", + }, + { + name: "ERR: offset is not a valid int32", + setup: func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) { + versionSvc.GetByCodeReturns(version, nil) + serverSvc.GetNormalByVersionCodeAndKeyReturns(server, nil) + tribeSvc.GetByServerKeyAndIDReturns(tribe, nil) + }, + versionCode: version.Code, + serverKey: server.Key, + tribeID: tribeID, + queryParams: url.Values{ + "offset": []string{"asd"}, + }, + expectedStatus: http.StatusBadRequest, + target: &model.ErrorResp{}, + expectedResponse: &model.ErrorResp{ + Error: model.APIError{ + Code: domain.ErrorCodeValidationError.String(), + Message: "offset: strconv.ParseInt: parsing \"asd\": invalid syntax", + }, + }, + expectedTotalCount: "", + }, + { + name: "ERR: sort - invalid format", + setup: func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) { + versionSvc.GetByCodeReturns(version, nil) + serverSvc.GetNormalByVersionCodeAndKeyReturns(server, nil) + tribeSvc.GetByServerKeyAndIDReturns(tribe, nil) + }, + versionCode: version.Code, + serverKey: server.Key, + tribeID: tribeID, + queryParams: url.Values{ + "sort": []string{"id:asc", "test"}, + }, + expectedStatus: http.StatusBadRequest, + target: &model.ErrorResp{}, + expectedResponse: &model.ErrorResp{ + Error: model.APIError{ + Code: domain.ErrorCodeValidationError.String(), + Message: "sort[1]: parsing \"test\": invalid syntax, expected field:direction", + }, + }, + expectedTotalCount: "", + }, + { + name: "ERR: sort - unsupported field", + setup: func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) { + versionSvc.GetByCodeReturns(version, nil) + serverSvc.GetNormalByVersionCodeAndKeyReturns(server, nil) + tribeSvc.GetByServerKeyAndIDReturns(tribe, nil) + }, + versionCode: version.Code, + serverKey: server.Key, + tribeID: tribeID, + queryParams: url.Values{ + "sort": []string{"id:asc", "test:asc"}, + }, + expectedStatus: http.StatusBadRequest, + target: &model.ErrorResp{}, + expectedResponse: &model.ErrorResp{ + Error: model.APIError{ + Code: domain.ErrorCodeValidationError.String(), + Message: "sort[1]: unsupported sort by: \"test\"", + }, + }, + expectedTotalCount: "", + }, + { + name: "ERR: sort - unsupported direction", + setup: func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) { + versionSvc.GetByCodeReturns(version, nil) + serverSvc.GetNormalByVersionCodeAndKeyReturns(server, nil) + tribeSvc.GetByServerKeyAndIDReturns(tribe, nil) + }, + versionCode: version.Code, + serverKey: server.Key, + tribeID: tribeID, + queryParams: url.Values{ + "sort": []string{"id:asc2"}, + }, + expectedStatus: http.StatusBadRequest, + target: &model.ErrorResp{}, + expectedResponse: &model.ErrorResp{ + Error: model.APIError{ + Code: domain.ErrorCodeValidationError.String(), + Message: "sort[0]: unsupported sort direction: \"asc2\"", + }, + }, + expectedTotalCount: "", + }, + { + name: "ERR: tribe id is not a valid int64", + setup: func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) { + versionSvc.GetByCodeReturns(version, nil) + serverSvc.GetNormalByVersionCodeAndKeyReturns(server, nil) + }, + versionCode: version.Code, + serverKey: server.Key, + tribeID: "asdf", + expectedStatus: http.StatusBadRequest, + target: &model.ErrorResp{}, + expectedResponse: &model.ErrorResp{ + Error: model.APIError{ + Code: domain.ErrorCodeValidationError.String(), + Message: "tribeId: strconv.ParseInt: parsing \"asdf\": invalid syntax", + }, + }, + expectedTotalCount: "", + }, + { + name: "ERR: version not found", + setup: func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) { + versionSvc.GetByCodeReturns(domain.Version{}, domain.VersionNotFoundError{VerCode: version.Code + "2"}) + }, + versionCode: version.Code + "2", + serverKey: server.Key, + tribeID: tribeID, + expectedStatus: http.StatusNotFound, + target: &model.ErrorResp{}, + expectedResponse: &model.ErrorResp{ + Error: model.APIError{ + Code: domain.ErrorCodeEntityNotFound.String(), + Message: fmt.Sprintf("version (code=%s) not found", version.Code+"2"), + }, + }, + expectedTotalCount: "", + }, + { + name: "ERR: server not found", + setup: func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) { + versionSvc.GetByCodeReturns(version, nil) + serverSvc.GetNormalByVersionCodeAndKeyReturns(domain.Server{}, domain.ServerNotFoundError{Key: server.Key + "2"}) + }, + versionCode: version.Code, + serverKey: server.Key + "2", + tribeID: tribeID, + expectedStatus: http.StatusNotFound, + target: &model.ErrorResp{}, + expectedResponse: &model.ErrorResp{ + Error: model.APIError{ + Code: domain.ErrorCodeEntityNotFound.String(), + Message: fmt.Sprintf("server (key=%s) not found", server.Key+"2"), + }, + }, + expectedTotalCount: "", + }, + { + name: "ERR: tribe not found", + setup: func( + versionSvc *mock.FakeVersionService, + serverSvc *mock.FakeServerService, + tribeSvc *mock.FakeTribeService, + playerSvc *mock.FakePlayerService, + ) { + versionSvc.GetByCodeReturns(version, nil) + serverSvc.GetNormalByVersionCodeAndKeyReturns(domain.Server{}, nil) + tribeSvc.GetByServerKeyAndIDCalls(func(_ context.Context, _ string, id int64) (domain.Tribe, error) { + return domain.Tribe{}, domain.TribeNotFoundError{ID: id} + }) + }, + versionCode: version.Code, + serverKey: server.Key, + tribeID: tribeID + "2", + expectedStatus: http.StatusNotFound, + target: &model.ErrorResp{}, + expectedResponse: &model.ErrorResp{ + Error: model.APIError{ + Code: domain.ErrorCodeEntityNotFound.String(), + Message: fmt.Sprintf("tribe (id=%s) not found", tribeID+"2"), + }, + }, + expectedTotalCount: "", + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + versionSvc := &mock.FakeVersionService{} + serverSvc := &mock.FakeServerService{} + playerSvc := &mock.FakePlayerService{} + tribeSvc := &mock.FakeTribeService{} + tt.setup(versionSvc, serverSvc, tribeSvc, playerSvc) + + router := newRouter( + withVersionService(versionSvc), + withPlayerService(playerSvc), + withServerService(serverSvc), + withTribeService(tribeSvc), + ) + + target := fmt.Sprintf( + "/v1/versions/%s/servers/%s/tribes/%s/members?%s", + tt.versionCode, + tt.serverKey, + tt.tribeID, tt.queryParams.Encode(), ) resp := doRequest(router, http.MethodGet, target, nil) diff --git a/internal/router/rest/rest.go b/internal/router/rest/rest.go index 92ab52e..1ee77ee 100644 --- a/internal/router/rest/rest.go +++ b/internal/router/rest/rest.go @@ -100,6 +100,7 @@ func New( r.Get("/", tribeHandler.list) r.With(tribeMiddleware(tribeSvc)).Route("/{tribeId}", func(r chi.Router) { r.Get("/", tribeHandler.getByID) + r.Get("/members", playerHandler.listTribeMembers) r.Get("/ennoblements", ennoblementHandler.listTribe) }) })