Check / check (pull_request) Skipped
App names may now contain dots, such as sneak.berlin: runs of lowercase letters and numbers joined by single dots or by hyphens, 2 to 63 characters. Docker accepts every such name in the app's image name, upaas-<name>; a dot needs a letter or number on both sides because Docker requires it. The rule also keeps a dot off either end, so the name is safe as a directory and log file name. The new and edit app forms use the same pattern. Their old one was not a valid regular expression under the flag browsers compile it with, so browsers ignored it. A test creates an app named sneak.berlin through the new app form, then builds and deploys it against the fake Docker API and checks the image and container names with Docker's own rules. Model: opus-5-5
114 lines
3.2 KiB
Go
114 lines
3.2 KiB
Go
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, "</div>")
|
|
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)
|
|
}
|
|
}
|