cli/pkg/cmd/browse/browse_test.go

511 lines
13 KiB
Go

package browse
import (
"fmt"
"net/http"
"os"
"path/filepath"
"testing"
"github.com/cli/cli/v2/git"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/httpmock"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/google/shlex"
"github.com/stretchr/testify/assert"
)
func TestNewCmdBrowse(t *testing.T) {
tests := []struct {
name string
cli string
factory func(*cmdutil.Factory) *cmdutil.Factory
wants BrowseOptions
wantsErr bool
}{
{
name: "no arguments",
cli: "",
wantsErr: false,
},
{
name: "settings flag",
cli: "--settings",
wants: BrowseOptions{
SettingsFlag: true,
},
wantsErr: false,
},
{
name: "projects flag",
cli: "--projects",
wants: BrowseOptions{
ProjectsFlag: true,
},
wantsErr: false,
},
{
name: "wiki flag",
cli: "--wiki",
wants: BrowseOptions{
WikiFlag: true,
},
wantsErr: false,
},
{
name: "no browser flag",
cli: "--no-browser",
wants: BrowseOptions{
NoBrowserFlag: true,
},
wantsErr: false,
},
{
name: "branch flag",
cli: "--branch main",
wants: BrowseOptions{
Branch: "main",
},
wantsErr: false,
},
{
name: "branch flag without a branch name",
cli: "--branch",
wantsErr: true,
},
{
name: "combination: settings projects",
cli: "--settings --projects",
wants: BrowseOptions{
SettingsFlag: true,
ProjectsFlag: true,
},
wantsErr: true,
},
{
name: "combination: projects wiki",
cli: "--projects --wiki",
wants: BrowseOptions{
ProjectsFlag: true,
WikiFlag: true,
},
wantsErr: true,
},
{
name: "passed argument",
cli: "main.go",
wants: BrowseOptions{
SelectorArg: "main.go",
},
wantsErr: false,
},
{
name: "passed two arguments",
cli: "main.go main.go",
wantsErr: true,
},
{
name: "last commit flag",
cli: "-c",
wants: BrowseOptions{
CommitFlag: true,
},
wantsErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := cmdutil.Factory{}
var opts *BrowseOptions
cmd := NewCmdBrowse(&f, func(o *BrowseOptions) error {
opts = o
return nil
})
argv, err := shlex.Split(tt.cli)
assert.NoError(t, err)
cmd.SetArgs(argv)
_, err = cmd.ExecuteC()
if tt.wantsErr {
assert.Error(t, err)
return
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.wants.Branch, opts.Branch)
assert.Equal(t, tt.wants.SelectorArg, opts.SelectorArg)
assert.Equal(t, tt.wants.ProjectsFlag, opts.ProjectsFlag)
assert.Equal(t, tt.wants.WikiFlag, opts.WikiFlag)
assert.Equal(t, tt.wants.NoBrowserFlag, opts.NoBrowserFlag)
assert.Equal(t, tt.wants.SettingsFlag, opts.SettingsFlag)
})
}
}
func setGitDir(t *testing.T, dir string) {
// taken from git_test.go
old_GIT_DIR := os.Getenv("GIT_DIR")
os.Setenv("GIT_DIR", dir)
t.Cleanup(func() {
os.Setenv("GIT_DIR", old_GIT_DIR)
})
}
func Test_runBrowse(t *testing.T) {
s := string(os.PathSeparator)
setGitDir(t, "../../../git/fixtures/simple.git")
tests := []struct {
name string
opts BrowseOptions
baseRepo ghrepo.Interface
defaultBranch string
expectedURL string
wantsErr bool
}{
{
name: "no arguments",
opts: BrowseOptions{
SelectorArg: "",
},
baseRepo: ghrepo.New("jlsestak", "cli"),
expectedURL: "https://github.com/jlsestak/cli",
},
{
name: "settings flag",
opts: BrowseOptions{
SettingsFlag: true,
},
baseRepo: ghrepo.New("bchadwic", "ObscuredByClouds"),
expectedURL: "https://github.com/bchadwic/ObscuredByClouds/settings",
},
{
name: "projects flag",
opts: BrowseOptions{
ProjectsFlag: true,
},
baseRepo: ghrepo.New("ttran112", "7ate9"),
expectedURL: "https://github.com/ttran112/7ate9/projects",
},
{
name: "wiki flag",
opts: BrowseOptions{
WikiFlag: true,
},
baseRepo: ghrepo.New("ravocean", "ThreatLevelMidnight"),
expectedURL: "https://github.com/ravocean/ThreatLevelMidnight/wiki",
},
{
name: "file argument",
opts: BrowseOptions{SelectorArg: "path/to/file.txt"},
baseRepo: ghrepo.New("ken", "mrprofessor"),
defaultBranch: "main",
expectedURL: "https://github.com/ken/mrprofessor/tree/main/path/to/file.txt",
},
{
name: "issue argument",
opts: BrowseOptions{
SelectorArg: "217",
},
baseRepo: ghrepo.New("kevin", "MinTy"),
expectedURL: "https://github.com/kevin/MinTy/issues/217",
},
{
name: "branch flag",
opts: BrowseOptions{
Branch: "trunk",
},
baseRepo: ghrepo.New("jlsestak", "CouldNotThinkOfARepoName"),
expectedURL: "https://github.com/jlsestak/CouldNotThinkOfARepoName/tree/trunk/",
},
{
name: "branch flag with file",
opts: BrowseOptions{
Branch: "trunk",
SelectorArg: "main.go",
},
baseRepo: ghrepo.New("bchadwic", "LedZeppelinIV"),
expectedURL: "https://github.com/bchadwic/LedZeppelinIV/tree/trunk/main.go",
},
{
name: "branch flag within dir",
opts: BrowseOptions{
Branch: "feature-123",
PathFromRepoRoot: func() string { return "pkg/dir" },
},
baseRepo: ghrepo.New("bstnc", "yeepers"),
expectedURL: "https://github.com/bstnc/yeepers/tree/feature-123/",
},
{
name: "branch flag within dir with .",
opts: BrowseOptions{
Branch: "feature-123",
SelectorArg: ".",
PathFromRepoRoot: func() string { return "pkg/dir" },
},
baseRepo: ghrepo.New("bstnc", "yeepers"),
expectedURL: "https://github.com/bstnc/yeepers/tree/feature-123/pkg/dir",
},
{
name: "branch flag within dir with dir",
opts: BrowseOptions{
Branch: "feature-123",
SelectorArg: "inner/more",
PathFromRepoRoot: func() string { return "pkg/dir" },
},
baseRepo: ghrepo.New("bstnc", "yeepers"),
expectedURL: "https://github.com/bstnc/yeepers/tree/feature-123/pkg/dir/inner/more",
},
{
name: "file with line number",
opts: BrowseOptions{
SelectorArg: "path/to/file.txt:32",
},
baseRepo: ghrepo.New("ravocean", "angur"),
defaultBranch: "trunk",
expectedURL: "https://github.com/ravocean/angur/blob/trunk/path/to/file.txt?plain=1#L32",
},
{
name: "file with line range",
opts: BrowseOptions{
SelectorArg: "path/to/file.txt:32-40",
},
baseRepo: ghrepo.New("ravocean", "angur"),
defaultBranch: "trunk",
expectedURL: "https://github.com/ravocean/angur/blob/trunk/path/to/file.txt?plain=1#L32-L40",
},
{
name: "invalid default branch",
opts: BrowseOptions{
SelectorArg: "chocolate-pecan-pie.txt",
},
baseRepo: ghrepo.New("andrewhsu", "recipies"),
defaultBranch: "",
wantsErr: true,
},
{
name: "file with invalid line number after colon",
opts: BrowseOptions{
SelectorArg: "laptime-notes.txt:w-9",
},
baseRepo: ghrepo.New("andrewhsu", "sonoma-raceway"),
wantsErr: true,
},
{
name: "file with invalid file format",
opts: BrowseOptions{
SelectorArg: "path/to/file.txt:32:32",
},
baseRepo: ghrepo.New("ttran112", "ttrain211"),
wantsErr: true,
},
{
name: "file with invalid line number",
opts: BrowseOptions{
SelectorArg: "path/to/file.txt:32a",
},
baseRepo: ghrepo.New("ttran112", "ttrain211"),
wantsErr: true,
},
{
name: "file with invalid line range",
opts: BrowseOptions{
SelectorArg: "path/to/file.txt:32-abc",
},
baseRepo: ghrepo.New("ttran112", "ttrain211"),
wantsErr: true,
},
{
name: "branch with issue number",
opts: BrowseOptions{
SelectorArg: "217",
Branch: "trunk",
},
baseRepo: ghrepo.New("ken", "grc"),
wantsErr: false,
expectedURL: "https://github.com/ken/grc/issues/217",
},
{
name: "opening branch file with line number",
opts: BrowseOptions{
Branch: "first-browse-pull",
SelectorArg: "browse.go:32",
},
baseRepo: ghrepo.New("github", "ThankYouGitHub"),
wantsErr: false,
expectedURL: "https://github.com/github/ThankYouGitHub/blob/first-browse-pull/browse.go?plain=1#L32",
},
{
name: "no browser with branch file and line number",
opts: BrowseOptions{
Branch: "3-0-stable",
SelectorArg: "init.rb:6",
NoBrowserFlag: true,
},
baseRepo: ghrepo.New("mislav", "will_paginate"),
wantsErr: false,
expectedURL: "https://github.com/mislav/will_paginate/blob/3-0-stable/init.rb?plain=1#L6",
},
{
name: "open last commit",
opts: BrowseOptions{
CommitFlag: true,
},
baseRepo: ghrepo.New("vilmibm", "gh-user-status"),
wantsErr: false,
expectedURL: "https://github.com/vilmibm/gh-user-status/tree/6f1a2405cace1633d89a79c74c65f22fe78f9659/",
},
{
name: "open last commit with a file",
opts: BrowseOptions{
CommitFlag: true,
SelectorArg: "main.go",
},
baseRepo: ghrepo.New("vilmibm", "gh-user-status"),
wantsErr: false,
expectedURL: "https://github.com/vilmibm/gh-user-status/tree/6f1a2405cace1633d89a79c74c65f22fe78f9659/main.go",
},
{
name: "relative path from browse_test.go",
opts: BrowseOptions{
SelectorArg: filepath.Join(".", "browse_test.go"),
PathFromRepoRoot: func() string {
return "pkg/cmd/browse/"
},
},
baseRepo: ghrepo.New("bchadwic", "gh-graph"),
defaultBranch: "trunk",
expectedURL: "https://github.com/bchadwic/gh-graph/tree/trunk/pkg/cmd/browse/browse_test.go",
wantsErr: false,
},
{
name: "relative path to file in parent folder from browse_test.go",
opts: BrowseOptions{
SelectorArg: ".." + s + "pr",
PathFromRepoRoot: func() string {
return "pkg/cmd/browse/"
},
},
baseRepo: ghrepo.New("bchadwic", "gh-graph"),
defaultBranch: "trunk",
expectedURL: "https://github.com/bchadwic/gh-graph/tree/trunk/pkg/cmd/pr",
wantsErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
io, _, stdout, stderr := iostreams.Test()
browser := cmdutil.TestBrowser{}
reg := httpmock.Registry{}
defer reg.Verify(t)
if tt.defaultBranch != "" {
reg.StubRepoInfoResponse(tt.baseRepo.RepoOwner(), tt.baseRepo.RepoName(), tt.defaultBranch)
}
opts := tt.opts
opts.IO = io
opts.BaseRepo = func() (ghrepo.Interface, error) {
return tt.baseRepo, nil
}
opts.HttpClient = func() (*http.Client, error) {
return &http.Client{Transport: &reg}, nil
}
opts.Browser = &browser
if opts.PathFromRepoRoot == nil {
opts.PathFromRepoRoot = git.PathFromRepoRoot
}
err := runBrowse(&opts)
if tt.wantsErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
if opts.NoBrowserFlag {
assert.Equal(t, fmt.Sprintf("%s\n", tt.expectedURL), stdout.String())
assert.Equal(t, "", stderr.String())
browser.Verify(t, "")
} else {
assert.Equal(t, "", stdout.String())
assert.Equal(t, "", stderr.String())
browser.Verify(t, tt.expectedURL)
}
})
}
}
func Test_parsePathFromFileArg(t *testing.T) {
s := string(os.PathSeparator)
tests := []struct {
name string
fileArg string
expectedPath string
}{
{
name: "go to parent folder",
fileArg: ".." + s,
expectedPath: "pkg/cmd",
},
{
name: "current folder",
fileArg: ".",
expectedPath: "pkg/cmd/browse",
},
{
name: "current folder (alternative)",
fileArg: "." + s,
expectedPath: "pkg/cmd/browse",
},
{
name: "file that starts with '.'",
fileArg: ".gitignore",
expectedPath: "pkg/cmd/browse/.gitignore",
},
{
name: "file in current folder",
fileArg: filepath.Join(".", "browse.go"),
expectedPath: "pkg/cmd/browse/browse.go",
},
{
name: "file within parent folder",
fileArg: filepath.Join("..", "browse.go"),
expectedPath: "pkg/cmd/browse.go",
},
{
name: "file within parent folder uncleaned",
fileArg: filepath.Join("..", ".") + s + s + s + "browse.go",
expectedPath: "pkg/cmd/browse.go",
},
{
name: "different path from root directory",
fileArg: filepath.Join("..", "..", "..", "internal/build/build.go"),
expectedPath: "internal/build/build.go",
},
{
name: "go out of repository",
fileArg: filepath.Join("..", "..", "..", "..", "..", "..") + s + "",
expectedPath: "",
},
{
name: "go to root of repository",
fileArg: filepath.Join("..", "..", "..") + s + "",
expectedPath: "",
},
{
name: "empty fileArg",
fileArg: "",
expectedPath: "",
},
}
for _, tt := range tests {
path, _, _, _ := parseFile(BrowseOptions{
PathFromRepoRoot: func() string {
return "pkg/cmd/browse/"
}}, tt.fileArg)
assert.Equal(t, tt.expectedPath, path, tt.name)
}
}