diff --git a/bundle/config/mutator/populate_current_user.go b/bundle/config/mutator/populate_current_user.go index bba0457c..b604d671 100644 --- a/bundle/config/mutator/populate_current_user.go +++ b/bundle/config/mutator/populate_current_user.go @@ -38,15 +38,17 @@ func (m *populateCurrentUser) Apply(ctx context.Context, b *bundle.Bundle) error return nil } +func replaceNonAlphanumeric(r rune) rune { + if unicode.IsLetter(r) || unicode.IsDigit(r) { + return r + } + return '_' +} + // Get a short-form username, based on the user's primary email address. // We leave the full range of unicode letters in tact, but remove all "special" characters, // including dots, which are not supported in e.g. experiment names. func getShortUserName(emailAddress string) string { - r := []rune(strings.Split(emailAddress, "@")[0]) - for i := 0; i < len(r); i++ { - if !unicode.IsLetter(r[i]) { - r[i] = '_' - } - } - return string(r) + local, _, _ := strings.Cut(emailAddress, "@") + return strings.Map(replaceNonAlphanumeric, local) } diff --git a/bundle/config/mutator/populate_current_user_test.go b/bundle/config/mutator/populate_current_user_test.go index 79ec52b8..bbb65e07 100644 --- a/bundle/config/mutator/populate_current_user_test.go +++ b/bundle/config/mutator/populate_current_user_test.go @@ -1,6 +1,10 @@ package mutator -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestPopulateCurrentUser(t *testing.T) { // We need to implement workspace client mocking to implement this test. @@ -13,28 +17,60 @@ func TestGetShortUserName(t *testing.T) { expected string }{ { - name: "test alphanumeric characters", - email: "test.user@example.com", - expected: "test_user", + email: "test.user.1234@example.com", + expected: "test_user_1234", }, { - name: "test unicode characters", email: "tést.üser@example.com", expected: "tést_üser", }, { - name: "test special characters", email: "test$.user@example.com", expected: "test__user", }, + { + email: `jöhn.dœ@domain.com`, // Using non-ASCII characters. + expected: "jöhn_dœ", + }, + { + email: `first+tag@email.com`, // The plus (+) sign is used for "sub-addressing" in some email services. + expected: "first_tag", + }, + { + email: `email@sub.domain.com`, // Using a sub-domain. + expected: "email", + }, + { + email: `"_quoted"@domain.com`, // Quoted strings can be part of the local-part. + expected: "__quoted_", + }, + { + email: `name-o'mally@website.org`, // Single quote in the local-part. + expected: "name_o_mally", + }, + { + email: `user%domain@external.com`, // Percent sign can be used for email routing in legacy systems. + expected: "user_domain", + }, + { + email: `long.name.with.dots@domain.net`, // Multiple dots in the local-part. + expected: "long_name_with_dots", + }, + { + email: `me&you@together.com`, // Using an ampersand (&) in the local-part. + expected: "me_you", + }, + { + email: `user!def!xyz@domain.org`, // The exclamation mark can be valid in some legacy systems. + expected: "user_def_xyz", + }, + { + email: `admin@ιντερνετ.com`, // Domain in non-ASCII characters (IDN or Internationalized Domain Name). + expected: "admin", + }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := getShortUserName(tt.email) - if result != tt.expected { - t.Errorf("getShortUserName(%q) = %q; expected %q", tt.email, result, tt.expected) - } - }) + assert.Equal(t, tt.expected, getShortUserName(tt.email)) } }