package handlers //nolint:testpackage // testing unexported validateAppName import ( "bytes" "strconv" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sneak.berlin/go/upaas/internal/models" "sneak.berlin/go/upaas/templates" ) func TestValidateAppName(t *testing.T) { t.Parallel() tests := []struct { name string input string wantErr bool }{ {"valid simple", "myapp", false}, {"valid with hyphen", "my-app", false}, {"valid with numbers", "app123", false}, {"valid two chars", "ab", false}, {"valid complex", "my-cool-app-v2", false}, {"valid all numbers", "123", false}, {"valid double hyphen", "my--app", false}, {"valid domain", "sneak.berlin", false}, {"valid two dots", "www.sneak.berlin", false}, {"valid dot and hyphen", "my-app.example.com", false}, {"empty", "", true}, {"single char", "a", true}, {"too long", "a" + string(make([]byte, 63)), true}, { "exactly 63 chars", "a23456789012345678901234567890123456789012345678901234567890123", false, }, { "64 chars", "a234567890123456789012345678901234567890123456789012345678901234", true, }, {"uppercase", "MyApp", true}, {"spaces", "my app", true}, {"starts with hyphen", "-myapp", true}, {"ends with hyphen", "myapp-", true}, {"underscore", "my_app", true}, {"two dots in a row", "a..b", true}, {"starts with dot", ".a", true}, {"ends with dot", "a.", true}, {"only dots", "..", true}, {"hyphen before dot", "a-.b", true}, {"hyphen after dot", "a.-b", true}, {"slash", "my/app", true}, {"path traversal", "../etc/passwd", true}, {"special chars", "app@name!", true}, {"unicode", "appñame", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() err := validateAppName(tt.input) if (err != nil) != tt.wantErr { t.Errorf("validateAppName(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) } }) } } func TestValidateAppNameErrorMentionsDots(t *testing.T) { t.Parallel() err := validateAppName("a..b") require.ErrorIs(t, err, errAppNamePattern) assert.Contains(t, err.Error(), "dots") } // TestAppFormsCheckAppNameLikeServer checks that the name field on the new // and edit app forms has the server's pattern and length limits, and that // its hint mentions dots. func TestAppFormsCheckAppNameLikeServer(t *testing.T) { t.Parallel() pattern := strings.TrimSuffix(strings.TrimPrefix(validAppNameRe.String(), "^"), "$") pages := map[string]map[string]any{ "app_new.html": {}, "app_edit.html": {dataKeyApp: &models.App{}}, } for page, data := range pages { var out bytes.Buffer require.NoError(t, templates.GetParsed().ExecuteTemplate(&out, page, data)) // The name field and its hint, up to the end of their div. _, field, found := strings.Cut(out.String(), `id="name"`) require.True(t, found, page) field, _, _ = strings.Cut(field, "") assert.Contains(t, field, `pattern="`+pattern+`"`, page) assert.Contains(t, field, `minlength="`+strconv.Itoa(appNameMinLength)+`"`, page) assert.Contains(t, field, `maxlength="`+strconv.Itoa(appNameMaxLength)+`"`, page) assert.Contains(t, field, "hyphens, and dots", page) } }