From 45dd0d4a9e90a0b9fcba531fe753769e64429d9e Mon Sep 17 00:00:00 2001 From: Vladimir Pestov Date: Sun, 13 Oct 2024 14:59:24 +0500 Subject: [PATCH 1/4] QUESTSPACE-7: Add max_teams_amount to quest --- src/docs/docs.go | 9 ++++ src/docs/swagger.json | 9 ++++ src/docs/swagger.yaml | 6 +++ .../V0018__AddQuestMaxTeamsAmount.sql | 1 + src/internal/pgdb/pgclient/quest.go | 49 ++++++++++++++----- src/pkg/storage/models.go | 1 + src/pkg/storage/requests.go | 2 + 7 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 src/internal/pgdb/migrations/V0018__AddQuestMaxTeamsAmount.sql diff --git a/src/docs/docs.go b/src/docs/docs.go index 7eb5cb7..5435468 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -1885,6 +1885,9 @@ const docTemplate = `{ "max_team_cap": { "type": "integer" }, + "max_teams_amount": { + "type": "integer" + }, "media_link": { "type": "string" }, @@ -2025,6 +2028,9 @@ const docTemplate = `{ "max_team_cap": { "type": "integer" }, + "max_teams_amount": { + "type": "integer" + }, "media_link": { "type": "string" }, @@ -2245,6 +2251,9 @@ const docTemplate = `{ "max_team_cap": { "type": "integer" }, + "max_teams_amount": { + "type": "integer" + }, "media_link": { "type": "string" }, diff --git a/src/docs/swagger.json b/src/docs/swagger.json index e7fdff1..a66ec37 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -1874,6 +1874,9 @@ "max_team_cap": { "type": "integer" }, + "max_teams_amount": { + "type": "integer" + }, "media_link": { "type": "string" }, @@ -2014,6 +2017,9 @@ "max_team_cap": { "type": "integer" }, + "max_teams_amount": { + "type": "integer" + }, "media_link": { "type": "string" }, @@ -2234,6 +2240,9 @@ "max_team_cap": { "type": "integer" }, + "max_teams_amount": { + "type": "integer" + }, "media_link": { "type": "string" }, diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 809ccb8..c1c50fe 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -338,6 +338,8 @@ definitions: type: boolean max_team_cap: type: integer + max_teams_amount: + type: integer media_link: type: string name: @@ -431,6 +433,8 @@ definitions: type: string max_team_cap: type: integer + max_teams_amount: + type: integer media_link: type: string name: @@ -578,6 +582,8 @@ definitions: type: boolean max_team_cap: type: integer + max_teams_amount: + type: integer media_link: type: string name: diff --git a/src/internal/pgdb/migrations/V0018__AddQuestMaxTeamsAmount.sql b/src/internal/pgdb/migrations/V0018__AddQuestMaxTeamsAmount.sql new file mode 100644 index 0000000..b9580d2 --- /dev/null +++ b/src/internal/pgdb/migrations/V0018__AddQuestMaxTeamsAmount.sql @@ -0,0 +1 @@ +ALTER TABLE questspace.quest ADD COLUMN max_teams_amount integer DEFAULT NULL; diff --git a/src/internal/pgdb/pgclient/quest.go b/src/internal/pgdb/pgclient/quest.go index 81f374c..1c4f7bf 100644 --- a/src/internal/pgdb/pgclient/quest.go +++ b/src/internal/pgdb/pgclient/quest.go @@ -43,6 +43,10 @@ func (c *Client) CreateQuest(ctx context.Context, req *storage.CreateQuestReques values = append(values, *req.MaxTeamCap) query = query.Columns("max_team_cap") } + if req.MaxTeamsAmount != nil { + values = append(values, *req.MaxTeamsAmount) + query = query.Columns("max_teams_amount") + } row := query.Values(values...).RunWith(c.runner).QueryRowContext(ctx) quest := storage.Quest{ @@ -61,6 +65,7 @@ func (c *Client) CreateQuest(ctx context.Context, req *storage.CreateQuestReques MaxTeamCap: req.MaxTeamCap, HasBrief: req.HasBrief, Brief: req.Brief, + MaxTeamsAmount: req.MaxTeamsAmount, } if err := row.Scan(&quest.ID); err != nil { return nil, xerrors.Errorf("scan row: %w", err) @@ -82,6 +87,7 @@ SELECT q.finished, q.has_brief, q.brief, + q.max_teams_amount, u.id, u.username, u.avatar_url @@ -110,6 +116,7 @@ func (c *Client) GetQuest(ctx context.Context, req *storage.GetQuestRequest) (*s &finished, &q.HasBrief, &q.Brief, + &q.MaxTeamsAmount, &userId, &creatorName, &userAvatarURL, @@ -161,9 +168,19 @@ func (c *Client) addOwnedQuestsCond(query sq.SelectBuilder, userID storage.ID) s func (c *Client) GetQuests(ctx context.Context, req *storage.GetQuestsRequest) (*storage.GetQuestsResponse, error) { query := sq.Select( - "q.id", "q.name", "q.description", "q.access", "q.registration_deadline", - "q.start_time", "q.finish_time", "q.media_link", "q.max_team_cap", "q.finished", - "q.creator_id", "u.username", "u.avatar_url", + "q.id", + "q.name", + "q.description", + "q.access", + "q.registration_deadline", + "q.start_time", + "q.finish_time", + "q.media_link", + "q.max_team_cap", + "q.finished", + "q.creator_id", + "u.username", + "u.avatar_url", ).From("questspace.quest q").LeftJoin("questspace.user u ON q.creator_id = u.id"). OrderBy("q.finished", "q.start_time"). Limit(uint64(req.PageSize)). @@ -256,7 +273,8 @@ func (c *Client) UpdateQuest(ctx context.Context, req *storage.UpdateQuestReques max_team_cap, finished, has_brief, - brief`). + brief, + max_teams_amount`). PlaceholderFormat(sq.Dollar) if len(req.Name) > 0 { query = query.Set("name", req.Name) @@ -288,6 +306,9 @@ func (c *Client) UpdateQuest(ctx context.Context, req *storage.UpdateQuestReques if req.Brief != nil { query = query.Set("brief", *req.Brief) } + if req.MaxTeamsAmount != nil { + query = query.Set("max_teams_amount", *req.MaxTeamsAmount) + } row := query.RunWith(c.runner).QueryRowContext(ctx) var ( @@ -309,6 +330,7 @@ func (c *Client) UpdateQuest(ctx context.Context, req *storage.UpdateQuestReques &finished, &q.HasBrief, &q.Brief, + &q.MaxTeamsAmount, ); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, storage.ErrNotFound @@ -326,23 +348,24 @@ func (c *Client) UpdateQuest(ctx context.Context, req *storage.UpdateQuestReques } func (c *Client) DeleteQuest(ctx context.Context, req *storage.DeleteQuestRequest) error { - query := sq.Delete("questspace.quest"). - Where(sq.Eq{"id": req.ID}). - PlaceholderFormat(sq.Dollar) + query := ` + DELETE FROM questspace.quest + WHERE id = $1 +` - if _, err := query.RunWith(c.runner).ExecContext(ctx); err != nil { + if _, err := c.runner.ExecContext(ctx, query, req.ID); err != nil { return xerrors.Errorf("delete quest: %w", err) } return nil } func (c *Client) FinishQuest(ctx context.Context, req *storage.FinishQuestRequest) error { - query := sq.Update("questspace.quest"). - Set("finished", true). - Where(sq.Eq{"id": req.ID}). - PlaceholderFormat(sq.Dollar) + query := ` + UPDATE questspace.quest SET finished = true + WHERE id = $1 +` - if _, err := query.RunWith(c.runner).ExecContext(ctx); err != nil { + if _, err := c.runner.ExecContext(ctx, query, req.ID); err != nil { return xerrors.Errorf("exec query: %w", err) } return nil diff --git a/src/pkg/storage/models.go b/src/pkg/storage/models.go index 9bdffde..2bbe2f0 100644 --- a/src/pkg/storage/models.go +++ b/src/pkg/storage/models.go @@ -119,6 +119,7 @@ type Quest struct { Status QuestStatus `json:"status"` HasBrief bool `json:"has_brief,omitempty"` Brief string `json:"brief,omitempty"` + MaxTeamsAmount *int `json:"max_teams_amount,omitempty"` } type GetQuestType int diff --git a/src/pkg/storage/requests.go b/src/pkg/storage/requests.go index 4ad42e6..95234a5 100644 --- a/src/pkg/storage/requests.go +++ b/src/pkg/storage/requests.go @@ -44,6 +44,7 @@ type CreateQuestRequest struct { MaxTeamCap *int `json:"max_team_cap,omitempty"` HasBrief bool `json:"has_brief,omitempty"` Brief string `json:"brief,omitempty"` + MaxTeamsAmount *int `json:"max_teams_amount,omitempty"` } type GetQuestRequest struct { @@ -74,6 +75,7 @@ type UpdateQuestRequest struct { MaxTeamCap *int `json:"max_team_cap,omitempty"` HasBrief *bool `json:"has_brief,omitempty"` Brief *string `json:"brief,omitempty"` + MaxTeamsAmount *int `json:"max_teams_amount,omitempty"` } type DeleteQuestRequest struct { From 2cbf61a039de87c9237a9a26e4fa747cbaafa8a9 Mon Sep 17 00:00:00 2001 From: Vladimir Pestov Date: Sun, 13 Oct 2024 18:33:21 +0500 Subject: [PATCH 2/4] QUESTSPACE-7: Add option to accept teams manually --- src/cmd/questspace/main.go | 1 + src/internal/handlers/play/play.go | 3 + src/internal/handlers/quest/quest.go | 2 +- src/internal/handlers/teams/teams.go | 50 ++++- .../V0019__AddRegistrationsToQuestAndTeam.sql | 7 + .../pgdb/pgclient/answer_hint_test.go | 7 +- src/internal/pgdb/pgclient/quest.go | 17 +- src/internal/pgdb/pgclient/team.go | 182 +++++++++++++----- src/internal/pgdb/pgclient/team_test.go | 16 +- src/internal/qtime/time.go | 6 +- src/internal/questspace/game/game.go | 10 +- src/internal/questspace/teams/service.go | 76 +++++++- src/internal/questspace/teams/service_test.go | 21 +- src/pkg/storage/interfaces.go | 1 + src/pkg/storage/mocks/client.go | 28 +++ src/pkg/storage/models.go | 62 +++--- src/pkg/storage/requests.go | 62 +++--- 17 files changed, 429 insertions(+), 122 deletions(-) create mode 100644 src/internal/pgdb/migrations/V0019__AddRegistrationsToQuestAndTeam.sql diff --git a/src/cmd/questspace/main.go b/src/cmd/questspace/main.go index 5142900..d76db1c 100644 --- a/src/cmd/questspace/main.go +++ b/src/cmd/questspace/main.go @@ -133,6 +133,7 @@ func InitApp(ctx context.Context, application *app.App) error { r.H().Use(jwt.AuthMiddlewareStrict(jwtParser)).POST("/teams/all/:id/captain", transport.WrapCtxErr(teamsHandler.HandleChangeLeader)) r.H().Use(jwt.AuthMiddlewareStrict(jwtParser)).POST("/teams/all/:id/leave", transport.WrapCtxErr(teamsHandler.HandleLeave)) r.H().Use(jwt.AuthMiddlewareStrict(jwtParser)).DELETE("/teams/all/:id/:user_id", transport.WrapCtxErr(teamsHandler.HandleRemoveUser)) + r.H().Use(jwt.AuthMiddlewareStrict(jwtParser)).POST("/quest/:id/teams/:team_id/accept", transport.WrapCtxErr(teamsHandler.HandleAcceptTeam)) taskGroupHandler := taskgroups.NewHandler(clientFactory, &taskMediaValidator) r.H().Use(jwt.AuthMiddlewareStrict(jwtParser)).PATCH("/quest/:id/task-groups/bulk", transport.WrapCtxErr(taskGroupHandler.HandleBulkUpdate)) diff --git a/src/internal/handlers/play/play.go b/src/internal/handlers/play/play.go index 5343652..b7ddac1 100644 --- a/src/internal/handlers/play/play.go +++ b/src/internal/handlers/play/play.go @@ -83,6 +83,9 @@ func (h *Handler) HandleGet(ctx context.Context, w http.ResponseWriter, r *http. } return xerrors.Errorf("get team: %w", err) } + if team.RegistrationStatus != storage.RegistrationStatusAccepted { + return httperrors.New(http.StatusForbidden, "only accepted teams can view game mode") + } resp := game.AnswerDataResponse{ Quest: quest, Team: team, diff --git a/src/internal/handlers/quest/quest.go b/src/internal/handlers/quest/quest.go index 9bedfb5..9d6282f 100644 --- a/src/internal/handlers/quest/quest.go +++ b/src/internal/handlers/quest/quest.go @@ -140,7 +140,7 @@ func (h *Handler) HandleGet(ctx context.Context, w http.ResponseWriter, r *http. resp.Team = team } - resp.AllTeams, err = s.GetTeams(ctx, &storage.GetTeamsRequest{QuestIDs: []storage.ID{questID}}) + resp.AllTeams, err = s.GetTeams(ctx, &storage.GetTeamsRequest{QuestIDs: []storage.ID{questID}, AcceptedOnly: true}) if err != nil { return xerrors.Errorf("get teams: %w", err) } diff --git a/src/internal/handlers/teams/teams.go b/src/internal/handlers/teams/teams.go index 0108498..27dc1f4 100644 --- a/src/internal/handlers/teams/teams.go +++ b/src/internal/handlers/teams/teams.go @@ -156,12 +156,16 @@ func (h *Handler) HandleGet(ctx context.Context, w http.ResponseWriter, r *http. return nil } +type ManyTeamsResponse struct { + Teams []storage.Team `json:"teams"` +} + // HandleGetMany handles GET /quest/:id/teams request // // @Summary Get all teams by quest id // @Tags Teams // @Param quest_id path string true "Quest id" -// @Success 200 {object} []storage.Team +// @Success 200 {object} ManyTeamsResponse // @Failure 400 // @Router /quest/{quest_id}/teams [get] func (h *Handler) HandleGetMany(ctx context.Context, w http.ResponseWriter, r *http.Request) error { @@ -180,7 +184,7 @@ func (h *Handler) HandleGetMany(ctx context.Context, w http.ResponseWriter, r *h return xerrors.Errorf("get teams of quest %q: %w", questID, err) } - if err = transport.ServeJSONResponse(w, http.StatusOK, questTeams); err != nil { + if err = transport.ServeJSONResponse(w, http.StatusOK, ManyTeamsResponse{Teams: questTeams}); err != nil { return err } return nil @@ -457,3 +461,45 @@ func (h *Handler) HandleGetQuestByTeamInvite(ctx context.Context, w http.Respons } return nil } + +// HandleAcceptTeam handles POST /quest/:id/teams/:team_id/accept request +// +// @Summary Accept team +// @Tags Teams +// @Param quest_id path string true "Quest id" +// @Param team_id path string true "Team id" +// @Success 200 {object} ManyTeamsResponse +// @Failure 404 +// @Failure 406 +// @Router /quest/{quest_id}/teams/{team_id}/accept [post] +// @Security ApiKeyAuth +func (h *Handler) HandleAcceptTeam(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + questID, err := transport.UUIDParam(r, "id") + if err != nil { + return err + } + teamID, err := transport.UUIDParam(r, "team_id") + if err != nil { + return err + } + uauth, err := jwt.GetUserFromContext(ctx) + if err != nil { + return err + } + s, err := h.factory.NewStorage(ctx, dbnode.Alive) + if err != nil { + return xerrors.Errorf("get storage: %w", err) + } + teamService := teams.NewService(s, h.inviteLinkPrefix) + teams, err := teamService.AcceptTeam(ctx, uauth, questID, teamID) + if err != nil { + return err + } + resp := ManyTeamsResponse{ + Teams: teams, + } + if err = transport.ServeJSONResponse(w, http.StatusOK, resp); err != nil { + return err + } + return nil +} diff --git a/src/internal/pgdb/migrations/V0019__AddRegistrationsToQuestAndTeam.sql b/src/internal/pgdb/migrations/V0019__AddRegistrationsToQuestAndTeam.sql new file mode 100644 index 0000000..3cd5fce --- /dev/null +++ b/src/internal/pgdb/migrations/V0019__AddRegistrationsToQuestAndTeam.sql @@ -0,0 +1,7 @@ +CREATE TYPE questspace.registration_type AS ENUM ('AUTO', 'VERIFY'); + +ALTER TABLE questspace.quest ADD COLUMN registration_type questspace.registration_type NOT NULL DEFAULT 'AUTO'; + +CREATE TYPE questspace.registration_status AS ENUM ('ON_CONSIDERATION', 'ACCEPTED'); + +ALTER TABLE questspace.team ADD COLUMN registration_status questspace.registration_status NOT NULL DEFAULT 'ACCEPTED'; diff --git a/src/internal/pgdb/pgclient/answer_hint_test.go b/src/internal/pgdb/pgclient/answer_hint_test.go index a838ba0..81ab138 100644 --- a/src/internal/pgdb/pgclient/answer_hint_test.go +++ b/src/internal/pgdb/pgclient/answer_hint_test.go @@ -21,9 +21,10 @@ func createTestTeam(t *testing.T, ctx context.Context, client *Client, quest *st require.NoError(t, err) teamReq := storage.CreateTeamRequest{ - QuestID: quest.ID, - Name: teamName, - Creator: user, + QuestID: quest.ID, + Name: teamName, + Creator: user, + RegistrationStatus: storage.RegistrationStatusAccepted, } team, err := client.CreateTeam(ctx, &teamReq) require.NoError(t, err) diff --git a/src/internal/pgdb/pgclient/quest.go b/src/internal/pgdb/pgclient/quest.go index 1c4f7bf..802d6e9 100644 --- a/src/internal/pgdb/pgclient/quest.go +++ b/src/internal/pgdb/pgclient/quest.go @@ -47,6 +47,10 @@ func (c *Client) CreateQuest(ctx context.Context, req *storage.CreateQuestReques values = append(values, *req.MaxTeamsAmount) query = query.Columns("max_teams_amount") } + if req.RegistrationType != storage.RegistrationUnspecified { + values = append(values, req.RegistrationType) + query = query.Columns("registration_type") + } row := query.Values(values...).RunWith(c.runner).QueryRowContext(ctx) quest := storage.Quest{ @@ -66,10 +70,14 @@ func (c *Client) CreateQuest(ctx context.Context, req *storage.CreateQuestReques HasBrief: req.HasBrief, Brief: req.Brief, MaxTeamsAmount: req.MaxTeamsAmount, + RegistrationType: req.RegistrationType, } if err := row.Scan(&quest.ID); err != nil { return nil, xerrors.Errorf("scan row: %w", err) } + if quest.RegistrationType == storage.RegistrationUnspecified { + quest.RegistrationType = storage.RegistrationAuto + } return &quest, nil } @@ -88,6 +96,7 @@ SELECT q.has_brief, q.brief, q.max_teams_amount, + q.registration_type, u.id, u.username, u.avatar_url @@ -117,6 +126,7 @@ func (c *Client) GetQuest(ctx context.Context, req *storage.GetQuestRequest) (*s &q.HasBrief, &q.Brief, &q.MaxTeamsAmount, + &q.RegistrationType, &userId, &creatorName, &userAvatarURL, @@ -274,7 +284,8 @@ func (c *Client) UpdateQuest(ctx context.Context, req *storage.UpdateQuestReques finished, has_brief, brief, - max_teams_amount`). + max_teams_amount, + registration_type`). PlaceholderFormat(sq.Dollar) if len(req.Name) > 0 { query = query.Set("name", req.Name) @@ -309,6 +320,9 @@ func (c *Client) UpdateQuest(ctx context.Context, req *storage.UpdateQuestReques if req.MaxTeamsAmount != nil { query = query.Set("max_teams_amount", *req.MaxTeamsAmount) } + if req.RegistrationType != storage.RegistrationUnspecified { + query = query.Set("registration_type", req.RegistrationType) + } row := query.RunWith(c.runner).QueryRowContext(ctx) var ( @@ -331,6 +345,7 @@ func (c *Client) UpdateQuest(ctx context.Context, req *storage.UpdateQuestReques &q.HasBrief, &q.Brief, &q.MaxTeamsAmount, + &q.RegistrationType, ); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, storage.ErrNotFound diff --git a/src/internal/pgdb/pgclient/team.go b/src/internal/pgdb/pgclient/team.go index 75bd1f7..e5c9f24 100644 --- a/src/internal/pgdb/pgclient/team.go +++ b/src/internal/pgdb/pgclient/team.go @@ -15,18 +15,25 @@ import ( const createTeamQuery = ` WITH created_team AS ( - INSERT INTO questspace.team (name, quest_id, cap_id, time_created) VALUES ($1, $2, $3, $4) - RETURNING id, name, link_id, cap_id -) SELECT t.id, t.name, t.link_id, u.id, u.username, u.avatar_url + INSERT INTO questspace.team (name, quest_id, cap_id, registration_status, time_created) VALUES ($1, $2, $3, $4, $5) + RETURNING id, name, link_id, registration_status, cap_id +) SELECT t.id, t.name, t.link_id, t.registration_status, u.id, u.username, u.avatar_url FROM created_team t LEFT JOIN questspace.user u ON t.cap_id = u.id ` func (c *Client) CreateTeam(ctx context.Context, req *storage.CreateTeamRequest) (*storage.Team, error) { - sqlQuery := sq.Expr(createTeamQuery, req.Name, req.QuestID, req.Creator.ID, qtime.Now()) + row := c.runner.QueryRowContext(ctx, createTeamQuery, req.Name, req.QuestID, req.Creator.ID, req.RegistrationStatus, qtime.Now()) - row := sq.QueryRowContextWith(ctx, c.runner, sqlQuery) team := &storage.Team{Captain: &storage.User{}} - if err := row.Scan(&team.ID, &team.Name, &team.InviteLinkID, &team.Captain.ID, &team.Captain.Username, &team.Captain.AvatarURL); err != nil { + if err := row.Scan( + &team.ID, + &team.Name, + &team.InviteLinkID, + &team.RegistrationStatus, + &team.Captain.ID, + &team.Captain.Username, + &team.Captain.AvatarURL, + ); err != nil { if pgErr := new(pgconn.PgError); errors.As(err, &pgErr) && pgErr.Code == uniqueViolationCode { return nil, storage.ErrExists } @@ -37,13 +44,12 @@ func (c *Client) CreateTeam(ctx context.Context, req *storage.CreateTeamRequest) } func (c *Client) getTeamMembers(ctx context.Context, teamID storage.ID) ([]storage.User, error) { - query := sq.Select("u.id", "u.username", "u.avatar_url"). - From("questspace.user u"). - LeftJoin("questspace.registration r ON r.user_id = u.id"). - Where(sq.Eq{"r.team_id": teamID}). - PlaceholderFormat(sq.Dollar) - - rows, err := query.RunWith(c.runner).QueryContext(ctx) + query := ` + SELECT u.id, u.username, u.avatar_url + FROM questspace.user u LEFT JOIN questspace.registration r ON r.user_id = u.id + WHERE r.team_id = $1 +` + rows, err := c.runner.QueryContext(ctx, query, teamID) if err != nil { return nil, xerrors.Errorf("query rows: %w", err) } @@ -52,7 +58,11 @@ func (c *Client) getTeamMembers(ctx context.Context, teamID storage.ID) ([]stora var members []storage.User for rows.Next() { var member storage.User - if err := rows.Scan(&member.ID, &member.Username, &member.AvatarURL); err != nil { + if err := rows.Scan( + &member.ID, + &member.Username, + &member.AvatarURL, + ); err != nil { return nil, xerrors.Errorf("scan row: %w", err) } members = append(members, member) @@ -64,7 +74,21 @@ func (c *Client) getTeamMembers(ctx context.Context, teamID storage.ID) ([]stora } func (c *Client) GetTeam(ctx context.Context, req *storage.GetTeamRequest) (*storage.Team, error) { - query := sq.Select("t.id", "t.name", "t.invite_path", "t.score", "q.id", "q.max_team_cap", "u.id", "u.username", "u.avatar_url", "cr.id"). + query := sq.Select( + "t.id", + "t.name", + "t.invite_path", + "t.score", + "t.registration_status", + "q.id", + "q.max_team_cap", + "q.max_teams_amount", + "q.registration_type", + "u.id", + "u.username", + "u.avatar_url", + "cr.id", + ). From("questspace.team t"). LeftJoin("questspace.quest q ON q.id = t.quest_id"). LeftJoin("questspace.user u ON t.cap_id = u.id"). @@ -77,7 +101,10 @@ func (c *Client) GetTeam(ctx context.Context, req *storage.GetTeamRequest) (*sto } else if req.UserRegistration != nil { query = query.Where( sq.Expr( - "t.id = (SELECT re.team_id FROM questspace.registration re LEFT JOIN questspace.team te ON te.id = re.team_id WHERE re.user_id = ? AND te.quest_id = ?)", + `t.id = ( + SELECT re.team_id FROM questspace.registration re LEFT JOIN questspace.team te ON te.id = re.team_id + WHERE re.user_id = ? AND te.quest_id = ? + )`, req.UserRegistration.UserID, req.UserRegistration.QuestID, ), @@ -88,7 +115,21 @@ func (c *Client) GetTeam(ctx context.Context, req *storage.GetTeamRequest) (*sto row := query.RunWith(c.runner).QueryRowContext(ctx) team := &storage.Team{Quest: &storage.Quest{Creator: &storage.User{}}, Captain: &storage.User{}} - if err := row.Scan(&team.ID, &team.Name, &team.InviteLink, &team.Score, &team.Quest.ID, &team.Quest.MaxTeamCap, &team.Captain.ID, &team.Captain.Username, &team.Captain.AvatarURL, &team.Quest.Creator.ID); err != nil { + if err := row.Scan( + &team.ID, + &team.Name, + &team.InviteLink, + &team.Score, + &team.RegistrationStatus, + &team.Quest.ID, + &team.Quest.MaxTeamCap, + &team.Quest.MaxTeamsAmount, + &team.Quest.RegistrationType, + &team.Captain.ID, + &team.Captain.Username, + &team.Captain.AvatarURL, + &team.Quest.Creator.ID, + ); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, storage.ErrNotFound } @@ -105,7 +146,14 @@ func (c *Client) GetTeam(ctx context.Context, req *storage.GetTeamRequest) (*sto } func (c *Client) GetTeams(ctx context.Context, req *storage.GetTeamsRequest) ([]storage.Team, error) { - query := sq.Select("t.id", "t.name", "u.id", "u.username", "u.avatar_url"). + query := sq.Select( + "t.id", + "t.name", + "t.registration_status", + "u.id", + "u.username", + "u.avatar_url", + ). From("questspace.team t"). LeftJoin("questspace.user u ON t.cap_id = u.id"). OrderBy("t.time_created, t.name ASC"). @@ -118,6 +166,9 @@ func (c *Client) GetTeams(ctx context.Context, req *storage.GetTeamsRequest) ([] if len(req.QuestIDs) > 0 { query = query.Where(sq.Eq{"t.quest_id": req.QuestIDs}) } + if req.AcceptedOnly { + query = query.Where(sq.Eq{"t.registration_status": storage.RegistrationStatusAccepted}) + } rows, err := query.RunWith(c.runner).QueryContext(ctx) if err != nil { @@ -135,6 +186,7 @@ func (c *Client) GetTeams(ctx context.Context, req *storage.GetTeamsRequest) ([] if err := rows.Scan( &team.ID, &team.Name, + &team.RegistrationStatus, &team.Captain.ID, &team.Captain.Username, &team.Captain.AvatarURL, @@ -159,30 +211,36 @@ const changeNameQuery = ` WITH updated_team AS ( UPDATE questspace.team SET name = $1 WHERE id = $2 - RETURNING id, name, cap_id, invite_path -) SELECT t.id, t.name, t.invite_path, t.cap_id, u.username, u.avatar_url + RETURNING id, name, cap_id, invite_path, registration_status +) SELECT t.id, t.name, t.invite_path, t.registration_status, t.cap_id, u.username, u.avatar_url FROM updated_team t LEFT JOIN questspace.user u ON t.cap_id = u.id ` func (c *Client) ChangeTeamName(ctx context.Context, req *storage.ChangeTeamNameRequest) (*storage.Team, error) { - sqlQuery := sq.Expr(changeNameQuery, req.Name, req.ID) - - row := sq.QueryRowContextWith(ctx, c.runner, sqlQuery) + row := c.runner.QueryRowContext(ctx, changeNameQuery, req.Name, req.ID) team := &storage.Team{Captain: &storage.User{}} - if err := row.Scan(&team.ID, &team.Name, &team.InviteLink, &team.Captain.ID, &team.Captain.Username, &team.Captain.AvatarURL); err != nil { + if err := row.Scan( + &team.ID, + &team.Name, + &team.InviteLink, + &team.RegistrationStatus, + &team.Captain.ID, + &team.Captain.Username, + &team.Captain.AvatarURL, + ); err != nil { return nil, xerrors.Errorf("scan row: %w", err) } return team, nil } func (c *Client) SetInviteLink(ctx context.Context, req *storage.SetInvitePathRequest) error { - query := sq.Update("questspace.team"). - Set("invite_path", req.InvitePath). - Where(sq.Eq{"id": req.TeamID}). - PlaceholderFormat(sq.Dollar) + query := ` + UPDATE questspace.team SET invite_path = $1 + WHERE id = $2 +` - _, err := query.RunWith(c.runner).ExecContext(ctx) + _, err := c.runner.ExecContext(ctx, query, req.InvitePath, req.TeamID) if err != nil { return xerrors.Errorf("exec query: %w", err) } @@ -203,11 +261,13 @@ WHERE id = ( ` func (c *Client) JoinTeam(ctx context.Context, req *storage.JoinTeamRequest) (*storage.User, error) { - sqlQuery := sq.Expr(joinTeamQuery, req.User.ID, req.InvitePath) - - row := sq.QueryRowContextWith(ctx, c.runner, sqlQuery) + row := c.runner.QueryRowContext(ctx, joinTeamQuery, req.User.ID, req.InvitePath) user := &storage.User{} - if err := row.Scan(&user.ID, &user.Username, &user.AvatarURL); err != nil { + if err := row.Scan( + &user.ID, + &user.Username, + &user.AvatarURL, + ); err != nil { if pgErr := new(pgconn.PgError); errors.As(err, &pgErr) && pgErr.Code == triggerActionExceptionCode { return nil, storage.ErrTeamAlreadyFull } @@ -217,11 +277,12 @@ func (c *Client) JoinTeam(ctx context.Context, req *storage.JoinTeamRequest) (*s } func (c *Client) DeleteTeam(ctx context.Context, req *storage.DeleteTeamRequest) error { - query := sq.Delete("questspace.team"). - Where(sq.Eq{"id": req.ID}). - PlaceholderFormat(sq.Dollar) + query := ` + DELETE FROM questspace.team + WHERE id = $1 +` - _, err := query.RunWith(c.runner).ExecContext(ctx) + _, err := c.runner.ExecContext(ctx, query, req.ID) if err != nil { return xerrors.Errorf("exec query: %w", err) } @@ -232,31 +293,58 @@ const changeLeaderQuery = ` WITH updated_team AS ( UPDATE questspace.team SET cap_id = $1 WHERE id = $2 - RETURNING id, name, cap_id, invite_path -) SELECT t.id, t.name, t.invite_path, t.cap_id, u.username, u.avatar_url + RETURNING id, name, cap_id, invite_path, registration_status +) SELECT t.id, t.name, t.invite_path, t.registration_status, t.cap_id, u.username, u.avatar_url FROM updated_team t LEFT JOIN questspace.user u ON t.cap_id = u.id ` func (c *Client) ChangeLeader(ctx context.Context, req *storage.ChangeLeaderRequest) (*storage.Team, error) { - sqlQuery := sq.Expr(changeLeaderQuery, req.CaptainID, req.ID) - - row := sq.QueryRowContextWith(ctx, c.runner, sqlQuery) + row := c.runner.QueryRowContext(ctx, changeLeaderQuery, req.CaptainID, req.ID) team := &storage.Team{Captain: &storage.User{}} - if err := row.Scan(&team.ID, &team.Name, &team.InviteLink, &team.Captain.ID, &team.Captain.Username, &team.Captain.AvatarURL); err != nil { + if err := row.Scan( + &team.ID, + &team.Name, + &team.InviteLink, + &team.RegistrationStatus, + &team.Captain.ID, + &team.Captain.Username, + &team.Captain.AvatarURL, + ); err != nil { return nil, xerrors.Errorf("scan row: %w", err) } return team, nil } func (c *Client) RemoveUser(ctx context.Context, req *storage.RemoveUserRequest) error { - query := sq.Delete("questspace.registration"). - Where(sq.Eq{"user_id": req.UserID, "team_id": req.ID}). - PlaceholderFormat(sq.Dollar) + query := ` + DELETE FROM questspace.registration + WHERE user_id = $1 AND team_id = $2 +` - _, err := query.RunWith(c.runner).ExecContext(ctx) + _, err := c.runner.ExecContext(ctx, query, req.UserID, req.ID) if err != nil { return xerrors.Errorf("exec query: %w", err) } return nil } + +func (c *Client) AcceptTeam(ctx context.Context, req *storage.AcceptTeamRequest) error { + query := ` + UPDATE questspace.team SET registration_status = 'ACCEPTED' + WHERE id = $1 +` + + res, err := c.runner.ExecContext(ctx, query, req.ID) + if err != nil { + return xerrors.Errorf("exec query: %w", err) + } + rowsAffected, err := res.RowsAffected() + if err != nil { + return xerrors.Errorf("get rows affected: %w", err) + } + if rowsAffected == 0 { + return storage.ErrNotFound + } + return nil +} diff --git a/src/internal/pgdb/pgclient/team_test.go b/src/internal/pgdb/pgclient/team_test.go index fd40991..0a9d586 100644 --- a/src/internal/pgdb/pgclient/team_test.go +++ b/src/internal/pgdb/pgclient/team_test.go @@ -37,13 +37,16 @@ var ( } teamReq1 = storage.CreateTeamRequest{ - Name: "my_great_team", + Name: "my_great_team", + RegistrationStatus: storage.RegistrationStatusAccepted, } teamReq2 = storage.CreateTeamRequest{ - Name: "my_even_greater_team", + Name: "my_even_greater_team", + RegistrationStatus: storage.RegistrationStatusAccepted, } teamReq3 = storage.CreateTeamRequest{ - Name: "team", + Name: "team", + RegistrationStatus: storage.RegistrationStatusAccepted, } firstPath = "first" @@ -103,9 +106,10 @@ func TestTeamStorage_AlreadyExists(t *testing.T) { assert.NotNil(t, team1) team2, err := client.CreateTeam(ctx, &storage.CreateTeamRequest{ - Name: teamReq.Name, - QuestID: teamReq.QuestID, - Creator: user2, + Name: teamReq.Name, + QuestID: teamReq.QuestID, + Creator: user2, + RegistrationStatus: storage.RegistrationStatusOnConsideration, }) require.Error(t, err) assert.ErrorIs(t, err, storage.ErrExists) diff --git a/src/internal/qtime/time.go b/src/internal/qtime/time.go index 37a7480..5a2fde2 100644 --- a/src/internal/qtime/time.go +++ b/src/internal/qtime/time.go @@ -6,11 +6,11 @@ import ( "time" ) -const testEnv = "TIME_TEST" +const TestTimeEnv = "TIME_TEST" var ( nowFunc = time.Now - testStartTime = time.Date(2024, time.April, 7, 12, 0, 0, 0, time.UTC) + testStartTime = time.Date(2024, time.April, 7, 12, 0, 0, 0, time.UTC) // 2024-04-07T12:00:00Z ) func init() { @@ -22,7 +22,7 @@ func init() { } func IsTestTimeMode() bool { - return len(os.Getenv(testEnv)) > 0 + return len(os.Getenv(TestTimeEnv)) > 0 } type TimeGetter func() time.Time diff --git a/src/internal/questspace/game/game.go b/src/internal/questspace/game/game.go index 5aa177e..260b16e 100644 --- a/src/internal/questspace/game/game.go +++ b/src/internal/questspace/game/game.go @@ -175,7 +175,7 @@ type TeamResults struct { } func (s *Service) GetResults(ctx context.Context, questID storage.ID) (*TeamResults, error) { - teams, err := s.tms.GetTeams(ctx, &storage.GetTeamsRequest{QuestIDs: []storage.ID{questID}}) + teams, err := s.tms.GetTeams(ctx, &storage.GetTeamsRequest{QuestIDs: []storage.ID{questID}, AcceptedOnly: true}) if err != nil { if errors.Is(err, storage.ErrNotFound) { return nil, httperrors.Errorf(http.StatusNotFound, "quest %q not found", questID) @@ -255,7 +255,7 @@ type LeaderboardResponse struct { } func (s *Service) GetLeaderboard(ctx context.Context, questID storage.ID) (*LeaderboardResponse, error) { - teams, err := s.tms.GetTeams(ctx, &storage.GetTeamsRequest{QuestIDs: []storage.ID{questID}}) + teams, err := s.tms.GetTeams(ctx, &storage.GetTeamsRequest{QuestIDs: []storage.ID{questID}, AcceptedOnly: true}) if err != nil { if errors.Is(err, storage.ErrNotFound) { return nil, httperrors.Errorf(http.StatusNotFound, "quest %q not found", questID) @@ -317,6 +317,9 @@ func (s *Service) TakeHint(ctx context.Context, user *storage.User, req *TakeHin } return nil, xerrors.Errorf("get team: %w", err) } + if team.RegistrationStatus != storage.RegistrationStatusAccepted { + return nil, httperrors.New(http.StatusForbidden, "only accepted teams can take hints") + } accepted, err := s.ah.GetAcceptedTasks(ctx, &storage.GetAcceptedTasksRequest{TeamID: team.ID, QuestID: req.QuestID}) if err != nil { return nil, xerrors.Errorf("get results: %w", err) @@ -364,6 +367,9 @@ func (s *Service) TryAnswer(ctx context.Context, user *storage.User, req *TryAns } return nil, xerrors.Errorf("get team: %w", err) } + if team.RegistrationStatus != storage.RegistrationStatusAccepted { + return nil, httperrors.New(http.StatusForbidden, "only accepted teams can answer tasks") + } acceptedTasks, err := s.ah.GetAcceptedTasks(ctx, &storage.GetAcceptedTasksRequest{TeamID: team.ID, QuestID: req.QuestID}) if err != nil { return nil, xerrors.Errorf("get results: %w", err) diff --git a/src/internal/questspace/teams/service.go b/src/internal/questspace/teams/service.go index d284d54..27e1c56 100644 --- a/src/internal/questspace/teams/service.go +++ b/src/internal/questspace/teams/service.go @@ -13,18 +13,44 @@ import ( "questspace/pkg/storage" ) +type TeamServiceStorage interface { + storage.QuestStorage + storage.TeamStorage +} + type Service struct { - s storage.TeamStorage + s TeamServiceStorage inviteLinkPrefix string } -func NewService(s storage.TeamStorage, inviteLinkPrefix string) *Service { +func NewService(s TeamServiceStorage, inviteLinkPrefix string) *Service { return &Service{ s: s, inviteLinkPrefix: inviteLinkPrefix, } } +func (s *Service) getRegistrationStatus(ctx context.Context, questID storage.ID) (storage.RegistrationStatus, error) { + quest, err := s.s.GetQuest(ctx, &storage.GetQuestRequest{ID: questID}) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + return storage.RegistrationStatusUnspecified, httperrors.Errorf(http.StatusNotFound, "quest %q not found", questID.String()) + } + return storage.RegistrationStatusUnspecified, xerrors.Errorf("get quest: %w", err) + } + if quest.RegistrationType == storage.RegistrationAuto && quest.MaxTeamsAmount == nil { + return storage.RegistrationStatusAccepted, nil + } + currentAcceptedTeams, err := s.s.GetTeams(ctx, &storage.GetTeamsRequest{QuestIDs: []storage.ID{questID}, AcceptedOnly: true}) + if err != nil { + return storage.RegistrationStatusUnspecified, xerrors.Errorf("get accepted teams: %w", err) + } + if quest.RegistrationType == storage.RegistrationAuto && len(currentAcceptedTeams) < *quest.MaxTeamsAmount { + return storage.RegistrationStatusAccepted, nil + } + return storage.RegistrationStatusOnConsideration, nil +} + func (s *Service) CreateTeam(ctx context.Context, req *storage.CreateTeamRequest) (*storage.Team, error) { exisingTeams, err := s.s.GetTeams(ctx, &storage.GetTeamsRequest{User: req.Creator, QuestIDs: []storage.ID{req.QuestID}}) if err != nil { @@ -33,6 +59,11 @@ func (s *Service) CreateTeam(ctx context.Context, req *storage.CreateTeamRequest if len(exisingTeams) > 0 { return nil, httperrors.New(http.StatusNotAcceptable, "cannot create more than one team for quest") } + regStatus, err := s.getRegistrationStatus(ctx, req.QuestID) + if err != nil { + return nil, xerrors.Errorf("get registration status for new team: %w", err) + } + req.RegistrationStatus = regStatus team, err := s.s.CreateTeam(ctx, req) if err != nil { return nil, xerrors.Errorf("create team: %w", err) @@ -255,3 +286,44 @@ func (s *Service) RemoveUser(ctx context.Context, user *storage.User, req *stora return team, nil } + +func (s *Service) AcceptTeam(ctx context.Context, user *storage.User, questID, teamID storage.ID) ([]storage.Team, error) { + quest, err := s.s.GetQuest(ctx, &storage.GetQuestRequest{ID: questID}) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + return nil, httperrors.Errorf(http.StatusNotFound, "quest %q not found", questID.String()) + } + return nil, xerrors.Errorf("get quest: %w", err) + } + if quest.Creator.ID != user.ID { + return nil, httperrors.New(http.StatusForbidden, "only quest creator can accept teams") + } + + currentAcceptedTeams, err := s.s.GetTeams(ctx, &storage.GetTeamsRequest{QuestIDs: []storage.ID{questID}, AcceptedOnly: true}) + if err != nil { + return nil, xerrors.Errorf("get current accepted teams: %w", err) + } + var alreadyAccepted bool + for _, at := range currentAcceptedTeams { + if at.ID == teamID { + alreadyAccepted = true + break + } + } + if !alreadyAccepted { + if quest.MaxTeamsAmount != nil && len(currentAcceptedTeams) == *quest.MaxTeamsAmount { + return nil, httperrors.Errorf(http.StatusNotAcceptable, "already have maximum amount of accepted teams: %d") + } + if err = s.s.AcceptTeam(ctx, &storage.AcceptTeamRequest{ID: teamID}); err != nil { + if errors.Is(err, storage.ErrNotFound) { + return nil, httperrors.Errorf(http.StatusNotFound, "team %q not found", teamID.String()) + } + return nil, xerrors.Errorf("accept team: %w", err) + } + } + allTeams, err := s.s.GetTeams(ctx, &storage.GetTeamsRequest{QuestIDs: []storage.ID{questID}}) + if err != nil { + return nil, xerrors.Errorf("get all teams: %w", err) + } + return allTeams, nil +} diff --git a/src/internal/questspace/teams/service_test.go b/src/internal/questspace/teams/service_test.go index eea3987..f575268 100644 --- a/src/internal/questspace/teams/service_test.go +++ b/src/internal/questspace/teams/service_test.go @@ -22,7 +22,7 @@ func TestService_CreateTeam(t *testing.T) { ctx := context.Background() ctrl := gomock.NewController(t) - s := storagemock.NewMockTeamStorage(ctrl) + s := storagemock.NewMockQuestSpaceStorage(ctrl) service := NewService(s, linkPrefix) creator := storage.User{ @@ -53,7 +53,16 @@ func TestService_CreateTeam(t *testing.T) { GetTeams(ctx, &storage.GetTeamsRequest{User: &creator, QuestIDs: []storage.ID{questID}}). Return(nil, nil), - s.EXPECT().CreateTeam(ctx, &req).Return(&createdTeam, nil), + s.EXPECT().GetQuest(ctx, &storage.GetQuestRequest{ID: questID}).Return(&storage.Quest{ + RegistrationType: storage.RegistrationAuto, + }, nil), + + s.EXPECT().CreateTeam(ctx, &storage.CreateTeamRequest{ + Creator: &creator, + QuestID: questID, + Name: "new team", + RegistrationStatus: storage.RegistrationStatusAccepted, + }).Return(&createdTeam, nil), s.EXPECT(). SetInviteLink(ctx, &storage.SetInvitePathRequest{TeamID: createdTeam.ID, InvitePath: inviteSuffix}). @@ -72,7 +81,7 @@ func TestTeamService_CreateTeam_AlreadyMember(t *testing.T) { ctx := context.Background() ctrl := gomock.NewController(t) - s := storagemock.NewMockTeamStorage(ctrl) + s := storagemock.NewMockQuestSpaceStorage(ctrl) service := NewService(s, linkPrefix) creator := storage.User{ @@ -106,7 +115,7 @@ func TestTeamService_JoinTeam(t *testing.T) { ctx := context.Background() ctrl := gomock.NewController(t) - s := storagemock.NewMockTeamStorage(ctrl) + s := storagemock.NewMockQuestSpaceStorage(ctrl) service := NewService(s, linkPrefix) newMember := storage.User{ @@ -158,7 +167,7 @@ func TestTeamService_JoinTeam_AlreadyInvited(t *testing.T) { ctx := context.Background() ctrl := gomock.NewController(t) - s := storagemock.NewMockTeamStorage(ctrl) + s := storagemock.NewMockQuestSpaceStorage(ctrl) service := NewService(s, linkPrefix) oldMember := storage.User{ @@ -202,7 +211,7 @@ func TestTeamService_LeaveTeam(t *testing.T) { ctx := context.Background() ctrl := gomock.NewController(t) - s := storagemock.NewMockTeamStorage(ctrl) + s := storagemock.NewMockQuestSpaceStorage(ctrl) service := NewService(s, linkPrefix) oldMember := storage.User{ diff --git a/src/pkg/storage/interfaces.go b/src/pkg/storage/interfaces.go index ca6d75f..102de2e 100644 --- a/src/pkg/storage/interfaces.go +++ b/src/pkg/storage/interfaces.go @@ -57,6 +57,7 @@ type TeamStorage interface { DeleteTeam(context.Context, *DeleteTeamRequest) error ChangeLeader(context.Context, *ChangeLeaderRequest) (*Team, error) RemoveUser(context.Context, *RemoveUserRequest) error + AcceptTeam(context.Context, *AcceptTeamRequest) error } type AnswerHintStorage interface { diff --git a/src/pkg/storage/mocks/client.go b/src/pkg/storage/mocks/client.go index 02e6ab5..5b6ad38 100644 --- a/src/pkg/storage/mocks/client.go +++ b/src/pkg/storage/mocks/client.go @@ -36,6 +36,20 @@ func (m *MockQuestSpaceStorage) EXPECT() *MockQuestSpaceStorageMockRecorder { return m.recorder } +// AcceptTeam mocks base method. +func (m *MockQuestSpaceStorage) AcceptTeam(arg0 context.Context, arg1 *storage.AcceptTeamRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AcceptTeam", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AcceptTeam indicates an expected call of AcceptTeam. +func (mr *MockQuestSpaceStorageMockRecorder) AcceptTeam(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptTeam", reflect.TypeOf((*MockQuestSpaceStorage)(nil).AcceptTeam), arg0, arg1) +} + // ChangeLeader mocks base method. func (m *MockQuestSpaceStorage) ChangeLeader(arg0 context.Context, arg1 *storage.ChangeLeaderRequest) (*storage.Team, error) { m.ctrl.T.Helper() @@ -1086,6 +1100,20 @@ func (m *MockTeamStorage) EXPECT() *MockTeamStorageMockRecorder { return m.recorder } +// AcceptTeam mocks base method. +func (m *MockTeamStorage) AcceptTeam(arg0 context.Context, arg1 *storage.AcceptTeamRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AcceptTeam", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AcceptTeam indicates an expected call of AcceptTeam. +func (mr *MockTeamStorageMockRecorder) AcceptTeam(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptTeam", reflect.TypeOf((*MockTeamStorage)(nil).AcceptTeam), arg0, arg1) +} + // ChangeLeader mocks base method. func (m *MockTeamStorage) ChangeLeader(arg0 context.Context, arg1 *storage.ChangeLeaderRequest) (*storage.Team, error) { m.ctrl.T.Helper() diff --git a/src/pkg/storage/models.go b/src/pkg/storage/models.go index 2bbe2f0..dc57f30 100644 --- a/src/pkg/storage/models.go +++ b/src/pkg/storage/models.go @@ -105,21 +105,30 @@ func (q QuestStatus) MarshalJSON() ([]byte, error) { return b.Bytes(), nil } +type RegistrationType string + +const ( + RegistrationUnspecified RegistrationType = "" + RegistrationAuto RegistrationType = "AUTO" + RegistrationVerify RegistrationType = "VERIFY" +) + type Quest struct { - ID ID `json:"id"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Access AccessType `json:"access"` - Creator *User `json:"creator"` - RegistrationDeadline *time.Time `json:"registration_deadline,omitempty" example:"2024-04-14T12:00:00+05:00"` - StartTime *time.Time `json:"start_time" example:"2024-04-14T14:00:00+05:00"` - FinishTime *time.Time `json:"finish_time,omitempty" example:"2024-04-21T14:00:00+05:00"` - MediaLink string `json:"media_link"` - MaxTeamCap *int `json:"max_team_cap,omitempty"` - Status QuestStatus `json:"status"` - HasBrief bool `json:"has_brief,omitempty"` - Brief string `json:"brief,omitempty"` - MaxTeamsAmount *int `json:"max_teams_amount,omitempty"` + ID ID `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Access AccessType `json:"access"` + Creator *User `json:"creator"` + RegistrationDeadline *time.Time `json:"registration_deadline,omitempty" example:"2024-04-14T12:00:00+05:00"` + StartTime *time.Time `json:"start_time" example:"2024-04-14T14:00:00+05:00"` + FinishTime *time.Time `json:"finish_time,omitempty" example:"2024-04-21T14:00:00+05:00"` + MediaLink string `json:"media_link"` + MaxTeamCap *int `json:"max_team_cap,omitempty"` + Status QuestStatus `json:"status"` + HasBrief bool `json:"has_brief,omitempty"` + Brief string `json:"brief,omitempty"` + MaxTeamsAmount *int `json:"max_teams_amount,omitempty"` + RegistrationType RegistrationType `json:"registration_type,omitempty" enums:"AUTO,VERIFY"` } type GetQuestType int @@ -173,15 +182,24 @@ func (p *Page) ID() string { return b.String() } +type RegistrationStatus string + +const ( + RegistrationStatusUnspecified RegistrationStatus = "" + RegistrationStatusOnConsideration RegistrationStatus = "ON_CONSIDERATION" + RegistrationStatusAccepted RegistrationStatus = "ACCEPTED" +) + type Team struct { - ID ID `json:"id"` - Name string `json:"name"` - Quest *Quest `json:"-"` - Captain *User `json:"captain,omitempty"` - Score int `json:"score"` - InviteLink string `json:"invite_link,omitempty"` - InviteLinkID int64 `json:"-"` - Members []User `json:"members,omitempty"` + ID ID `json:"id"` + Name string `json:"name"` + Quest *Quest `json:"-"` + Captain *User `json:"captain,omitempty"` + Score int `json:"score"` + InviteLink string `json:"invite_link,omitempty"` + InviteLinkID int64 `json:"-"` + Members []User `json:"members,omitempty"` + RegistrationStatus RegistrationStatus `json:"registration_status,omitempty" enums:"ON_CONSIDERATION,ACCEPTED"` } type User struct { diff --git a/src/pkg/storage/requests.go b/src/pkg/storage/requests.go index 95234a5..12f236c 100644 --- a/src/pkg/storage/requests.go +++ b/src/pkg/storage/requests.go @@ -33,18 +33,19 @@ type DeleteUserRequest struct { } type CreateQuestRequest struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` - Access AccessType `json:"access"` - Creator *User `json:"-"` - RegistrationDeadline *time.Time `json:"registration_deadline,omitempty" example:"2024-04-14T12:00:00+05:00"` - StartTime *time.Time `json:"start_time" example:"2024-04-14T14:00:00+05:00"` - FinishTime *time.Time `json:"finish_time,omitempty" example:"2024-04-21T14:00:00+05:00"` - MediaLink string `json:"media_link"` - MaxTeamCap *int `json:"max_team_cap,omitempty"` - HasBrief bool `json:"has_brief,omitempty"` - Brief string `json:"brief,omitempty"` - MaxTeamsAmount *int `json:"max_teams_amount,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Access AccessType `json:"access"` + Creator *User `json:"-"` + RegistrationDeadline *time.Time `json:"registration_deadline,omitempty" example:"2024-04-14T12:00:00+05:00"` + StartTime *time.Time `json:"start_time" example:"2024-04-14T14:00:00+05:00"` + FinishTime *time.Time `json:"finish_time,omitempty" example:"2024-04-21T14:00:00+05:00"` + MediaLink string `json:"media_link"` + MaxTeamCap *int `json:"max_team_cap,omitempty"` + HasBrief bool `json:"has_brief,omitempty"` + Brief string `json:"brief,omitempty"` + MaxTeamsAmount *int `json:"max_teams_amount,omitempty"` + RegistrationType RegistrationType `json:"registration_type,omitempty" enums:"AUTO,VERIFY"` } type GetQuestRequest struct { @@ -64,18 +65,19 @@ type GetQuestsResponse struct { } type UpdateQuestRequest struct { - ID ID `json:"-"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Access AccessType `json:"access,omitempty"` - RegistrationDeadline *time.Time `json:"registration_deadline,omitempty"` - StartTime *time.Time `json:"start_time,omitempty"` - FinishTime *time.Time `json:"finish_time,omitempty"` - MediaLink string `json:"media_link,omitempty"` - MaxTeamCap *int `json:"max_team_cap,omitempty"` - HasBrief *bool `json:"has_brief,omitempty"` - Brief *string `json:"brief,omitempty"` - MaxTeamsAmount *int `json:"max_teams_amount,omitempty"` + ID ID `json:"-"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Access AccessType `json:"access,omitempty"` + RegistrationDeadline *time.Time `json:"registration_deadline,omitempty"` + StartTime *time.Time `json:"start_time,omitempty"` + FinishTime *time.Time `json:"finish_time,omitempty"` + MediaLink string `json:"media_link,omitempty"` + MaxTeamCap *int `json:"max_team_cap,omitempty"` + HasBrief *bool `json:"has_brief,omitempty"` + Brief *string `json:"brief,omitempty"` + MaxTeamsAmount *int `json:"max_teams_amount,omitempty"` + RegistrationType RegistrationType `json:"registration_type,omitempty" enums:"AUTO,VERIFY"` } type DeleteQuestRequest struct { @@ -87,9 +89,10 @@ type FinishQuestRequest struct { } type CreateTeamRequest struct { - Name string - QuestID ID - Creator *User + Name string + QuestID ID + Creator *User + RegistrationStatus RegistrationStatus } type UserRegistration struct { @@ -108,6 +111,7 @@ type GetTeamsRequest struct { User *User QuestIDs []ID IncludeMembers bool + AcceptedOnly bool } type ChangeTeamNameRequest struct { @@ -125,6 +129,10 @@ type JoinTeamRequest struct { User *User } +type AcceptTeamRequest struct { + ID ID +} + type DeleteTeamRequest struct { ID ID } From 4adbaaa2c73caf9ae6d7f6839206f872b417bdd6 Mon Sep 17 00:00:00 2001 From: Vladimir Pestov Date: Sun, 13 Oct 2024 18:34:52 +0500 Subject: [PATCH 3/4] QUESTSPACE-7: Add integration tests --- src/test/integration/questspace_test.go | 8 +- src/test/integration/runner.go | 3 +- .../integration/testdata/cases/15/tc.yaml | 6 +- .../integration/testdata/cases/16/tc.yaml | 9 +- .../integration/testdata/cases/17/tc.yaml | 3 +- .../integration/testdata/cases/18/tc.yaml | 3 +- .../integration/testdata/cases/19/tc.yaml | 9 +- .../integration/testdata/cases/20/tc.yaml | 21 ++- .../integration/testdata/cases/21/tc.yaml | 12 +- .../integration/testdata/cases/22/tc.yaml | 9 +- .../integration/testdata/cases/23/tc.yaml | 9 +- .../integration/testdata/cases/24/tc.yaml | 6 +- .../integration/testdata/cases/25/tc.yaml | 6 +- .../integration/testdata/cases/26/tc.yaml | 6 +- .../integration/testdata/cases/27/tc.yaml | 6 +- .../integration/testdata/cases/29/tc.yaml | 15 +- .../integration/testdata/cases/30/tc.yaml | 18 +- .../integration/testdata/cases/31/tc.yaml | 18 +- .../integration/testdata/cases/32/tc.yaml | 18 +- .../integration/testdata/cases/33/tc.yaml | 21 ++- .../integration/testdata/cases/34/tc.yaml | 6 +- .../integration/testdata/cases/35/tc.yaml | 18 +- .../integration/testdata/cases/36/tc.yaml | 15 +- .../integration/testdata/cases/37/tc.yaml | 15 +- .../integration/testdata/cases/38/tc.yaml | 15 +- .../integration/testdata/cases/39/tc.yaml | 15 +- .../integration/testdata/cases/40/tc.yaml | 15 +- .../integration/testdata/cases/41/tc.yaml | 136 +++++++++++++++ .../integration/testdata/cases/42/tc.yaml | 163 ++++++++++++++++++ src/test/testutils/testmain.go | 7 + 30 files changed, 487 insertions(+), 124 deletions(-) create mode 100644 src/test/integration/testdata/cases/41/tc.yaml create mode 100644 src/test/integration/testdata/cases/42/tc.yaml diff --git a/src/test/integration/questspace_test.go b/src/test/integration/questspace_test.go index 868b27d..0309944 100644 --- a/src/test/integration/questspace_test.go +++ b/src/test/integration/questspace_test.go @@ -32,12 +32,10 @@ func TestQuestspace(t *testing.T) { t.Run(tcFullName, func(t *testing.T) { if tc.Ignore { - t.Logf("WARNING: Test case %q is ignored. Skipping...", tcFullName) - return + t.Skipf("WARNING: Test case %q is ignored. Skipping...", tcFullName) } if len(runTC) != 0 && !strings.HasPrefix(tcFullName, runTC+"/") { - t.Logf("WARNING: Test case %q is ignored by %s. Skipping...", tcFullName, RunTCkey) - return + t.Skipf("WARNING: Test case %q is ignored by %s. Skipping...", tcFullName, RunTCkey) } testutils.StartServer(t) runner := NewTestRunner(testutils.ServerURL) @@ -49,7 +47,7 @@ func TestQuestspace(t *testing.T) { runner.VerifyData(t, req.ExpectedJSON, body) } } else { - t.Fatalf(`expected status %d but got %d. Stopping test case...`, req.ExpectedStatus, code) + t.Fatalf("expected status %d but got %d. Stopping test case...\nBody: \n%s", req.ExpectedStatus, code, body) } } }) diff --git a/src/test/integration/runner.go b/src/test/integration/runner.go index 66b6e0a..7711bd1 100644 --- a/src/test/integration/runner.go +++ b/src/test/integration/runner.go @@ -11,6 +11,7 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -276,5 +277,5 @@ func (r *TestRunner) VerifyData(t *testing.T, expected, actual string) { needGot, err := json.Marshal(*gotData) require.NoError(t, err) - require.JSONEq(t, string(needExp), string(needGot)) + assert.JSONEq(t, string(needExp), string(needGot)) } diff --git a/src/test/integration/testdata/cases/15/tc.yaml b/src/test/integration/testdata/cases/15/tc.yaml index 969382b..f6b9565 100644 --- a/src/test/integration/testdata/cases/15/tc.yaml +++ b/src/test/integration/testdata/cases/15/tc.yaml @@ -50,7 +50,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: GET @@ -73,7 +74,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } } diff --git a/src/test/integration/testdata/cases/16/tc.yaml b/src/test/integration/testdata/cases/16/tc.yaml index 6369cc0..a54854b 100644 --- a/src/test/integration/testdata/cases/16/tc.yaml +++ b/src/test/integration/testdata/cases/16/tc.yaml @@ -52,7 +52,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST @@ -101,7 +102,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: GET @@ -125,7 +127,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: DELETE diff --git a/src/test/integration/testdata/cases/17/tc.yaml b/src/test/integration/testdata/cases/17/tc.yaml index de59032..70b6c9c 100644 --- a/src/test/integration/testdata/cases/17/tc.yaml +++ b/src/test/integration/testdata/cases/17/tc.yaml @@ -52,7 +52,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST diff --git a/src/test/integration/testdata/cases/18/tc.yaml b/src/test/integration/testdata/cases/18/tc.yaml index 607f477..e774de9 100644 --- a/src/test/integration/testdata/cases/18/tc.yaml +++ b/src/test/integration/testdata/cases/18/tc.yaml @@ -52,7 +52,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST diff --git a/src/test/integration/testdata/cases/19/tc.yaml b/src/test/integration/testdata/cases/19/tc.yaml index 0aedf9a..c1d42f6 100644 --- a/src/test/integration/testdata/cases/19/tc.yaml +++ b/src/test/integration/testdata/cases/19/tc.yaml @@ -52,7 +52,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST @@ -121,7 +122,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: GET @@ -151,7 +153,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } diff --git a/src/test/integration/testdata/cases/20/tc.yaml b/src/test/integration/testdata/cases/20/tc.yaml index 55d5382..fbf6540 100644 --- a/src/test/integration/testdata/cases/20/tc.yaml +++ b/src/test/integration/testdata/cases/20/tc.yaml @@ -52,7 +52,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST @@ -121,7 +122,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: GET @@ -146,7 +148,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" }, "team": { "captain": { @@ -164,7 +167,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" }, "all_teams": [ { @@ -175,7 +179,8 @@ requests: }, "id": "$TEAM_ID", "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } ] } @@ -207,7 +212,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: POST @@ -232,5 +238,6 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } diff --git a/src/test/integration/testdata/cases/21/tc.yaml b/src/test/integration/testdata/cases/21/tc.yaml index 1f5d5c8..58f8cc6 100644 --- a/src/test/integration/testdata/cases/21/tc.yaml +++ b/src/test/integration/testdata/cases/21/tc.yaml @@ -52,7 +52,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST @@ -121,7 +122,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: GET @@ -151,7 +153,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: POST @@ -176,5 +179,6 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } diff --git a/src/test/integration/testdata/cases/22/tc.yaml b/src/test/integration/testdata/cases/22/tc.yaml index 7a36bb3..255bd1f 100644 --- a/src/test/integration/testdata/cases/22/tc.yaml +++ b/src/test/integration/testdata/cases/22/tc.yaml @@ -52,7 +52,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST @@ -121,7 +122,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: GET @@ -151,7 +153,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: POST diff --git a/src/test/integration/testdata/cases/23/tc.yaml b/src/test/integration/testdata/cases/23/tc.yaml index b857d30..211f053 100644 --- a/src/test/integration/testdata/cases/23/tc.yaml +++ b/src/test/integration/testdata/cases/23/tc.yaml @@ -52,7 +52,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST @@ -101,7 +102,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: POST @@ -119,7 +121,8 @@ requests: "id": "$SET$:TEAM_ID", "invite_link": "$SET$:INVITE_LINK", "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: GET diff --git a/src/test/integration/testdata/cases/24/tc.yaml b/src/test/integration/testdata/cases/24/tc.yaml index 5cac984..6aa9b39 100644 --- a/src/test/integration/testdata/cases/24/tc.yaml +++ b/src/test/integration/testdata/cases/24/tc.yaml @@ -52,7 +52,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST @@ -101,7 +102,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: GET diff --git a/src/test/integration/testdata/cases/25/tc.yaml b/src/test/integration/testdata/cases/25/tc.yaml index 5dc8028..5f8686e 100644 --- a/src/test/integration/testdata/cases/25/tc.yaml +++ b/src/test/integration/testdata/cases/25/tc.yaml @@ -52,7 +52,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST @@ -121,7 +122,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: POST diff --git a/src/test/integration/testdata/cases/26/tc.yaml b/src/test/integration/testdata/cases/26/tc.yaml index 411003c..8ac9acb 100644 --- a/src/test/integration/testdata/cases/26/tc.yaml +++ b/src/test/integration/testdata/cases/26/tc.yaml @@ -52,7 +52,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST @@ -121,7 +122,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: GET diff --git a/src/test/integration/testdata/cases/27/tc.yaml b/src/test/integration/testdata/cases/27/tc.yaml index e562dd3..36d6a1f 100644 --- a/src/test/integration/testdata/cases/27/tc.yaml +++ b/src/test/integration/testdata/cases/27/tc.yaml @@ -50,7 +50,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: POST @@ -174,7 +175,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" }, "task_groups": [ { diff --git a/src/test/integration/testdata/cases/29/tc.yaml b/src/test/integration/testdata/cases/29/tc.yaml index f0ebc24..4bf93e6 100644 --- a/src/test/integration/testdata/cases/29/tc.yaml +++ b/src/test/integration/testdata/cases/29/tc.yaml @@ -27,11 +27,11 @@ requests: { "access": "public", "description": "description", - "finish_time": "2027-04-21T14:00:00+05:00", + "finish_time": "2024-04-14T12:00:00Z", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "2024-04-18T12:00:00+05:00", - "start_time": "2024-04-18T14:00:00+05:00" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z" } expected-status: 200 @@ -44,13 +44,14 @@ requests: "username": "svayp11" }, "description": "description", - "finish_time": "$ANY$", + "finish_time": "2024-04-14T12:00:00Z", "id": "$SET$:QUEST_ID", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "$ANY$", - "start_time": "$ANY$", - "status": "RUNNING" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z", + "status": "RUNNING", + "registration_type": "AUTO" } - method: PATCH diff --git a/src/test/integration/testdata/cases/30/tc.yaml b/src/test/integration/testdata/cases/30/tc.yaml index d3339a3..42547a6 100644 --- a/src/test/integration/testdata/cases/30/tc.yaml +++ b/src/test/integration/testdata/cases/30/tc.yaml @@ -27,11 +27,11 @@ requests: { "access": "public", "description": "description", - "finish_time": "2027-04-21T14:00:00+05:00", + "finish_time": "2024-04-14T12:00:00Z", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "2024-04-18T12:00:00+05:00", - "start_time": "2024-04-18T14:00:00+05:00" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z" } expected-status: 200 @@ -44,13 +44,14 @@ requests: "username": "svayp11" }, "description": "description", - "finish_time": "$ANY$", + "finish_time": "2024-04-14T12:00:00Z", "id": "$SET$:QUEST_ID", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "$ANY$", - "start_time": "$ANY$", - "status": "RUNNING" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z", + "status": "RUNNING", + "registration_type": "AUTO" } - method: PATCH @@ -210,7 +211,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: POST diff --git a/src/test/integration/testdata/cases/31/tc.yaml b/src/test/integration/testdata/cases/31/tc.yaml index 56bae92..7bed6e8 100644 --- a/src/test/integration/testdata/cases/31/tc.yaml +++ b/src/test/integration/testdata/cases/31/tc.yaml @@ -27,11 +27,11 @@ requests: { "access": "public", "description": "description", - "finish_time": "2027-04-21T14:00:00+05:00", + "finish_time": "2024-04-14T12:00:00Z", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "2024-04-18T12:00:00+05:00", - "start_time": "2024-04-18T14:00:00+05:00" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z" } expected-status: 200 @@ -44,13 +44,14 @@ requests: "username": "svayp11" }, "description": "description", - "finish_time": "$ANY$", + "finish_time": "2024-04-14T12:00:00Z", "id": "$SET$:QUEST_ID", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "$ANY$", - "start_time": "$ANY$", - "status": "RUNNING" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z", + "status": "RUNNING", + "registration_type": "AUTO" } - method: PATCH @@ -202,7 +203,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: GET diff --git a/src/test/integration/testdata/cases/32/tc.yaml b/src/test/integration/testdata/cases/32/tc.yaml index eb08341..e9b9423 100644 --- a/src/test/integration/testdata/cases/32/tc.yaml +++ b/src/test/integration/testdata/cases/32/tc.yaml @@ -27,11 +27,11 @@ requests: { "access": "public", "description": "description", - "finish_time": "2027-04-21T14:00:00+05:00", + "finish_time": "2024-04-14T12:00:00Z", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "2024-04-18T12:00:00+05:00", - "start_time": "2024-04-18T14:00:00+05:00" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z" } expected-status: 200 @@ -44,13 +44,14 @@ requests: "username": "svayp11" }, "description": "description", - "finish_time": "$ANY$", + "finish_time": "2024-04-14T12:00:00Z", "id": "$SET$:QUEST_ID", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "$ANY$", - "start_time": "$ANY$", - "status": "RUNNING" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z", + "status": "RUNNING", + "registration_type": "AUTO" } - method: PATCH @@ -222,7 +223,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: POST diff --git a/src/test/integration/testdata/cases/33/tc.yaml b/src/test/integration/testdata/cases/33/tc.yaml index 2b2f162..e043950 100644 --- a/src/test/integration/testdata/cases/33/tc.yaml +++ b/src/test/integration/testdata/cases/33/tc.yaml @@ -27,11 +27,11 @@ requests: { "access": "public", "description": "description", - "finish_time": "2027-04-21T14:00:00+05:00", + "finish_time": "2024-04-14T12:00:00Z", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "2024-04-18T12:00:00+05:00", - "start_time": "2024-04-18T14:00:00+05:00" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z" } expected-status: 200 @@ -44,13 +44,14 @@ requests: "username": "svayp11" }, "description": "description", - "finish_time": "$ANY$", + "finish_time": "2024-04-14T12:00:00Z", "id": "$SET$:QUEST_ID", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "$ANY$", - "start_time": "$ANY$", - "status": "RUNNING" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z", + "status": "RUNNING", + "registration_type": "AUTO" } - method: PATCH @@ -222,7 +223,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: GET @@ -252,7 +254,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: POST diff --git a/src/test/integration/testdata/cases/34/tc.yaml b/src/test/integration/testdata/cases/34/tc.yaml index cb3c3ea..be23360 100644 --- a/src/test/integration/testdata/cases/34/tc.yaml +++ b/src/test/integration/testdata/cases/34/tc.yaml @@ -50,7 +50,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "ON_REGISTRATION" + "status": "ON_REGISTRATION", + "registration_type": "AUTO" } - method: PATCH @@ -202,7 +203,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: POST diff --git a/src/test/integration/testdata/cases/35/tc.yaml b/src/test/integration/testdata/cases/35/tc.yaml index 871ceda..e4e8525 100644 --- a/src/test/integration/testdata/cases/35/tc.yaml +++ b/src/test/integration/testdata/cases/35/tc.yaml @@ -27,11 +27,11 @@ requests: { "access": "public", "description": "description", - "finish_time": "2027-04-21T14:00:00+05:00", + "finish_time": "2024-04-14T12:00:00Z", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "2024-04-18T12:00:00+05:00", - "start_time": "2024-04-18T14:00:00+05:00" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z" } expected-status: 200 @@ -44,13 +44,14 @@ requests: "username": "svayp11" }, "description": "description", - "finish_time": "$ANY$", + "finish_time": "2024-04-14T12:00:00Z", "id": "$SET$:QUEST_ID", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "$ANY$", - "start_time": "$ANY$", - "status": "RUNNING" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z", + "status": "RUNNING", + "registration_type": "AUTO" } - method: PATCH @@ -202,7 +203,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: POST diff --git a/src/test/integration/testdata/cases/36/tc.yaml b/src/test/integration/testdata/cases/36/tc.yaml index 4f5c7b6..47a9800 100644 --- a/src/test/integration/testdata/cases/36/tc.yaml +++ b/src/test/integration/testdata/cases/36/tc.yaml @@ -51,7 +51,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "RUNNING" + "status": "RUNNING", + "registration_type": "AUTO" } - method: PATCH @@ -223,7 +224,8 @@ requests: } ], "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: POST @@ -253,7 +255,8 @@ requests: } ], "name": "dust_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } - method: GET @@ -277,7 +280,8 @@ requests: "name": "name", "registration_deadline": "$ANY$", "start_time": "$ANY$", - "status": "RUNNING" + "status": "RUNNING", + "registration_type": "AUTO" }, "task_groups": [ { @@ -336,7 +340,8 @@ requests: "id": "$SET$:TEAM_ID", "invite_link": "$SET$:INVITE_LINK", "name": "dream_team", - "score": 0 + "score": 0, + "registration_status": "ACCEPTED" } } diff --git a/src/test/integration/testdata/cases/37/tc.yaml b/src/test/integration/testdata/cases/37/tc.yaml index 68ec353..40e615c 100644 --- a/src/test/integration/testdata/cases/37/tc.yaml +++ b/src/test/integration/testdata/cases/37/tc.yaml @@ -27,11 +27,11 @@ requests: { "access": "public", "description": "description", - "finish_time": "2027-04-21T14:00:00+05:00", + "finish_time": "2024-04-14T12:00:00Z", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "2024-04-18T12:00:00+05:00", - "start_time": "2024-04-18T14:00:00+05:00" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z" } expected-status: 200 @@ -44,13 +44,14 @@ requests: "username": "svayp11" }, "description": "description", - "finish_time": "$ANY$", + "finish_time": "2024-04-14T12:00:00Z", "id": "$SET$:QUEST_ID", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "$ANY$", - "start_time": "$ANY$", - "status": "RUNNING" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z", + "status": "RUNNING", + "registration_type": "AUTO" } - method: PATCH diff --git a/src/test/integration/testdata/cases/38/tc.yaml b/src/test/integration/testdata/cases/38/tc.yaml index 09a0b87..92dbbc0 100644 --- a/src/test/integration/testdata/cases/38/tc.yaml +++ b/src/test/integration/testdata/cases/38/tc.yaml @@ -27,11 +27,11 @@ requests: { "access": "public", "description": "description", - "finish_time": "2027-04-21T14:00:00+05:00", + "finish_time": "2024-04-14T12:00:00Z", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "2024-04-18T12:00:00+05:00", - "start_time": "2024-04-18T14:00:00+05:00" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z" } expected-status: 200 @@ -44,13 +44,14 @@ requests: "username": "svayp11" }, "description": "description", - "finish_time": "$ANY$", + "finish_time": "2024-04-14T12:00:00Z", "id": "$SET$:QUEST_ID", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "$ANY$", - "start_time": "$ANY$", - "status": "RUNNING" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z", + "status": "RUNNING", + "registration_type": "AUTO" } - method: PATCH diff --git a/src/test/integration/testdata/cases/39/tc.yaml b/src/test/integration/testdata/cases/39/tc.yaml index 083863e..33d67f2 100644 --- a/src/test/integration/testdata/cases/39/tc.yaml +++ b/src/test/integration/testdata/cases/39/tc.yaml @@ -27,11 +27,11 @@ requests: { "access": "public", "description": "description", - "finish_time": "2027-04-21T14:00:00+05:00", + "finish_time": "2024-04-14T12:00:00Z", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "2024-04-18T12:00:00+05:00", - "start_time": "2024-04-18T14:00:00+05:00" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z" } expected-status: 200 @@ -44,13 +44,14 @@ requests: "username": "svayp11" }, "description": "description", - "finish_time": "$ANY$", + "finish_time": "2024-04-14T12:00:00Z", "id": "$SET$:QUEST_ID", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "$ANY$", - "start_time": "$ANY$", - "status": "RUNNING" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z", + "status": "RUNNING", + "registration_type": "AUTO" } - method: PATCH diff --git a/src/test/integration/testdata/cases/40/tc.yaml b/src/test/integration/testdata/cases/40/tc.yaml index 2bb5091..1ffe1a3 100644 --- a/src/test/integration/testdata/cases/40/tc.yaml +++ b/src/test/integration/testdata/cases/40/tc.yaml @@ -27,11 +27,11 @@ requests: { "access": "public", "description": "description", - "finish_time": "2027-04-21T14:00:00+05:00", + "finish_time": "2024-04-14T12:00:00Z", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "2024-04-18T12:00:00+05:00", - "start_time": "2024-04-18T14:00:00+05:00" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z" } expected-status: 200 @@ -44,13 +44,14 @@ requests: "username": "svayp11" }, "description": "description", - "finish_time": "$ANY$", + "finish_time": "2024-04-14T12:00:00Z", "id": "$SET$:QUEST_ID", "media_link": "https://api.dicebear.com/8.x/thumbs/svg", "name": "name", - "registration_deadline": "$ANY$", - "start_time": "$ANY$", - "status": "RUNNING" + "registration_deadline": "2024-04-06T12:00:00Z", + "start_time": "2024-04-06T14:00:00Z", + "status": "RUNNING", + "registration_type": "AUTO" } - method: PATCH diff --git a/src/test/integration/testdata/cases/41/tc.yaml b/src/test/integration/testdata/cases/41/tc.yaml new file mode 100644 index 0000000..dcdd2aa --- /dev/null +++ b/src/test/integration/testdata/cases/41/tc.yaml @@ -0,0 +1,136 @@ +name: verify-team +requests: + - method: POST + uri: /auth/register + json-input: > + { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "username": "svayp11", + "password": "password" + } + + expected-status: 200 + expected-json: > + { + "access_token": "$SET$:SVAYP11_TOKEN", + "user": { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "username": "svayp11", + "id": "$SET$:USER_ID" + } + } + + - method: POST + uri: /quest + authorization: $SVAYP11_TOKEN + json-input: > + { + "access": "public", + "description": "description", + "finish_time": "2024-04-14T18:00:00Z", + "max_team_cap": 4, + "media_link": "https://api.dicebear.com/8.x/thumbs/svg", + "name": "name", + "start_time": "2024-04-14T12:00:00Z", + "registration_type": "VERIFY" + } + + expected-status: 200 + expected-json: > + { + "access": "public", + "creator": { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID", + "username": "svayp11" + }, + "description": "description", + "finish_time": "2024-04-14T18:00:00Z", + "id": "$SET$:QUEST_ID", + "max_team_cap": 4, + "media_link": "https://api.dicebear.com/8.x/thumbs/svg", + "name": "name", + "start_time": "2024-04-14T12:00:00Z", + "status": "ON_REGISTRATION", + "registration_type": "VERIFY" + } + + - method: POST + uri: /auth/register + json-input: > + { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "username": "pyavs22", + "password": "qwerty" + } + + expected-status: 200 + expected-json: > + { + "access_token": "$SET$:PYAVS22_TOKEN", + "user": { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "username": "pyavs22", + "id": "$SET$:USER_ID2" + } + } + + - method: POST + uri: /quest/$QUEST_ID/teams + authorization: $PYAVS22_TOKEN + json-input: > + { + "name": "dream_team" + } + + expected-status: 200 + expected-json: > + { + "captain": { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID2", + "username": "pyavs22" + }, + "id": "$SET$:TEAM_ID", + "invite_link": "$SET$:INVITE_LINK", + "members": [ + { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID2", + "username": "pyavs22" + } + ], + "name": "dream_team", + "score": 0, + "registration_status": "ON_CONSIDERATION" + } + + - method: GET + uri: /teams/all/$TEAM_ID + + expected-status: 200 + expected-json: > + { + "captain": { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID2", + "username": "pyavs22" + }, + "id": "$SET$:TEAM_ID", + "invite_link": "$SET$:INVITE_LINK", + "members": [ + { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID2", + "username": "pyavs22" + } + ], + "name": "dream_team", + "score": 0, + "registration_status": "ON_CONSIDERATION" + } + + - method: DELETE + uri: /teams/all/$TEAM_ID + authorization: $PYAVS22_TOKEN + expected-status: 200 diff --git a/src/test/integration/testdata/cases/42/tc.yaml b/src/test/integration/testdata/cases/42/tc.yaml new file mode 100644 index 0000000..7212b80 --- /dev/null +++ b/src/test/integration/testdata/cases/42/tc.yaml @@ -0,0 +1,163 @@ +name: team-do-not-automatically-accept-on-overflow +requests: + - method: POST + uri: /auth/register + json-input: > + { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "username": "svayp11", + "password": "password" + } + + expected-status: 200 + expected-json: > + { + "access_token": "$SET$:SVAYP11_TOKEN", + "user": { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "username": "svayp11", + "id": "$SET$:USER_ID" + } + } + + - method: POST + uri: /quest + authorization: $SVAYP11_TOKEN + json-input: > + { + "access": "public", + "description": "description", + "finish_time": "2024-04-14T18:00:00Z", + "max_team_cap": 4, + "media_link": "https://api.dicebear.com/8.x/thumbs/svg", + "name": "name", + "start_time": "2024-04-14T12:00:00Z", + "registration_type": "AUTO", + "max_teams_amount": 1 + } + + expected-status: 200 + expected-json: > + { + "access": "public", + "creator": { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID", + "username": "svayp11" + }, + "description": "description", + "finish_time": "2024-04-14T18:00:00Z", + "id": "$SET$:QUEST_ID", + "max_team_cap": 4, + "media_link": "https://api.dicebear.com/8.x/thumbs/svg", + "name": "name", + "start_time": "2024-04-14T12:00:00Z", + "status": "ON_REGISTRATION", + "registration_type": "AUTO", + "max_teams_amount": 1 + } + + - method: POST + uri: /auth/register + json-input: > + { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "username": "pyavs22", + "password": "qwerty" + } + + expected-status: 200 + expected-json: > + { + "access_token": "$SET$:PYAVS22_TOKEN", + "user": { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "username": "pyavs22", + "id": "$SET$:USER_ID2" + } + } + + - method: POST + uri: /quest/$QUEST_ID/teams + authorization: $PYAVS22_TOKEN + json-input: > + { + "name": "dream_team" + } + + expected-status: 200 + expected-json: > + { + "captain": { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID2", + "username": "pyavs22" + }, + "id": "$SET$:TEAM_ID", + "invite_link": "$SET$:INVITE_LINK", + "members": [ + { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID2", + "username": "pyavs22" + } + ], + "name": "dream_team", + "score": 0, + "registration_status": "ACCEPTED" + } + + - method: GET + uri: /teams/all/$TEAM_ID + + expected-status: 200 + expected-json: > + { + "captain": { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID2", + "username": "pyavs22" + }, + "id": "$SET$:TEAM_ID", + "invite_link": "$SET$:INVITE_LINK", + "members": [ + { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID2", + "username": "pyavs22" + } + ], + "name": "dream_team", + "score": 0, + "registration_status": "ACCEPTED" + } + + - method: POST + uri: /quest/$QUEST_ID/teams + authorization: $SVAYP11_TOKEN + json-input: > + { + "name": "excess_team" + } + + expected-status: 200 + expected-json: > + { + "captain": { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID", + "username": "svayp11" + }, + "id": "$SET$:TEAM1_ID", + "invite_link": "$SET$:INVITE_LINK1", + "members": [ + { + "avatar_url": "https://api.dicebear.com/7.x/thumbs/svg", + "id": "$USER_ID", + "username": "svayp11" + } + ], + "name": "excess_team", + "score": 0, + "registration_status": "ON_CONSIDERATION" + } diff --git a/src/test/testutils/testmain.go b/src/test/testutils/testmain.go index 6911118..bb5f3c8 100644 --- a/src/test/testutils/testmain.go +++ b/src/test/testutils/testmain.go @@ -1,13 +1,17 @@ package testutils import ( + "fmt" "io" "net/http" "os" "os/exec" + "slices" "sync" "testing" "time" + + "questspace/internal/qtime" ) type Database interface { @@ -47,6 +51,8 @@ func InitApplication(m *testing.M) (code int) { func StartServer(t *testing.T) { t.Helper() + env := slices.Clone(os.Environ()) + env = append(env, fmt.Sprintf("%s=true", qtime.TestTimeEnv)) once.Do(func() { binCache = NewBinaryCache() @@ -56,6 +62,7 @@ func StartServer(t *testing.T) { cmd := exec.Command(binary, "--config", ConfigPath) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + cmd.Env = env if err := cmd.Start(); err != nil { t.Errorf("Error starting server: %v", err) t.FailNow() From 608c77bc804dd54a48efb56905dae9728ec000ba Mon Sep 17 00:00:00 2001 From: Vladimir Pestov Date: Sun, 13 Oct 2024 18:35:29 +0500 Subject: [PATCH 4/4] QUESTSPACE-7: Add docs --- src/docs/docs.go | 129 ++++++++++++++++++++++++++++++++++++++++-- src/docs/swagger.json | 129 ++++++++++++++++++++++++++++++++++++++++-- src/docs/swagger.yaml | 82 ++++++++++++++++++++++++++- 3 files changed, 329 insertions(+), 11 deletions(-) diff --git a/src/docs/docs.go b/src/docs/docs.go index 5435468..13e5f20 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -873,10 +873,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/storage.Team" - } + "$ref": "#/definitions/teams.ManyTeamsResponse" } }, "400": { @@ -931,6 +928,49 @@ const docTemplate = `{ } } }, + "/quest/{quest_id}/teams/{team_id}/accept": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "Teams" + ], + "summary": "Accept team", + "parameters": [ + { + "type": "string", + "description": "Quest id", + "name": "quest_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Team id", + "name": "team_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/teams.ManyTeamsResponse" + } + }, + "404": { + "description": "Not Found" + }, + "406": { + "description": "Not Acceptable" + } + } + } + }, "/teams/all/{team_id}": { "get": { "tags": [ @@ -1898,6 +1938,17 @@ const docTemplate = `{ "type": "string", "example": "2024-04-14T12:00:00+05:00" }, + "registration_type": { + "enum": [ + "AUTO", + "VERIFY" + ], + "allOf": [ + { + "$ref": "#/definitions/storage.RegistrationType" + } + ] + }, "start_time": { "type": "string", "example": "2024-04-14T14:00:00+05:00" @@ -2041,6 +2092,17 @@ const docTemplate = `{ "type": "string", "example": "2024-04-14T12:00:00+05:00" }, + "registration_type": { + "enum": [ + "AUTO", + "VERIFY" + ], + "allOf": [ + { + "$ref": "#/definitions/storage.RegistrationType" + } + ] + }, "start_time": { "type": "string", "example": "2024-04-14T14:00:00+05:00" @@ -2069,6 +2131,32 @@ const docTemplate = `{ "StatusFinished" ] }, + "storage.RegistrationStatus": { + "type": "string", + "enum": [ + "", + "ON_CONSIDERATION", + "ACCEPTED" + ], + "x-enum-varnames": [ + "RegistrationStatusUnspecified", + "RegistrationStatusOnConsideration", + "RegistrationStatusAccepted" + ] + }, + "storage.RegistrationType": { + "type": "string", + "enum": [ + "", + "AUTO", + "VERIFY" + ], + "x-enum-varnames": [ + "RegistrationUnspecified", + "RegistrationAuto", + "RegistrationVerify" + ] + }, "storage.Task": { "type": "object", "properties": { @@ -2225,6 +2313,17 @@ const docTemplate = `{ "name": { "type": "string" }, + "registration_status": { + "enum": [ + "ON_CONSIDERATION", + "ACCEPTED" + ], + "allOf": [ + { + "$ref": "#/definitions/storage.RegistrationStatus" + } + ] + }, "score": { "type": "integer" } @@ -2263,6 +2362,17 @@ const docTemplate = `{ "registration_deadline": { "type": "string" }, + "registration_type": { + "enum": [ + "AUTO", + "VERIFY" + ], + "allOf": [ + { + "$ref": "#/definitions/storage.RegistrationType" + } + ] + }, "start_time": { "type": "string" } @@ -2395,6 +2505,17 @@ const docTemplate = `{ } } }, + "teams.ManyTeamsResponse": { + "type": "object", + "properties": { + "teams": { + "type": "array", + "items": { + "$ref": "#/definitions/storage.Team" + } + } + } + }, "teams.UpdateRequest": { "type": "object", "properties": { diff --git a/src/docs/swagger.json b/src/docs/swagger.json index a66ec37..d5fd34a 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -862,10 +862,7 @@ "200": { "description": "OK", "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/storage.Team" - } + "$ref": "#/definitions/teams.ManyTeamsResponse" } }, "400": { @@ -920,6 +917,49 @@ } } }, + "/quest/{quest_id}/teams/{team_id}/accept": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "Teams" + ], + "summary": "Accept team", + "parameters": [ + { + "type": "string", + "description": "Quest id", + "name": "quest_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Team id", + "name": "team_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/teams.ManyTeamsResponse" + } + }, + "404": { + "description": "Not Found" + }, + "406": { + "description": "Not Acceptable" + } + } + } + }, "/teams/all/{team_id}": { "get": { "tags": [ @@ -1887,6 +1927,17 @@ "type": "string", "example": "2024-04-14T12:00:00+05:00" }, + "registration_type": { + "enum": [ + "AUTO", + "VERIFY" + ], + "allOf": [ + { + "$ref": "#/definitions/storage.RegistrationType" + } + ] + }, "start_time": { "type": "string", "example": "2024-04-14T14:00:00+05:00" @@ -2030,6 +2081,17 @@ "type": "string", "example": "2024-04-14T12:00:00+05:00" }, + "registration_type": { + "enum": [ + "AUTO", + "VERIFY" + ], + "allOf": [ + { + "$ref": "#/definitions/storage.RegistrationType" + } + ] + }, "start_time": { "type": "string", "example": "2024-04-14T14:00:00+05:00" @@ -2058,6 +2120,32 @@ "StatusFinished" ] }, + "storage.RegistrationStatus": { + "type": "string", + "enum": [ + "", + "ON_CONSIDERATION", + "ACCEPTED" + ], + "x-enum-varnames": [ + "RegistrationStatusUnspecified", + "RegistrationStatusOnConsideration", + "RegistrationStatusAccepted" + ] + }, + "storage.RegistrationType": { + "type": "string", + "enum": [ + "", + "AUTO", + "VERIFY" + ], + "x-enum-varnames": [ + "RegistrationUnspecified", + "RegistrationAuto", + "RegistrationVerify" + ] + }, "storage.Task": { "type": "object", "properties": { @@ -2214,6 +2302,17 @@ "name": { "type": "string" }, + "registration_status": { + "enum": [ + "ON_CONSIDERATION", + "ACCEPTED" + ], + "allOf": [ + { + "$ref": "#/definitions/storage.RegistrationStatus" + } + ] + }, "score": { "type": "integer" } @@ -2252,6 +2351,17 @@ "registration_deadline": { "type": "string" }, + "registration_type": { + "enum": [ + "AUTO", + "VERIFY" + ], + "allOf": [ + { + "$ref": "#/definitions/storage.RegistrationType" + } + ] + }, "start_time": { "type": "string" } @@ -2384,6 +2494,17 @@ } } }, + "teams.ManyTeamsResponse": { + "type": "object", + "properties": { + "teams": { + "type": "array", + "items": { + "$ref": "#/definitions/storage.Team" + } + } + } + }, "teams.UpdateRequest": { "type": "object", "properties": { diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index c1c50fe..6f379ae 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -347,6 +347,12 @@ definitions: registration_deadline: example: "2024-04-14T12:00:00+05:00" type: string + registration_type: + allOf: + - $ref: '#/definitions/storage.RegistrationType' + enum: + - AUTO + - VERIFY start_time: example: "2024-04-14T14:00:00+05:00" type: string @@ -442,6 +448,12 @@ definitions: registration_deadline: example: "2024-04-14T12:00:00+05:00" type: string + registration_type: + allOf: + - $ref: '#/definitions/storage.RegistrationType' + enum: + - AUTO + - VERIFY start_time: example: "2024-04-14T14:00:00+05:00" type: string @@ -464,6 +476,26 @@ definitions: - StatusRunning - StatusWaitResults - StatusFinished + storage.RegistrationStatus: + enum: + - "" + - ON_CONSIDERATION + - ACCEPTED + type: string + x-enum-varnames: + - RegistrationStatusUnspecified + - RegistrationStatusOnConsideration + - RegistrationStatusAccepted + storage.RegistrationType: + enum: + - "" + - AUTO + - VERIFY + type: string + x-enum-varnames: + - RegistrationUnspecified + - RegistrationAuto + - RegistrationVerify storage.Task: properties: correct_answers: @@ -565,6 +597,12 @@ definitions: type: array name: type: string + registration_status: + allOf: + - $ref: '#/definitions/storage.RegistrationStatus' + enum: + - ON_CONSIDERATION + - ACCEPTED score: type: integer type: object @@ -590,6 +628,12 @@ definitions: type: string registration_deadline: type: string + registration_type: + allOf: + - $ref: '#/definitions/storage.RegistrationType' + enum: + - AUTO + - VERIFY start_time: type: string type: object @@ -677,6 +721,13 @@ definitions: name: type: string type: object + teams.ManyTeamsResponse: + properties: + teams: + items: + $ref: '#/definitions/storage.Team' + type: array + type: object teams.UpdateRequest: properties: name: @@ -1253,9 +1304,7 @@ paths: "200": description: OK schema: - items: - $ref: '#/definitions/storage.Team' - type: array + $ref: '#/definitions/teams.ManyTeamsResponse' "400": description: Bad Request summary: Get all teams by quest id @@ -1290,6 +1339,33 @@ paths: summary: Create new team tags: - Teams + /quest/{quest_id}/teams/{team_id}/accept: + post: + parameters: + - description: Quest id + in: path + name: quest_id + required: true + type: string + - description: Team id + in: path + name: team_id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/teams.ManyTeamsResponse' + "404": + description: Not Found + "406": + description: Not Acceptable + security: + - ApiKeyAuth: [] + summary: Accept team + tags: + - Teams /teams/{team_id}: post: parameters: