package core
import (
"errors"
"strings"
"testing"
)
func TestValidateOwner(t *testing.T) {
tests := []struct {
in string
ok bool
}{
{"bigbes", true},
{"user_name", true},
{"user-name", true},
{"user.name", true},
{"u123", true},
{"a", true},
{strings.Repeat("a", MaxOwnerLen), true},
{"", false},
{strings.Repeat("a", MaxOwnerLen+1), false},
{"-leading", false},
{"~bigbes", false}, // caller must strip the '~' first
{"has/slash", false},
{"has..dots", false},
{"..", false},
{"Upper", false},
{"has space", false},
{"tilde~", false},
{"emoji😀", false},
{"привет", false},
{"nul\x00byte", false},
}
for _, tc := range tests {
err := ValidateOwner(tc.in)
if (err == nil) != tc.ok {
t.Errorf("ValidateOwner(%q) = %v, want ok=%v", tc.in, err, tc.ok)
}
if err != nil && !errors.Is(err, ErrInvalidName) {
t.Errorf("ValidateOwner(%q) error %v is not ErrInvalidName", tc.in, err)
}
}
}
func TestValidateSpaceName(t *testing.T) {
tests := []struct {
in string
ok bool
}{
{"rfcs", true},
{"tarantool-rfcs", true},
{"home_ops", true},
{"v1.0", true},
{strings.Repeat("a", MaxSpaceNameLen), true},
{"", false},
{strings.Repeat("a", MaxSpaceNameLen+1), false},
{"-dashfirst", false},
{"a/b", false},
{"a..b", false},
{".", false}, // single dot is a legal byte set but "." alone is a directory
{"..", false}, // traversal
{"CamelCase", false},
{"+everything", false}, // project namespace, not a space name
}
for _, tc := range tests {
err := ValidateSpaceName(tc.in)
if (err == nil) != tc.ok {
t.Errorf("ValidateSpaceName(%q) = %v, want ok=%v", tc.in, err, tc.ok)
}
}
}
func TestValidateSpaceNameRejectsBareDot(t *testing.T) {
// "." passes the byte-set rule, so it must be caught somewhere: a space
// literally named "." would resolve to the repos root.
if err := ValidateSpaceName("."); err == nil {
t.Fatal(`ValidateSpaceName(".") must fail: it would resolve to the repos root`)
}
}
func TestParseSpaceRef(t *testing.T) {
tests := []struct {
name string
in string
wantOwner string
wantName string
ok bool
}{
{"tilde form", "~bigbes/rfcs", "bigbes", "rfcs", true},
{"bare form", "bigbes/rfcs", "bigbes", "rfcs", true},
{"leading slash", "/~bigbes/rfcs", "bigbes", "rfcs", true},
{"trailing slash", "~bigbes/rfcs/", "bigbes", "rfcs", true},
{"empty", "", "", "", false},
{"slashes only", "///", "", "", false},
{"one segment", "~bigbes", "", "", false},
{"three segments", "~bigbes/rfcs/docs", "", "", false},
{"empty owner", "~/rfcs", "", "", false},
{"empty name", "~bigbes/", "", "", false},
{"traversal owner", "~../rfcs", "", "", false},
{"traversal name", "~bigbes/..", "", "", false},
{"uppercase owner", "~BigBes/rfcs", "", "", false},
{"space in name", "~bigbes/my rfcs", "", "", false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ref, err := ParseSpaceRef(tc.in)
if (err == nil) != tc.ok {
t.Fatalf("ParseSpaceRef(%q) = %v, %v, want ok=%v", tc.in, ref, err, tc.ok)
}
if !tc.ok {
if !errors.Is(err, ErrInvalidName) {
t.Fatalf("error %v is not ErrInvalidName", err)
}
return
}
if ref.Owner != tc.wantOwner || ref.Name != tc.wantName {
t.Fatalf("ParseSpaceRef(%q) = %+v, want {%q %q}", tc.in, ref, tc.wantOwner, tc.wantName)
}
if got, want := ref.String(), "~"+tc.wantOwner+"/"+tc.wantName; got != want {
t.Fatalf("String() = %q, want %q", got, want)
}
})
}
}
func TestValidatePath(t *testing.T) {
tests := []struct {
name string
in string
ok bool
}{
{"simple", "specs/0007-storage.md", true},
{"root file", "README.md", true},
{"dotfile", ".spec.yml", true},
{"deep", "a/b/c/d/e.md", true},
{"cyrillic", "спеки/хранилище.md", true},
{"attachment", "assets/diagram.png", true},
{"dot in name", "v1.2.3.md", true},
{"leading dot component", "notes/.private.md", true},
{"max length", strings.Repeat("a", MaxPathLen), true},
{"empty", "", false},
{"too long", strings.Repeat("a", MaxPathLen+1), false},
{"absolute", "/etc/passwd", false},
{"trailing slash", "specs/", false},
{"parent traversal", "../etc/passwd", false},
{"embedded traversal", "specs/../../etc/passwd", false},
{"interior traversal", "specs/../notes/a.md", false},
{"dot component", "./a.md", false},
{"interior dot component", "a/./b.md", false},
{"empty component", "specs//a.md", false},
{"git dir", ".git/config", false},
{"nested git dir", "specs/.git/HEAD", false},
{"backslash", `specs\a.md`, false},
{"windows traversal", `..\a.md`, false},
{"nul byte", "specs/a\x00.md", false},
{"newline", "specs/a\nb.md", false},
{"tab", "specs/a\tb.md", false},
{"del", "specs/a\x7f.md", false},
{"rtl override", "specs/exe\u202egnp.md", false},
{"rtl mark", "specs/a\u200fb.md", false},
{"pop directional isolate", "specs/a\u2069b.md", false},
{"invalid utf-8", "specs/\xff\xfe.md", false},
{"trailing dot component", "specs/a./b.md", false},
{"trailing space component", "specs/a /b.md", false},
{"trailing space at end", "specs/a.md ", false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := ValidatePath(tc.in)
if (err == nil) != tc.ok {
t.Fatalf("ValidatePath(%q) = %v, want ok=%v", tc.in, err, tc.ok)
}
if err != nil && !errors.Is(err, ErrInvalidPath) {
t.Fatalf("error %v is not ErrInvalidPath", err)
}
})
}
}
func TestValidateDocPath(t *testing.T) {
tests := []struct {
name string
in string
ok bool
}{
{"spec", "specs/0007-storage.md", true},
{"root", "README.md", true},
{"hidden doc", "notes/.draft.md", true},
{"cyrillic", "спеки/хранилище.md", true},
{"no extension", "specs/0007-storage", false},
{"wrong extension", "specs/0007-storage.markdown", false},
{"uppercase extension", "specs/0007-storage.MD", false},
{"extension only", ".md", false},
{"extension only in dir", "specs/.md", false},
{"extension in middle", "specs/a.md.txt", false},
{"traversal wins", "../a.md", false},
{"empty", "", false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := ValidateDocPath(tc.in)
if (err == nil) != tc.ok {
t.Fatalf("ValidateDocPath(%q) = %v, want ok=%v", tc.in, err, tc.ok)
}
if err != nil && !errors.Is(err, ErrInvalidPath) {
t.Fatalf("error %v is not ErrInvalidPath", err)
}
})
}
}