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) } }) } } func TestValidateProjectName(t *testing.T) { tests := []struct { in string want error // nil means accepted }{ {"tarantool", nil}, {"all-docs", nil}, {"home_ops", nil}, {"v1.0", nil}, {strings.Repeat("a", MaxProjectNameLen), nil}, {"", ErrInvalidName}, {strings.Repeat("a", MaxProjectNameLen+1), ErrInvalidName}, {"-dashfirst", ErrInvalidName}, {"has/slash", ErrInvalidName}, {"has..dots", ErrInvalidName}, {".", ErrInvalidName}, {"Upper", ErrInvalidName}, {"has space", ErrInvalidName}, // The '+' is address decoration, never part of the stored name, so a // name that carries one is a caller that failed to strip it. {"+everything", ErrInvalidName}, // Well-formed, but the meta-project is a filter rather than a row: a // project of this name could only shadow an address that already // resolves without one. {MetaProjectName, ErrReservedName}, } for _, tc := range tests { err := ValidateProjectName(tc.in) if tc.want == nil { if err != nil { t.Errorf("ValidateProjectName(%q) = %v, want nil", tc.in, err) } continue } if !errors.Is(err, tc.want) { t.Errorf("ValidateProjectName(%q) = %v, want %v", tc.in, err, tc.want) } } } func TestParseProjectRef(t *testing.T) { tests := []struct { name string in string wantOwner string wantName string ok bool }{ {"tilde form", "~bigbes/+tarantool", "bigbes", "tarantool", true}, {"no tilde", "bigbes/+tarantool", "bigbes", "tarantool", true}, {"surrounding slashes", "/~bigbes/+tarantool/", "bigbes", "tarantool", true}, // The meta-project is an address that resolves to a filter; only // *storing* it is refused, which is ValidateProjectName's job. {"meta project", "~bigbes/+everything", "bigbes", "everything", true}, {"empty", "", "", "", false}, {"one segment", "~bigbes", "", "", false}, {"three segments", "~bigbes/+tarantool/docs", "", "", false}, {"no sigil", "~bigbes/tarantool", "", "", false}, {"sigil only", "~bigbes/+", "", "", false}, {"empty owner", "~/+tarantool", "", "", false}, {"traversal name", "~bigbes/+..", "", "", false}, {"uppercase", "~bigbes/+Tarantool", "", "", false}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ref, err := ParseProjectRef(tc.in) if (err == nil) != tc.ok { t.Fatalf("ParseProjectRef(%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("ParseProjectRef(%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) } // Round-tripping the rendered form must give the same reference, // or a project URL means something different once it has been // through a redirect. again, err := ParseProjectRef(ref.String()) if err != nil || again != ref { t.Fatalf("round trip of %q = %+v, %v", ref, again, err) } }) } } // A project and a space of the same name are different addresses, which is the // whole job of the sigil. func TestProjectAndSpaceNamespacesDoNotCollide(t *testing.T) { sp := SpaceRef{Owner: "bigbes", Name: "rfcs"} pr := ProjectRef{Owner: "bigbes", Name: "rfcs"} if sp.String() == pr.String() { t.Fatalf("space and project render alike: %q", sp) } if _, err := ParseSpaceRef(pr.String()); err == nil { t.Errorf("ParseSpaceRef accepted the project reference %q", pr) } if _, err := ParseProjectRef(sp.String()); err == nil { t.Errorf("ParseProjectRef accepted the space reference %q", sp) } } func TestIsMeta(t *testing.T) { if !(ProjectRef{Owner: "bigbes", Name: MetaProjectName}).IsMeta() { t.Error("the reserved name must be the meta-project") } if (ProjectRef{Owner: "bigbes", Name: "tarantool"}).IsMeta() { t.Error("an ordinary project must not be the meta-project") } }