Skip to content

Commit 1537b1e

Browse files
authored
chore(tui): validate profile name (#97)
## What does this PR do? Adds profile name validation.
2 parents 6639f7a + 486a266 commit 1537b1e

File tree

2 files changed

+148
-4
lines changed

2 files changed

+148
-4
lines changed

internal/tui/profile_name_picker.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package tui
33
import (
44
"errors"
55
"fmt"
6+
"regexp"
67

78
"github.com/charmbracelet/bubbles/help"
89
"github.com/charmbracelet/bubbles/key"
@@ -65,6 +66,28 @@ func (p *ProfileNamePicker) SetHeight(height int) {
6566
p.height = height
6667
}
6768

69+
// validateProfileName validates the profile name
70+
func validateProfileName(name string) error {
71+
if name == "" {
72+
return errors.New("profile name cannot be empty")
73+
}
74+
75+
if len(name) > 50 {
76+
return errors.New("profile name must be 50 characters or less")
77+
}
78+
79+
if (name[0] < 'a' || name[0] > 'z') && (name[0] < 'A' || name[0] > 'Z') {
80+
return errors.New("profile name must start with a letter")
81+
}
82+
83+
validName := regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]*$`)
84+
if !validName.MatchString(name) {
85+
return errors.New("profile name can only contain letters, numbers, hyphens, and underscores")
86+
}
87+
88+
return nil
89+
}
90+
6891
func (p *ProfileNamePicker) Update(msg tea.Msg) tea.Cmd {
6992
cmds := []tea.Cmd{}
7093
// nolint:gocritic
@@ -75,10 +98,8 @@ func (p *ProfileNamePicker) Update(msg tea.Msg) tea.Cmd {
7598
cmds = append(cmds, profileNameToogled())
7699
case key.Matches(msg, p.keyMap.Create):
77100
text := p.textInput.Value()
78-
// todo validate profile name, no spaces etc, check other profiles here from cfg
79-
if text == "" {
80-
cmds = append(cmds, OperationStatusCmd(OperationNameCreateProfile,
81-
errors.New("cant create empty name profile")))
101+
if err := validateProfileName(text); err != nil {
102+
cmds = append(cmds, OperationStatusCmd(OperationNameCreateProfile, err))
82103
} else {
83104
p.textInput.SetValue("")
84105
logrus.Debugf("Setting name to: %s", text)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package tui
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestValidateProfileName(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
profileName string
13+
wantErr bool
14+
errContains string
15+
}{
16+
{
17+
name: "valid lowercase",
18+
profileName: "home",
19+
wantErr: false,
20+
},
21+
{
22+
name: "valid with numbers",
23+
profileName: "office2",
24+
wantErr: false,
25+
},
26+
{
27+
name: "valid with hyphens",
28+
profileName: "home-office",
29+
wantErr: false,
30+
},
31+
{
32+
name: "valid with underscores",
33+
profileName: "home_office",
34+
wantErr: false,
35+
},
36+
{
37+
name: "valid complex",
38+
profileName: "my-profile_123",
39+
wantErr: false,
40+
},
41+
{
42+
name: "valid starts with uppercase",
43+
profileName: "Home",
44+
wantErr: false,
45+
},
46+
{
47+
name: "valid all uppercase",
48+
profileName: "HOME",
49+
wantErr: false,
50+
},
51+
{
52+
name: "valid camelCase",
53+
profileName: "homeOffice",
54+
wantErr: false,
55+
},
56+
{
57+
name: "valid PascalCase",
58+
profileName: "HomeOffice",
59+
wantErr: false,
60+
},
61+
{
62+
name: "valid mixed case with numbers",
63+
profileName: "MyProfile123",
64+
wantErr: false,
65+
},
66+
{
67+
name: "empty string",
68+
profileName: "",
69+
wantErr: true,
70+
errContains: "cannot be empty",
71+
},
72+
{
73+
name: "starts with number",
74+
profileName: "1home",
75+
wantErr: true,
76+
errContains: "must start with a letter",
77+
},
78+
{
79+
name: "contains space",
80+
profileName: "home office",
81+
wantErr: true,
82+
errContains: "letters, numbers, hyphens, and underscores",
83+
},
84+
{
85+
name: "contains special characters",
86+
profileName: "home@office",
87+
wantErr: true,
88+
errContains: "letters, numbers, hyphens, and underscores",
89+
},
90+
{
91+
name: "too long",
92+
profileName: "this_is_a_very_long_profile_name_that_exceeds_the_maximum_allowed_length",
93+
wantErr: true,
94+
errContains: "must be 50 characters or less",
95+
},
96+
{
97+
name: "starts with hyphen",
98+
profileName: "-home",
99+
wantErr: true,
100+
errContains: "must start with a letter",
101+
},
102+
{
103+
name: "starts with underscore",
104+
profileName: "_home",
105+
wantErr: true,
106+
errContains: "must start with a letter",
107+
},
108+
}
109+
110+
for _, tt := range tests {
111+
t.Run(tt.name, func(t *testing.T) {
112+
err := validateProfileName(tt.profileName)
113+
if tt.wantErr {
114+
assert.Error(t, err)
115+
if tt.errContains != "" {
116+
assert.Contains(t, err.Error(), tt.errContains)
117+
}
118+
} else {
119+
assert.NoError(t, err)
120+
}
121+
})
122+
}
123+
}

0 commit comments

Comments
 (0)