- I think it would be better if one for commands only, one for the spec handling core logic so people won't be confused by the core logic inside the commands folder (cleaner I guess) - Regen docs
377 lines
9.9 KiB
Go
377 lines
9.9 KiB
Go
package tests
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
_ "github.com/versenilvis/iris/commands"
|
|
"github.com/versenilvis/iris/spec"
|
|
)
|
|
|
|
// setupGitRepo creates a real git repo in a temp dir with:
|
|
// - local branches: main (HEAD), dev, feature/login, stable
|
|
// - remote branches: origin/main, origin/dev (written directly to .git/refs)
|
|
// - a tag: v1.0
|
|
// - a stash entry
|
|
func setupGitRepo(t *testing.T) (tmp string, cleanup func()) {
|
|
t.Helper()
|
|
|
|
tmp = t.TempDir()
|
|
ctx := context.Background()
|
|
|
|
run := func(args ...string) {
|
|
t.Helper()
|
|
out, err := exec.CommandContext(ctx, args[0], args[1:]...).CombinedOutput()
|
|
if err != nil {
|
|
t.Logf("git cmd %v: %s", args, out)
|
|
}
|
|
}
|
|
|
|
run("git", "-C", tmp, "init", "--initial-branch=main")
|
|
|
|
// use fallback for older git that doesn't support --initial-branch
|
|
if _, err := os.Stat(filepath.Join(tmp, ".git", "refs", "heads", "main")); err != nil {
|
|
run("git", "-C", tmp, "init")
|
|
}
|
|
|
|
run("git", "-C", tmp, "config", "user.email", "iris-test@example.com") // this is for ci/cd
|
|
run("git", "-C", tmp, "config", "user.name", "Iris Test") // this is for ci/cd
|
|
|
|
// initial commit so branches can be created
|
|
if err := os.WriteFile(filepath.Join(tmp, "file.go"), []byte("package main"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
run("git", "-C", tmp, "add", ".")
|
|
run("git", "-C", tmp, "commit", "-m", "initial")
|
|
|
|
// local branches (incl. slash branch to test tokenization)
|
|
run("git", "-C", tmp, "branch", "dev")
|
|
run("git", "-C", tmp, "branch", "stable")
|
|
run("git", "-C", tmp, "branch", "feature/login")
|
|
|
|
// tag
|
|
run("git", "-C", tmp, "tag", "v1.0")
|
|
|
|
// add a real remote in config
|
|
run("git", "-C", tmp, "remote", "add", "origin", "https://github.com/versenilvis/iris.git")
|
|
|
|
// write fake remote refs directly (no need for actual remote server)
|
|
for _, ref := range []string{"main", "dev"} {
|
|
dir := filepath.Join(tmp, ".git", "refs", "remotes", "origin")
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// point them to the same commit as HEAD for simplicity
|
|
headBytes, err := os.ReadFile(filepath.Join(tmp, ".git", "refs", "heads", "main"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, ref), headBytes, 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// stash entry
|
|
if err := os.WriteFile(filepath.Join(tmp, "dirty.go"), []byte("dirty"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
run("git", "-C", tmp, "add", ".")
|
|
run("git", "-C", tmp, "stash")
|
|
|
|
// chdir into repo so generators can run git commands
|
|
oldWd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Chdir(tmp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cleanup = func() { _ = os.Chdir(oldWd) }
|
|
return tmp, cleanup
|
|
}
|
|
|
|
func TestGitSuggestions(t *testing.T) {
|
|
tmp, cleanup := setupGitRepo(t)
|
|
defer cleanup()
|
|
|
|
t.Run("git top-level", func(t *testing.T) {
|
|
res := spec.Lookup("git ")
|
|
if len(res) < 10 {
|
|
t.Errorf("expected many git subcommands, got %d", len(res))
|
|
}
|
|
})
|
|
|
|
t.Run("tag -d shows tags", func(t *testing.T) {
|
|
res := spec.Lookup("git tag -d ")
|
|
found := false
|
|
for _, r := range res {
|
|
if strings.Contains(r.Cmd, "v1.0") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("git tag -d should suggest v1.0")
|
|
}
|
|
})
|
|
|
|
t.Run("push HEAD options", func(t *testing.T) {
|
|
res := spec.Lookup("git push origin HEAD --")
|
|
found := false
|
|
for _, r := range res {
|
|
if strings.Contains(r.Cmd, "--force") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("git push origin HEAD -- should suggest --force")
|
|
}
|
|
})
|
|
|
|
t.Run("push -u origin suggests branches", func(t *testing.T) {
|
|
res := spec.Lookup("git push -u origin ")
|
|
found := false
|
|
for _, r := range res {
|
|
if strings.Contains(r.Cmd, "dev") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("git push -u origin should suggest branches, got: %v", res)
|
|
}
|
|
})
|
|
|
|
t.Run("push origin suggests active branch", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
out, err := exec.CommandContext(ctx, "git", "rev-parse", "--abbrev-ref", "HEAD").Output()
|
|
if err != nil {
|
|
t.Skip("can't determine HEAD branch")
|
|
}
|
|
activeBranch := strings.TrimSpace(string(out))
|
|
res := spec.Lookup("git push origin ")
|
|
found := false
|
|
for _, r := range res {
|
|
parts := strings.Fields(r.Cmd)
|
|
if len(parts) > 0 && parts[len(parts)-1] == activeBranch {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("git push origin should suggest active branch '%s'", activeBranch)
|
|
}
|
|
if len(res) > 0 {
|
|
parts := strings.Fields(res[0].Cmd)
|
|
if len(parts) == 0 || parts[len(parts)-1] != activeBranch {
|
|
t.Errorf("expected active branch '%s' to be first suggestion, got: %s", activeBranch, res[0].Cmd)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("push origin no duplicate branches", func(t *testing.T) {
|
|
res := spec.Lookup("git push origin ")
|
|
seen := make(map[string]int)
|
|
for _, r := range res {
|
|
parts := strings.Fields(r.Cmd)
|
|
if len(parts) == 0 {
|
|
continue
|
|
}
|
|
branch := parts[len(parts)-1]
|
|
seen[branch]++
|
|
if seen[branch] > 1 {
|
|
t.Errorf("duplicate branch suggestion: %s", branch)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("branch with slash is suggested correctly", func(t *testing.T) {
|
|
res := spec.Lookup("git checkout ")
|
|
found := false
|
|
for _, r := range res {
|
|
if strings.Contains(r.Cmd, "feature/login") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("git checkout should suggest 'feature/login'")
|
|
}
|
|
})
|
|
|
|
t.Run("remote branches suggested for push", func(t *testing.T) {
|
|
res := spec.Lookup("git push origin ")
|
|
var cmdStr strings.Builder
|
|
for _, r := range res {
|
|
cmdStr.WriteString(r.Cmd)
|
|
cmdStr.WriteByte(' ')
|
|
}
|
|
// should have at least dev or main from branch list
|
|
if !strings.Contains(cmdStr.String(), "dev") && !strings.Contains(cmdStr.String(), "main") {
|
|
t.Errorf("git push origin should suggest local branches, got: %s", cmdStr.String())
|
|
}
|
|
})
|
|
|
|
t.Run("active branch not suggested for checkout", func(t *testing.T) {
|
|
// find actual active branch
|
|
ctx := context.Background()
|
|
out, err := exec.CommandContext(ctx, "git", "rev-parse", "--abbrev-ref", "HEAD").Output()
|
|
if err != nil {
|
|
t.Skip("can't determine HEAD branch")
|
|
}
|
|
activeBranch := strings.TrimSpace(string(out))
|
|
|
|
res := spec.Lookup("git checkout ")
|
|
for _, r := range res {
|
|
// the suggestion should not contain the active branch as a standalone word
|
|
parts := strings.FieldsSeq(r.Cmd)
|
|
for p := range parts {
|
|
if p == activeBranch {
|
|
t.Errorf("git checkout should not suggest active branch '%s', got: %s", activeBranch, r.Cmd)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("checkout -b no suggest", func(t *testing.T) {
|
|
res := spec.Lookup("git checkout -b ")
|
|
for _, r := range res {
|
|
if strings.Contains(r.Cmd, "dev") {
|
|
t.Error("git checkout -b should not suggest existing branches")
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("switch -c no suggest", func(t *testing.T) {
|
|
res := spec.Lookup("git switch -c ")
|
|
for _, r := range res {
|
|
if strings.Contains(r.Cmd, "dev") {
|
|
t.Error("git switch -c should not suggest existing branches")
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("stash variants suggest entries", func(t *testing.T) {
|
|
for _, cmd := range []string{"apply", "drop", "pop"} {
|
|
res := spec.Lookup("git stash " + cmd + " ")
|
|
found := false
|
|
for _, r := range res {
|
|
if strings.Contains(r.Cmd, "stash@{0}") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("git stash %s should suggest stash@{0}", cmd)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("remote subcommands suggest remotes", func(t *testing.T) {
|
|
for _, cmd := range []string{"remove", "rename", "set-url"} {
|
|
res := spec.Lookup("git remote " + cmd + " ")
|
|
found := false
|
|
for _, r := range res {
|
|
// origin is our fake remote
|
|
if strings.Contains(r.Cmd, "origin") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("git remote %s should suggest origin", cmd)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("not a git repo no crash", func(t *testing.T) {
|
|
emptyDir := t.TempDir()
|
|
_ = os.Chdir(emptyDir)
|
|
defer func() { _ = os.Chdir(tmp) }()
|
|
_ = spec.Lookup("git status ")
|
|
})
|
|
|
|
t.Run("reset options", func(t *testing.T) {
|
|
_ = spec.Lookup("git reset --soft origin/main ")
|
|
res := spec.Lookup("git reset HEAD ")
|
|
found := false
|
|
for _, r := range res {
|
|
if strings.Contains(r.Cmd, "file.go") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("git reset HEAD should suggest file.go")
|
|
}
|
|
})
|
|
|
|
t.Run("show suggests tags and commits", func(t *testing.T) {
|
|
res := spec.Lookup("git show ")
|
|
foundTag := false
|
|
foundCommit := false
|
|
for _, r := range res {
|
|
if strings.Contains(r.Cmd, "v1.0") {
|
|
foundTag = true
|
|
}
|
|
// commit hashes are 7+ hex chars
|
|
parts := strings.Fields(r.Cmd)
|
|
if len(parts) > 0 {
|
|
h := parts[len(parts)-1]
|
|
if len(h) >= 7 {
|
|
foundCommit = true
|
|
}
|
|
}
|
|
}
|
|
if !foundTag {
|
|
t.Error("git show should suggest tag v1.0")
|
|
}
|
|
if !foundCommit {
|
|
t.Error("git show should suggest commit hashes")
|
|
}
|
|
})
|
|
|
|
t.Run("cherry-pick suggests commits", func(t *testing.T) {
|
|
res := spec.Lookup("git cherry-pick ")
|
|
if len(res) == 0 {
|
|
t.Error("git cherry-pick should suggest commits")
|
|
}
|
|
// all suggestions should be short hex hashes
|
|
for _, r := range res {
|
|
parts := strings.Fields(r.Cmd)
|
|
if len(parts) == 0 {
|
|
continue
|
|
}
|
|
h := parts[len(parts)-1]
|
|
if len(h) < 7 {
|
|
t.Errorf("cherry-pick suggestion looks invalid: %s", r.Cmd)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("revert suggests commits", func(t *testing.T) {
|
|
res := spec.Lookup("git revert ")
|
|
if len(res) == 0 {
|
|
t.Error("git revert should suggest commits")
|
|
}
|
|
})
|
|
|
|
t.Run("global flags don't break subcommand detection", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
out, err := exec.CommandContext(ctx, "git", "rev-parse", "--abbrev-ref", "HEAD").Output()
|
|
if err != nil {
|
|
t.Skip("can't determine HEAD branch")
|
|
}
|
|
activeBranch := strings.TrimSpace(string(out))
|
|
|
|
res := spec.Lookup("git -c core.pager=cat checkout ")
|
|
for _, r := range res {
|
|
parts := strings.FieldsSeq(r.Cmd)
|
|
for p := range parts {
|
|
if p == activeBranch {
|
|
t.Errorf("git -c core.pager=cat checkout should not suggest active branch '%s', got: %s", activeBranch, r.Cmd)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|