Skip to content

Commit

Permalink
refactor(api, db): team validation (#1123)
Browse files Browse the repository at this point in the history
  • Loading branch information
AleksandrMatsko authored Nov 26, 2024
1 parent dbaeade commit 520c968
Show file tree
Hide file tree
Showing 17 changed files with 987 additions and 29 deletions.
19 changes: 19 additions & 0 deletions api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ const (
type LimitsConfig struct {
// Trigger contains limits for triggers.
Trigger TriggerLimits
// Trigger contains limits for teams.
Team TeamLimits
}

// TriggerLimits contains all limits applied for triggers.
Expand All @@ -85,5 +87,22 @@ func GetTestLimitsConfig() LimitsConfig {
Trigger: TriggerLimits{
MaxNameSize: DefaultTriggerNameMaxSize,
},
Team: TeamLimits{
MaxNameSize: DefaultTeamNameMaxSize,
MaxDescriptionSize: DefaultTeamDescriptionMaxSize,
},
}
}

const (
DefaultTeamNameMaxSize = 100
DefaultTeamDescriptionMaxSize = 1000
)

// TeamLimits contains all limits applied for triggers.
type TeamLimits struct {
// MaxNameSize is the amount of characters allowed in team name.
MaxNameSize int
// MaxNameSize is the amount of characters allowed in team description.
MaxDescriptionSize int
}
9 changes: 9 additions & 0 deletions api/controller/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,13 @@ func CreateTeam(dataBase moira.Database, team dto.TeamModel, userID string) (dto
return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot generate unique id for team"))
}
}

err := dataBase.SaveTeam(teamID, team.ToMoiraTeam())
if err != nil {
if errors.Is(err, database.ErrTeamWithNameAlreadyExists) {
return dto.SaveTeamResponse{}, api.ErrorInvalidRequest(fmt.Errorf("cannot save team: %w", err))
}

return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot save team: %w", err))
}

Expand Down Expand Up @@ -356,6 +361,10 @@ func AddTeamUsers(dataBase moira.Database, teamID string, newUsers []string) (dt
func UpdateTeam(dataBase moira.Database, teamID string, team dto.TeamModel) (dto.SaveTeamResponse, *api.ErrorResponse) {
err := dataBase.SaveTeam(teamID, team.ToMoiraTeam())
if err != nil {
if errors.Is(err, database.ErrTeamWithNameAlreadyExists) {
return dto.SaveTeamResponse{}, api.ErrorInvalidRequest(fmt.Errorf("cannot save team: %w", err))
}

return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot save team: %w", err))
}
return dto.SaveTeamResponse{ID: teamID}, nil
Expand Down
8 changes: 8 additions & 0 deletions api/controller/team_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ func TestCreateTeam(t *testing.T) {
So(response, ShouldResemble, dto.SaveTeamResponse{})
So(err, ShouldResemble, api.ErrorInternalServer(fmt.Errorf("cannot save team: %w", returnErr)))
})

Convey("team with name already exists error, while saving", func() {
dataBase.EXPECT().GetTeam(gomock.Any()).Return(moira.Team{}, database.ErrNil)
dataBase.EXPECT().SaveTeam(gomock.Any(), team.ToMoiraTeam()).Return(database.ErrTeamWithNameAlreadyExists)
response, err := CreateTeam(dataBase, team, user)
So(response, ShouldResemble, dto.SaveTeamResponse{})
So(err, ShouldResemble, api.ErrorInvalidRequest(fmt.Errorf("cannot save team: %w", database.ErrTeamWithNameAlreadyExists)))
})
})
}

Expand Down
23 changes: 14 additions & 9 deletions api/dto/team.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package dto

import (
"errors"
"fmt"
"net/http"
"unicode/utf8"

"github.com/moira-alert/moira/api/middleware"

"github.com/moira-alert/moira"
)

const (
teamNameLimit = 100
teamDescriptionLimit = 1000
)
var errEmptyTeamName = errors.New("team name cannot be empty")

// TeamModel is a structure that represents team entity in HTTP transfer.
type TeamModel struct {
Expand All @@ -31,15 +31,20 @@ func NewTeamModel(team moira.Team) TeamModel {

// Bind is a method that implements Binder interface from chi and checks that validity of data in request.
func (t TeamModel) Bind(request *http.Request) error {
limits := middleware.GetLimits(request)

if t.Name == "" {
return fmt.Errorf("team name cannot be empty")
return errEmptyTeamName
}
if utf8.RuneCountInString(t.Name) > teamNameLimit {
return fmt.Errorf("team name cannot be longer than %d characters", teamNameLimit)

if utf8.RuneCountInString(t.Name) > limits.Team.MaxNameSize {
return fmt.Errorf("team name cannot be longer than %d characters", limits.Team.MaxNameSize)
}
if utf8.RuneCountInString(t.Description) > teamDescriptionLimit {
return fmt.Errorf("team description cannot be longer than %d characters", teamNameLimit)

if utf8.RuneCountInString(t.Description) > limits.Team.MaxDescriptionSize {
return fmt.Errorf("team description cannot be longer than %d characters", limits.Team.MaxDescriptionSize)
}

return nil
}

Expand Down
57 changes: 57 additions & 0 deletions api/dto/team_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dto

import (
"fmt"
"net/http"
"strings"
"testing"

"github.com/moira-alert/moira/api"
"github.com/moira-alert/moira/api/middleware"

. "github.com/smartystreets/goconvey/convey"
)

func TestTeamValidation(t *testing.T) {
Convey("Test team validation", t, func() {
teamModel := TeamModel{}

limits := api.GetTestLimitsConfig()

request, _ := http.NewRequest("POST", "/api/teams", nil)
request.Header.Set("Content-Type", "application/json")
request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "limits", limits))

Convey("with empty team.Name", func() {
err := teamModel.Bind(request)

So(err, ShouldResemble, errEmptyTeamName)
})

Convey("with team.Name has characters more than in limit", func() {
teamModel.Name = strings.Repeat("ё", limits.Team.MaxNameSize+1)

err := teamModel.Bind(request)

So(err, ShouldResemble, fmt.Errorf("team name cannot be longer than %d characters", limits.Team.MaxNameSize))
})

Convey("with team.Description has characters more than in limit", func() {
teamModel.Name = strings.Repeat("ё", limits.Team.MaxNameSize)
teamModel.Description = strings.Repeat("ё", limits.Team.MaxDescriptionSize+1)

err := teamModel.Bind(request)

So(err, ShouldResemble, fmt.Errorf("team description cannot be longer than %d characters", limits.Team.MaxDescriptionSize))
})

Convey("with valid team", func() {
teamModel.Name = strings.Repeat("ё", limits.Team.MaxNameSize)
teamModel.Description = strings.Repeat("ё", limits.Team.MaxDescriptionSize)

err := teamModel.Bind(request)

So(err, ShouldBeNil)
})
})
}
18 changes: 18 additions & 0 deletions cmd/api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ type apiConfig struct {
type LimitsConfig struct {
// Trigger contains the limits applied to triggers.
Trigger TriggerLimitsConfig `yaml:"trigger"`
// Team contains the limits applied to teams.
Team TeamLimitsConfig `yaml:"team"`
}

// TriggerLimitsConfig represents the limits which will be applied to all triggers.
Expand All @@ -64,12 +66,24 @@ type TriggerLimitsConfig struct {
MaxNameSize int `yaml:"max_name_size"`
}

// TeamLimitsConfig represents the limits which will be applied to all teams.
type TeamLimitsConfig struct {
// MaxNameSize is the max amount of characters allowed in team name.
MaxNameSize int `yaml:"max_name_size"`
// MaxDescriptionSize is the max amount of characters allowed in team description.
MaxDescriptionSize int `yaml:"max_description_size"`
}

// ToLimits converts LimitsConfig to api.LimitsConfig.
func (conf LimitsConfig) ToLimits() api.LimitsConfig {
return api.LimitsConfig{
Trigger: api.TriggerLimits{
MaxNameSize: conf.Trigger.MaxNameSize,
},
Team: api.TeamLimits{
MaxNameSize: conf.Team.MaxNameSize,
MaxDescriptionSize: conf.Team.MaxDescriptionSize,
},
}
}

Expand Down Expand Up @@ -259,6 +273,10 @@ func getDefault() config {
Trigger: TriggerLimitsConfig{
MaxNameSize: api.DefaultTriggerNameMaxSize,
},
Team: TeamLimitsConfig{
MaxNameSize: api.DefaultTeamNameMaxSize,
MaxDescriptionSize: api.DefaultTeamDescriptionMaxSize,
},
},
},
Web: webConfig{
Expand Down
4 changes: 4 additions & 0 deletions cmd/api/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func Test_webConfig_getDefault(t *testing.T) {
Trigger: TriggerLimitsConfig{
MaxNameSize: api.DefaultTriggerNameMaxSize,
},
Team: TeamLimitsConfig{
MaxNameSize: api.DefaultTeamNameMaxSize,
MaxDescriptionSize: api.DefaultTeamDescriptionMaxSize,
},
},
},
Web: webConfig{
Expand Down
27 changes: 27 additions & 0 deletions cmd/cli/from_2.13_to_2.14.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import "github.com/moira-alert/moira"

func updateFrom213(logger moira.Logger, database moira.Database) error {
logger.Info().Msg("Update 2.13 -> 2.14 started")

err := fillTeamNamesHash(logger, database)
if err != nil {
return err
}

logger.Info().Msg("Update 2.13 -> 2.14 was finished")
return nil
}

func downgradeTo213(logger moira.Logger, database moira.Database) error {
logger.Info().Msg("Downgrade 2.14 -> 2.13 started")

err := removeTeamNamesHash(logger, database)
if err != nil {
return err
}

logger.Info().Msg("Downgrade 2.14 -> 2.13 was finished")
return nil
}
24 changes: 23 additions & 1 deletion cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ var (
GoVersion = "unknown"
)

var moiraValidVersions = []string{"2.3", "2.6", "2.7", "2.9", "2.11", "2.12"}
var moiraValidVersions = []string{
"2.3",
"2.6",
"2.7",
"2.9",
"2.11",
"2.12",
"2.13",
}

var (
configFileName = flag.String("config", "/etc/moira/cli.yml", "Path to configuration file")
Expand Down Expand Up @@ -125,6 +133,13 @@ func main() { //nolint
Error(err).
Msg("Fail to update from version 2.12")
}
case "2.13":
err := updateFrom213(logger, database)
if err != nil {
logger.Fatal().
Error(err).
Msg("Fail to update from version 2.13")
}
}
}

Expand Down Expand Up @@ -173,6 +188,13 @@ func main() { //nolint
Error(err).
Msg("Fail to update to version 2.12")
}
case "2.13":
err := downgradeTo213(logger, database)
if err != nil {
logger.Fatal().
Error(err).
Msg("Fail to update to version 2.13")
}
}
}

Expand Down
Loading

0 comments on commit 520c968

Please sign in to comment.