diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fe35d0a..8e502ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: go-version: '1.19' cache: true - name: Run tests - run: go test ./... + run: GOCAT_TEST_KUBECONFIG=$HOME/.kube/config go test ./... golangci: name: lint runs-on: ubuntu-latest diff --git a/slackcmd/describe_locks.go b/slackcmd/describe_locks.go new file mode 100644 index 0000000..68c0012 --- /dev/null +++ b/slackcmd/describe_locks.go @@ -0,0 +1,8 @@ +package slackcmd + +type DescribeLocks struct { +} + +func (l *DescribeLocks) Name() string { + return "describe-locks" +} diff --git a/slackcmd/parse.go b/slackcmd/parse.go index 410ce47..d1fadd9 100644 --- a/slackcmd/parse.go +++ b/slackcmd/parse.go @@ -1,17 +1,48 @@ package slackcmd import ( + "errors" "fmt" "regexp" "strings" ) +type PatternError struct { + Pattern string +} + +func (e PatternError) Error() string { + return fmt.Errorf("valid pattern is `%s`", e.Pattern).Error() +} + +func patternError(pattern string) PatternError { + return PatternError{Pattern: pattern} +} + var lockUnlockPattern = regexp.MustCompile(`(unlock|lock) ([0-9a-zA-Z-]+) (staging|production|sandbox|stg|pro|prd)\s*(.*)`) func Parse(text string) (Command, error) { + cmd, err1 := parseLockUnlock(text) + if err1 == nil { + return cmd, nil + } else if !errors.As(err1, &PatternError{}) { + return nil, fmt.Errorf("invalid command %q: %w", text, err1) + } + + cmd, err2 := parseDescribeLocks(text) + if err2 == nil { + return cmd, nil + } else if !errors.As(err2, &PatternError{}) { + return nil, fmt.Errorf("invalid command %q: %w", text, err2) + } + + return nil, fmt.Errorf("invalid command %q: %v, %v", text, err1, err2) +} + +func parseLockUnlock(text string) (Command, error) { match := findLockUnlock(text) if match == nil { - return nil, fmt.Errorf("invalid command %q: valid pattern is 'lock|unlock [for ]", text) + return nil, patternError("lock|unlock [for ]") } var ( @@ -24,7 +55,7 @@ func Parse(text string) (Command, error) { switch command { case "unlock": if reason != "" { - return nil, fmt.Errorf("invalid command %q: unlock command does not accept reason", text) + return nil, errors.New("unlock command does not accept reason") } return &Unlock{ @@ -33,11 +64,11 @@ func Parse(text string) (Command, error) { }, nil case "lock": if reason == "" { - return nil, fmt.Errorf("invalid command %q: lock command requires reason", text) + return nil, errors.New("lock command requires reason") } if !strings.HasPrefix(reason, "for ") { - return nil, fmt.Errorf("invalid command %q: reason must start with 'for'", text) + return nil, errors.New("reason must start with 'for'") } reason = strings.TrimPrefix(reason, "for ") @@ -52,6 +83,14 @@ func Parse(text string) (Command, error) { } } +func parseDescribeLocks(text string) (Command, error) { + if text != "describe locks" { + return nil, patternError("describe locks") + } + + return &DescribeLocks{}, nil +} + func findLockUnlock(text string) [][]string { return lockUnlockPattern.FindAllStringSubmatch(text, -1) } diff --git a/slackcmd/parse_test.go b/slackcmd/parse_test.go index 9b5f93d..4f240bf 100644 --- a/slackcmd/parse_test.go +++ b/slackcmd/parse_test.go @@ -16,10 +16,10 @@ var ( func TestParse(t *testing.T) { type test struct { - name string - text string - want Command - err error + name string + text string + want Command + errMsg string } var tests = []test{} @@ -37,9 +37,9 @@ func TestParse(t *testing.T) { for i, p := range invalidProjects { for j, e := range invalidENvs { tests = append(tests, test{ - name: fmt.Sprintf("lock with invalid project %d and env %d", i, j), - text: fmt.Sprintf("lock %s %s for deployment of revision a", p, e), - err: fmt.Errorf("invalid command %q: valid pattern is 'lock|unlock [for ]", fmt.Sprintf("lock %s %s for deployment of revision a", p, e)), + name: fmt.Sprintf("lock with invalid project %d and env %d", i, j), + text: fmt.Sprintf("lock %s %s for deployment of revision a", p, e), + errMsg: fmt.Sprintf("invalid command %q: valid pattern is `lock|unlock [for ]`, valid pattern is `describe locks`", fmt.Sprintf("lock %s %s for deployment of revision a", p, e)), }) } } @@ -57,36 +57,46 @@ func TestParse(t *testing.T) { for i, p := range invalidProjects { for j, e := range invalidENvs { tests = append(tests, test{ - name: fmt.Sprintf("unlock with invalid project %d and env %d", i, j), - text: fmt.Sprintf("unlock %s %s", p, e), - err: fmt.Errorf("invalid command %q: valid pattern is 'lock|unlock [for ]", fmt.Sprintf("unlock %s %s", p, e)), + name: fmt.Sprintf("unlock with invalid project %d and env %d", i, j), + text: fmt.Sprintf("unlock %s %s", p, e), + errMsg: fmt.Sprintf("invalid command %q: valid pattern is `lock|unlock [for ]`, valid pattern is `describe locks`", fmt.Sprintf("unlock %s %s", p, e)), }) } } tests = append(tests, test{ - name: "unlock has redundant reason", - text: "unlock myproject1 production for deployment of revision a", - err: fmt.Errorf("invalid command %q: unlock command does not accept reason", "unlock myproject1 production for deployment of revision a"), + name: "unlock has redundant reason", + text: "unlock myproject1 production for deployment of revision a", + errMsg: fmt.Sprintf("invalid command %q: unlock command does not accept reason", "unlock myproject1 production for deployment of revision a"), }) tests = append(tests, test{ - name: "lock missing reason", - text: "lock myproject1 production", - err: fmt.Errorf("invalid command %q: lock command requires reason", "lock myproject1 production"), + name: "lock missing reason", + text: "lock myproject1 production", + errMsg: fmt.Sprintf("invalid command %q: lock command requires reason", "lock myproject1 production"), }) tests = append(tests, test{ - name: "unknown command", - text: "unknown myproject1 production for deployment of revision a", - err: fmt.Errorf("invalid command %q: valid pattern is 'lock|unlock [for ]", "unknown myproject1 production for deployment of revision a"), + name: "unknown command", + text: "unknown myproject1 production for deployment of revision a", + errMsg: fmt.Sprintf("invalid command %q: valid pattern is `lock|unlock [for ]`, valid pattern is `describe locks`", "unknown myproject1 production for deployment of revision a"), }) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Parse(tt.text) assert.Equal(t, tt.want, got, "result") - assert.Equal(t, tt.err, err, "error") + var errMsg string + if err != nil { + errMsg = err.Error() + } + assert.Equal(t, tt.errMsg, errMsg, "error") }) } + + t.Run("describe locks", func(t *testing.T) { + got, err := Parse("describe locks") + assert.NoError(t, err) + assert.IsType(t, &DescribeLocks{}, got) + }) }