grokkit/internal/workon/workon_test.go

475 lines
13 KiB
Go
Raw Permalink Normal View History

package workon
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestRunRejectsEmptyTitle(t *testing.T) {
err := Run("", "", false, false)
if err == nil {
t.Fatal("expected error for empty title, got nil")
}
if !strings.Contains(err.Error(), "todo_item_title is required") {
t.Errorf("unexpected error message: %s", err.Error())
}
}
func TestMoveQueuedToDoing(t *testing.T) {
tmpDir := t.TempDir()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chdir(origDir) }()
// Set up todo structure
if err := os.MkdirAll("todo/queued", 0755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll("todo/doing", 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile("todo/queued/test-item.md", []byte("# Test Item\n"), 0644); err != nil {
t.Fatal(err)
}
if err := moveQueuedToDoing("test-item"); err != nil {
t.Fatalf("moveQueuedToDoing failed: %v", err)
}
// Verify source is gone
if _, err := os.Stat("todo/queued/test-item.md"); !os.IsNotExist(err) {
t.Error("expected queued file to be removed")
}
// Verify destination exists
if _, err := os.Stat("todo/doing/test-item.md"); err != nil {
t.Errorf("expected doing file to exist: %v", err)
}
}
func TestMoveQueuedToDoingMissingFile(t *testing.T) {
tmpDir := t.TempDir()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chdir(origDir) }()
if err := os.MkdirAll("todo/queued", 0755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll("todo/doing", 0755); err != nil {
t.Fatal(err)
}
err := moveQueuedToDoing("nonexistent")
if err == nil {
t.Fatal("expected error for missing queued file, got nil")
}
}
func TestCreateFixFile(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "my-fix.md")
if err := createFixFile(path, "my-fix"); err != nil {
t.Fatalf("createFixFile failed: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("failed to read created file: %v", err)
}
content := string(data)
if !strings.Contains(content, "# my-fix") {
t.Errorf("expected title heading, got: %s", content)
}
if !strings.Contains(content, "## Work Plan") {
t.Errorf("expected Work Plan heading, got: %s", content)
}
}
func TestReadFileContent(t *testing.T) {
tmpDir := t.TempDir()
t.Run("existing file", func(t *testing.T) {
path := filepath.Join(tmpDir, "exists.md")
expected := "hello world"
if err := os.WriteFile(path, []byte(expected), 0644); err != nil {
t.Fatal(err)
}
got := readFileContent(path)
if got != expected {
t.Errorf("readFileContent = %q, want %q", got, expected)
}
})
t.Run("missing file returns empty string", func(t *testing.T) {
got := readFileContent(filepath.Join(tmpDir, "missing.md"))
if got != "" {
t.Errorf("readFileContent for missing file = %q, want empty", got)
}
})
}
func TestCompleteItemMovesFile(t *testing.T) {
tmpDir := t.TempDir()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chdir(origDir) }()
// Set up todo structure with a doing item
if err := os.MkdirAll("todo/doing", 0755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll("todo/completed", 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile("todo/doing/test-item.md", []byte("# Test\n"), 0644); err != nil {
t.Fatal(err)
}
// completeItem will try to git commit — that will fail in a non-git temp dir,
// but we can verify the file move happened before the commit step
_ = completeItem("test-item", true)
// For a fix, file should be moved regardless of commit outcome
if _, err := os.Stat("todo/completed/test-item.md"); err != nil {
t.Errorf("expected completed file to exist: %v", err)
}
if _, err := os.Stat("todo/doing/test-item.md"); !os.IsNotExist(err) {
t.Error("expected doing file to be removed")
}
}
func TestUpdateReadmeIndex(t *testing.T) {
tmpDir := t.TempDir()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chdir(origDir) }()
if err := os.MkdirAll("todo", 0755); err != nil {
t.Fatal(err)
}
readme := `# Todo Index
## Queued
* [1] [my-feature](queued/my-feature.md): My feature
* [2] [other-item](queued/other-item.md): Other item
## Completed
* [old-item](./completed/old-item.md) : old-item *(done)*
`
if err := os.WriteFile("todo/README.md", []byte(readme), 0644); err != nil {
t.Fatal(err)
}
if err := updateReadmeIndex("my-feature"); err != nil {
t.Fatalf("updateReadmeIndex failed: %v", err)
}
data, err := os.ReadFile("todo/README.md")
if err != nil {
t.Fatal(err)
}
result := string(data)
// Should have removed the queued entry
if strings.Contains(result, "queued/my-feature.md") {
t.Error("expected my-feature to be removed from Queued section")
}
// Should still have the other queued item
if !strings.Contains(result, "queued/other-item.md") {
t.Error("expected other-item to remain in Queued section")
}
// Should have added to Completed section
if !strings.Contains(result, "completed/my-feature.md") {
t.Error("expected my-feature to appear in Completed section")
}
// Old completed item should still be there
if !strings.Contains(result, "completed/old-item.md") {
t.Error("expected old-item to remain in Completed section")
}
}
func TestUpdateReadmeIndexMissingReadme(t *testing.T) {
tmpDir := t.TempDir()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chdir(origDir) }()
err := updateReadmeIndex("anything")
if err == nil {
t.Fatal("expected error when README doesn't exist, got nil")
}
}
// initTempGitRepo creates a temp dir with a git repo and returns it.
// The caller is responsible for chdir-ing back.
func initTempGitRepo(t *testing.T) string {
t.Helper()
tmpDir := t.TempDir()
cmds := [][]string{
{"git", "init"},
{"git", "config", "user.email", "test@test.com"},
{"git", "config", "user.name", "Test"},
}
for _, args := range cmds {
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = tmpDir
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git init setup failed (%v): %s", err, out)
}
}
return tmpDir
}
func TestCreateGitBranchNew(t *testing.T) {
tmpDir := initTempGitRepo(t)
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chdir(origDir) }()
// Need at least one commit for branches to work
if err := os.WriteFile("init.txt", []byte("init"), 0644); err != nil {
t.Fatal(err)
}
_ = exec.Command("git", "add", ".").Run()
_ = exec.Command("git", "commit", "-m", "initial").Run()
if err := createGitBranch("feature/test-branch"); err != nil {
t.Fatalf("createGitBranch failed: %v", err)
}
// Verify we're on the new branch
out, err := exec.Command("git", "branch", "--show-current").Output()
if err != nil {
t.Fatalf("git branch --show-current failed: %v", err)
}
branch := strings.TrimSpace(string(out))
if branch != "feature/test-branch" {
t.Errorf("expected branch 'feature/test-branch', got %q", branch)
}
}
func TestCreateGitBranchExisting(t *testing.T) {
tmpDir := initTempGitRepo(t)
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chdir(origDir) }()
// Initial commit + create branch
if err := os.WriteFile("init.txt", []byte("init"), 0644); err != nil {
t.Fatal(err)
}
_ = exec.Command("git", "add", ".").Run()
_ = exec.Command("git", "commit", "-m", "initial").Run()
_ = exec.Command("git", "checkout", "-b", "fix/existing").Run()
_ = exec.Command("git", "checkout", "-").Run() // back to main/master
// Should checkout existing branch, not error
if err := createGitBranch("fix/existing"); err != nil {
t.Fatalf("createGitBranch for existing branch failed: %v", err)
}
out, _ := exec.Command("git", "branch", "--show-current").Output()
branch := strings.TrimSpace(string(out))
if branch != "fix/existing" {
t.Errorf("expected branch 'fix/existing', got %q", branch)
}
}
func TestCommitChangesInGitRepo(t *testing.T) {
tmpDir := initTempGitRepo(t)
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chdir(origDir) }()
// Initial commit
if err := os.WriteFile("init.txt", []byte("init"), 0644); err != nil {
t.Fatal(err)
}
_ = exec.Command("git", "add", ".").Run()
_ = exec.Command("git", "commit", "-m", "initial").Run()
// Create a todo file to commit
if err := os.MkdirAll("todo/doing", 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile("todo/doing/test.md", []byte("# Test\n"), 0644); err != nil {
t.Fatal(err)
}
if err := commitChanges("test commit message"); err != nil {
t.Fatalf("commitChanges failed: %v", err)
}
// Verify the commit was created
out, err := exec.Command("git", "log", "--oneline", "-1").Output()
if err != nil {
t.Fatalf("git log failed: %v", err)
}
if !strings.Contains(string(out), "test commit message") {
t.Errorf("expected commit message in log, got: %s", out)
}
}
func TestCommitChangesNothingToCommit(t *testing.T) {
tmpDir := initTempGitRepo(t)
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chdir(origDir) }()
// Initial commit
if err := os.WriteFile("init.txt", []byte("init"), 0644); err != nil {
t.Fatal(err)
}
_ = exec.Command("git", "add", ".").Run()
_ = exec.Command("git", "commit", "-m", "initial").Run()
// Create empty todo dir but no files to stage
if err := os.MkdirAll("todo", 0755); err != nil {
t.Fatal(err)
}
// Should fail — nothing to commit
err := commitChanges("empty commit")
if err == nil {
t.Error("expected error when nothing to commit, got nil")
}
}
func TestCompleteItemWithGit(t *testing.T) {
tmpDir := initTempGitRepo(t)
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chdir(origDir) }()
// Initial commit
if err := os.WriteFile("init.txt", []byte("init"), 0644); err != nil {
t.Fatal(err)
}
_ = exec.Command("git", "add", ".").Run()
_ = exec.Command("git", "commit", "-m", "initial").Run()
// Set up todo structure
for _, d := range []string{"todo/doing", "todo/completed"} {
if err := os.MkdirAll(d, 0755); err != nil {
t.Fatal(err)
}
}
if err := os.WriteFile("todo/doing/my-fix.md", []byte("# Fix\n"), 0644); err != nil {
t.Fatal(err)
}
// Stage and commit the doing file first so git tracks it
_ = exec.Command("git", "add", "todo/").Run()
_ = exec.Command("git", "commit", "-m", "add doing item").Run()
// Now complete as a fix (no README update)
if err := completeItem("my-fix", true); err != nil {
t.Fatalf("completeItem failed: %v", err)
}
// Verify moved
if _, err := os.Stat("todo/completed/my-fix.md"); err != nil {
t.Errorf("expected completed file: %v", err)
}
if _, err := os.Stat("todo/doing/my-fix.md"); !os.IsNotExist(err) {
t.Error("expected doing file to be gone")
}
// Verify commit
out, _ := exec.Command("git", "log", "--oneline", "-1").Output()
if !strings.Contains(string(out), "Complete work on my-fix") {
t.Errorf("expected completion commit, got: %s", out)
}
}
func TestCompleteItemNonFixUpdatesReadme(t *testing.T) {
tmpDir := initTempGitRepo(t)
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chdir(origDir) }()
// Initial commit
if err := os.WriteFile("init.txt", []byte("init"), 0644); err != nil {
t.Fatal(err)
}
_ = exec.Command("git", "add", ".").Run()
_ = exec.Command("git", "commit", "-m", "initial").Run()
// Set up todo structure with README
for _, d := range []string{"todo/doing", "todo/completed"} {
if err := os.MkdirAll(d, 0755); err != nil {
t.Fatal(err)
}
}
readme := "# Todo\n\n## Queued\n\n* [my-todo](queued/my-todo.md): My todo\n\n## Completed\n\n"
if err := os.WriteFile("todo/README.md", []byte(readme), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile("todo/doing/my-todo.md", []byte("# My Todo\n"), 0644); err != nil {
t.Fatal(err)
}
_ = exec.Command("git", "add", "todo/").Run()
_ = exec.Command("git", "commit", "-m", "add todo item").Run()
// Complete as a non-fix (should update README)
if err := completeItem("my-todo", false); err != nil {
t.Fatalf("completeItem (non-fix) failed: %v", err)
}
data, err := os.ReadFile("todo/README.md")
if err != nil {
t.Fatal(err)
}
result := string(data)
if strings.Contains(result, "queued/my-todo.md") {
t.Error("expected my-todo removed from Queued")
}
if !strings.Contains(result, "completed/my-todo.md") {
t.Error("expected my-todo added to Completed")
}
}
func TestRunCnaddIfAvailable(t *testing.T) {
// Just exercise the function — it should not panic regardless
// of whether cnadd is installed
runCnaddIfAvailable("test-item")
}
func TestOpenIDEIfConfigured(t *testing.T) {
// With no IDE configured (default), this should silently skip
openIDEIfConfigured()
}