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: team validation #1122

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7bcda5f
refactor: team name and description limits, add team validation tests
AleksandrMatsko Nov 5, 2024
05157df
refactor: save team method to add ream name to set
AleksandrMatsko Nov 5, 2024
e7b50f5
merge master into refactor/team-validation
AleksandrMatsko Nov 5, 2024
539ee34
refactor: save team method
AleksandrMatsko Nov 6, 2024
0b4e263
refactor: saving team
AleksandrMatsko Nov 7, 2024
8d3da0d
merge master into refactor/team-validation
AleksandrMatsko Nov 7, 2024
de04bff
refactor: use redis Hash instead of set
AleksandrMatsko Nov 7, 2024
feb31a0
tests: for saving and deleting teams
AleksandrMatsko Nov 8, 2024
d6b1de1
cli: start migrations
AleksandrMatsko Nov 8, 2024
b9058d1
feat(cli): add update and downgrade migration
AleksandrMatsko Nov 12, 2024
4c8745f
test: for migrations
AleksandrMatsko Nov 12, 2024
48bc3be
feat: add method to moira.Database
AleksandrMatsko Nov 12, 2024
ff5e332
feat: add default values for team limits to local config
AleksandrMatsko Nov 12, 2024
bd8932e
fix: do not use callFunc
AleksandrMatsko Nov 13, 2024
996f411
fix: do not use callFunc
AleksandrMatsko Nov 13, 2024
166e7df
refactor: working with watch
AleksandrMatsko Nov 13, 2024
57b3ab1
refactor: updating migration
AleksandrMatsko Nov 13, 2024
31f2972
style: use linter
AleksandrMatsko Nov 13, 2024
a184a2c
test: fix case
AleksandrMatsko Nov 13, 2024
34caa71
style: change concat of strings
AleksandrMatsko Nov 13, 2024
45a0103
fix: add check for equality of fetched teams and renamed teams
AleksandrMatsko Nov 13, 2024
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 @@ -49,8 +49,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 @@ -295,6 +300,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 @@ -82,6 +82,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