socle

validate_test.go

package main

import (
	"errors"
	"testing"
)

func TestValidateRepoName(t *testing.T) {
	t.Parallel()
	runs(t, validateRepoName, []vcase{
		{"plain", "parnass", true},
		{"nested", "work/infra/deploy", true},
		{"dots and dashes inside", "a-b.c_d", true},
		{"digits", "repo2", true},
		{"empty", "", false},
		{"leading slash", "/foo", false},
		{"trailing slash", "foo/", false},
		{"double slash", "foo//bar", false},
		{"dot segment", "foo/./bar", false},
		{"dotdot segment", "foo/../bar", false},
		{"leading dot", ".hidden", false},
		{"leading dash", "-flag", false},
		{"leading dash in segment", "ok/-flag", false},
		{"git suffix", "foo.git", false},
		{"git suffix in segment", "foo.git/bar", false},
		{"bare .git segment", "foo/.git", false},
		{"space", "fo o", false},
		{"colon", "fo:o", false},
		{"unicode", "caf\u00e9", false},
	})
}

func TestValidateRef(t *testing.T) {
	t.Parallel()
	runs(t, validateRef, []vcase{
		{"branch", "main", true},
		{"HEAD", "HEAD", true},
		{"tag", "v1.0", true},
		{"slashed branch", "feature/shiny", true},
		{"full sha", "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", true},
		{"empty", "", false},
		{"leading dash", "-option", false},
		{"range", "main..dev", false},
		{"triple dot range", "main...dev", false},
		{"reflog syntax", "HEAD@{1}", false},
		{"parent suffix", "HEAD^", false},
		{"tilde suffix", "HEAD~2", false},
		{"colon path", "main:README.md", false},
		{"space", "ma in", false},
		{"dot-led segment", "foo/.bar", false},
		{"lock suffix", "main.lock", false},
		{"empty segment", "foo//bar", false},
		{"leading slash", "/main", false},
		{"trailing slash", "main/", false},
		{"overlong", string(make([]byte, 256)), false},
	})
}

func TestValidateTreePath(t *testing.T) {
	t.Parallel()
	runs(t, validateTreePath, []vcase{
		{"root", "", true},
		{"file", "README.md", true},
		{"nested", "docs/notes.md", true},
		{"dotfile", ".gitignore", true},
		{"space in name", "docs/with space.txt", true},
		{"unicode", "caf\u00e9/menu.md", true},
		{"leading dash", "-dashed", true}, // safe: paths ride after the colon of <sha>:<path>
		{"dot segment", "./README.md", false},
		{"dotdot segment", "docs/../secret", false},
		{"bare dotdot", "..", false},
		{"leading slash", "/etc/passwd", false},
		{"trailing slash", "docs/", false},
		{"double slash", "docs//notes.md", false},
		{"newline", "do\ncs", false},
		{"nul", "do\x00cs", false},
	})
}

type vcase struct {
	name  string
	input string
	ok    bool
}

// runs drives one validator through a case table,
// checking both acceptance and that every rejection is an errInvalid.
func runs(t *testing.T, validate func(string) error, cases []vcase) {
	t.Helper()
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			err := validate(c.input)
			if c.ok && err != nil {
				t.Errorf("%q: expected acceptance, got %v", c.input, err)
			}
			if !c.ok {
				if err == nil {
					t.Errorf("%q: expected rejection, got acceptance", c.input)
				} else if !errors.Is(err, errInvalid) {
					t.Errorf("%q: rejection should wrap errInvalid, got %v", c.input, err)
				}
			}
		})
	}
}

validate_test.go · 3.0 KiB · ef8eddc0ca