Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(api, db): team validation #1123

Merged
merged 6 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading