package core
import (
"errors"
"testing"
)
func TestParseCompareSpec(t *testing.T) {
tests := []struct {
name string
raw string
wantBase string
wantHead string
want3Dot bool
wantErr bool
}{
// Happy paths.
{name: "three-dot", raw: "main...feature", wantBase: "main", wantHead: "feature", want3Dot: true},
{name: "two-dot", raw: "main..feature", wantBase: "main", wantHead: "feature", want3Dot: false},
{name: "slash branch head", raw: "main...feature/foo", wantBase: "main", wantHead: "feature/foo", want3Dot: true},
{name: "slash branch both", raw: "feature/with-slash...release/v1", wantBase: "feature/with-slash", wantHead: "release/v1", want3Dot: true},
{name: "two-dot slash", raw: "feature/a..feature/b", wantBase: "feature/a", wantHead: "feature/b", want3Dot: false},
{name: "sha40 vs branch", raw: "0123456789abcdef0123456789abcdef01234567...main",
wantBase: "0123456789abcdef0123456789abcdef01234567", wantHead: "main", want3Dot: true},
{name: "sha256 head", raw: "main.." +
"0000000000000000000000000000000000000000000000000000000000000000",
wantBase: "main", wantHead: "0000000000000000000000000000000000000000000000000000000000000000"},
{name: "percent-escaped slash", raw: "feature%2Ffoo...main", wantBase: "feature/foo", wantHead: "main", want3Dot: true},
{name: "tag with dot", raw: "v1.0.0...v1.1.0", wantBase: "v1.0.0", wantHead: "v1.1.0", want3Dot: true},
// Separator handling.
{name: "no separator", raw: "mainfeature", wantErr: true},
{name: "empty", raw: "", wantErr: true},
{name: "empty base", raw: "...main", wantErr: true},
{name: "empty head", raw: "main...", wantErr: true},
{name: "both empty two-dot", raw: "..", wantErr: true},
{name: "four dots splits on three then leading dot", raw: "a....b", wantErr: true},
// Hostile input — must be rejected via ValidRef.
{name: "option injection head", raw: "main...--upload-pack=x", wantErr: true},
{name: "option injection base", raw: "--output=/etc/passwd...main", wantErr: true},
{name: "revision at-brace", raw: "main...@{u}", wantErr: true},
{name: "trailing .lock", raw: "main...foo.lock", wantErr: true},
{name: "leading dot head", raw: "main....hidden", wantErr: true},
{name: "caret ref", raw: "main...HEAD^", wantErr: true},
{name: "tilde ref", raw: "main...HEAD~3", wantErr: true},
{name: "colon ref", raw: "main...refs:x", wantErr: true},
{name: "space in ref", raw: "main...my branch", wantErr: true},
{name: "control char", raw: "main...he\x00ad", wantErr: true},
{name: "escaped dotdot becomes traversal", raw: "main...%2E%2E", wantErr: true},
{name: "escaped traversal path", raw: "main...%2e%2e%2Fetc", wantErr: true},
{name: "bad percent-encoding", raw: "main...%zz", wantErr: true},
{name: "double slash", raw: "main...a//b", wantErr: true},
{name: "at shorthand", raw: "main...@", wantErr: true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := ParseCompareSpec(tc.raw)
if tc.wantErr {
if err == nil {
t.Fatalf("ParseCompareSpec(%q) = %+v, want error", tc.raw, got)
}
if !errors.Is(err, ErrBadRef) {
t.Fatalf("ParseCompareSpec(%q) error = %v, want wrapping ErrBadRef", tc.raw, err)
}
return
}
if err != nil {
t.Fatalf("ParseCompareSpec(%q) unexpected error: %v", tc.raw, err)
}
if got.Base != tc.wantBase || got.Head != tc.wantHead || got.ThreeDot != tc.want3Dot {
t.Fatalf("ParseCompareSpec(%q) = {Base:%q Head:%q ThreeDot:%v}, want {Base:%q Head:%q ThreeDot:%v}",
tc.raw, got.Base, got.Head, got.ThreeDot, tc.wantBase, tc.wantHead, tc.want3Dot)
}
})
}
}