Skip to content

Commit

Permalink
Add tests for invalid team slugs and Slack channel names
Browse files Browse the repository at this point in the history
  • Loading branch information
christeredvartsen committed Oct 30, 2024
1 parent 732a9e0 commit df004c0
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 9 deletions.
117 changes: 117 additions & 0 deletions integration_tests/create_team.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,123 @@ Test.gql("Create team", function(t)
}
end)

Test.gql("Create team with invalid slug", function(t)
local testSlug = function(slugs, errorMessageMatch)
for _, s in ipairs(slugs) do
t.query(string.format([[
mutation {
createTeam(
input: {
slug: "%s"
purpose: "some purpose"
slackChannel: "#channel"
}
) {
team {
id
slug
}
}
}
]], s))
t.check {
data = Null,
errors = {
{
message = errorMessageMatch,
path = {
"createTeam",
"input",
"slug",
},
},
},
}
end
end

local invalidPrefix = {
"team",
"teamfoo",
"team-foo",
}
testSlug(invalidPrefix, Contains("The name prefix 'team' is redundant."))

local shortSlugs = {
"a",
"ab",
}
testSlug(shortSlugs, "A team slug must be at least 3 characters long.")

local longSlugs = {
"some-long-string-more-than-30-chars",
}
testSlug(longSlugs, "A team slug must be at most 30 characters long.")

local reservedSlugs = {
"nais-system",
"kube-system",
"kube-node-lease",
"kube-public",
"kyverno",
"cnrm-system",
"configconnector-operator-system",
}
testSlug(reservedSlugs, "This slug is reserved by NAIS.")

local invalidSlugs = {
"-foo",
"foo-",
"foo--bar",
"4chan",
"you-aint-got-the-æøå",
"Uppercase",
"rollback()",
}
testSlug(invalidSlugs, Contains("A team slug must match the following pattern:"))
end)

Test.gql("Create team with invalid Slack channel name", function(t)
local invalidSlackChannelNames = {
"foo", -- missing hash
"#a", -- too short
"#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", -- too long
"#foo bar", -- space not allowed
"#Foobar", -- upper case not allowed
}
for _, s in ipairs(invalidSlackChannelNames) do
t.query(string.format([[
mutation {
createTeam(
input: {
slug: "myteam"
purpose: "some purpose"
slackChannel: "%s"
}
) {
team {
id
slug
}
}
}
]], s))
t.check {
data = Null,
errors = {
{
message = Contains("A Slack channel name must match the following pattern"),
extensions = {
field = "slackChannel",
},
path = {
"createTeam",
},
},
},
}
end
end)

Test.pubsub("Check if pubsub message was sent", function(t)
t.check("topic", {
Expand Down
2 changes: 1 addition & 1 deletion integration_tests/secrets.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Test.gql("Create secret for team that does not exist", function(t)
createSecret(input: {
name: "secret-name"
environment: "dev"
team: "team-that-does-not-exist"
team: "does-not-exist"
}) {
secret {
id
Expand Down
29 changes: 25 additions & 4 deletions internal/slug/slug.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ func (s Slug) String() string {
return string(s)
}

var slugPattern = regexp.MustCompile(`^[a-z](-?[a-z0-9]+)+$`)

type ErrInvalidSlug struct {
Message string
}
Expand All @@ -45,13 +43,36 @@ func (e *ErrInvalidSlug) GraphError() string {
return e.Message
}

// reservedSlugs is a list of slugs that are reserved and cannot be used for NAIS teams.
var reservedSlugs = []Slug{
"nais-system",
"kube-system",
"kube-node-lease",
"kube-public",
"kyverno",
"cnrm-system",
"configconnector-operator-system",
}

var slugPattern = regexp.MustCompile(`^[a-z](-?[a-z0-9]+)+$`)

func (s Slug) Validate() error {
for _, reserved := range reservedSlugs {
if s == reserved {
return invalid("This slug is reserved by NAIS.")
}
}

if strings.HasPrefix(s.String(), "team") {
return invalid("The name prefix 'team' is redundant. When you create a team, it is by definition a team. Try again with a different name, perhaps just removing the prefix?")
}

if len(s) < 3 {
return invalid("A team slug must be at least 3 characters long.")
}

if len(s) > 80 {
return invalid("A team slug must be at most 80 characters long.")
if len(s) > 30 {
return invalid("A team slug must be at most 30 characters long.")
}

if !slugPattern.MatchString(s.String()) {
Expand Down
12 changes: 8 additions & 4 deletions internal/v1/team/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -345,6 +346,9 @@ type CreateTeamInput struct {
SlackChannel string `json:"slackChannel"`
}

// Rules can be found here: https://api.slack.com/methods/conversations.create#naming
var slackChannelNamePattern = regexp.MustCompile("^#[a-z0-9æøå_-]{2,80}$")

func (i *CreateTeamInput) Validate(ctx context.Context) error {
verr := validate.New()
i.Purpose = strings.TrimSpace(i.Purpose)
Expand All @@ -360,8 +364,8 @@ func (i *CreateTeamInput) Validate(ctx context.Context) error {
verr.Add("purpose", "This is not a valid purpose.")
}

if s := i.SlackChannel; !strings.HasPrefix(s, "#") || len(s) < 3 || len(s) > 80 {
verr.Add("slackChannel", "This is not a valid Slack channel name. A valid channel name starts with a '#' and is between 3 and 80 characters long.")
if !slackChannelNamePattern.MatchString(i.SlackChannel) {
verr.Add("slackChannel", "A Slack channel name must match the following pattern: %q.", slackChannelNamePattern.String())
}

return verr.NilIfEmpty()
Expand Down Expand Up @@ -389,8 +393,8 @@ func (i *UpdateTeamInput) Validate() error {
}

if i.SlackChannel != nil {
if s := *i.SlackChannel; !strings.HasPrefix(s, "#") || len(s) < 3 || len(s) > 80 {
verr.Add("slackChannel", "This is not a valid Slack channel name. A valid channel name starts with a '#' and is between 3 and 80 characters long.")
if !slackChannelNamePattern.MatchString(*i.SlackChannel) {
verr.Add("slackChannel", "A Slack channel name must match the following pattern: %q.", slackChannelNamePattern.String())
}
}

Expand Down

0 comments on commit df004c0

Please sign in to comment.