package main import ( "os" "path/filepath" "testing" ) func TestListProjects(t *testing.T) { dir := t.TempDir() // Create test projects os.MkdirAll(filepath.Join(dir, "alpha"), 0755) os.MkdirAll(filepath.Join(dir, "beta"), 0755) os.MkdirAll(filepath.Join(dir, ".hidden"), 0755) os.WriteFile(filepath.Join(dir, "file.txt"), []byte("not a dir"), 0644) // Add README to alpha os.WriteFile(filepath.Join(dir, "alpha", "README.md"), []byte("# Alpha Project\nSome description"), 0644) t.Run("lists directories only", func(t *testing.T) { projects, err := ListProjects(dir) if err != nil { t.Fatalf("ListProjects: %v", err) } if len(projects) != 2 { t.Fatalf("got %d projects, want 2", len(projects)) } }) t.Run("sorted alphabetically", func(t *testing.T) { projects, err := ListProjects(dir) if err != nil { t.Fatalf("ListProjects: %v", err) } if projects[0].Name != "alpha" { t.Errorf("first = %q, want alpha", projects[0].Name) } if projects[1].Name != "beta" { t.Errorf("second = %q, want beta", projects[1].Name) } }) t.Run("reads README description", func(t *testing.T) { projects, err := ListProjects(dir) if err != nil { t.Fatalf("ListProjects: %v", err) } if !projects[0].HasReadme { t.Error("alpha should have HasReadme=true") } if projects[0].Description != "Alpha Project" { t.Errorf("description = %q, want 'Alpha Project'", projects[0].Description) } }) t.Run("no README", func(t *testing.T) { projects, err := ListProjects(dir) if err != nil { t.Fatalf("ListProjects: %v", err) } if projects[1].HasReadme { t.Error("beta should have HasReadme=false") } if projects[1].Description != "" { t.Errorf("description = %q, want empty", projects[1].Description) } }) t.Run("excludes hidden dirs", func(t *testing.T) { projects, err := ListProjects(dir) if err != nil { t.Fatalf("ListProjects: %v", err) } for _, p := range projects { if p.Name == ".hidden" { t.Error("should not include hidden directories") } } }) t.Run("excludes files", func(t *testing.T) { projects, err := ListProjects(dir) if err != nil { t.Fatalf("ListProjects: %v", err) } for _, p := range projects { if p.Name == "file.txt" { t.Error("should not include files") } } }) t.Run("nonexistent directory", func(t *testing.T) { _, err := ListProjects("/nonexistent/path") if err == nil { t.Fatal("expected error for nonexistent dir") } }) t.Run("empty directory", func(t *testing.T) { emptyDir := t.TempDir() projects, err := ListProjects(emptyDir) if err != nil { t.Fatalf("ListProjects: %v", err) } if len(projects) != 0 { t.Errorf("got %d projects, want 0", len(projects)) } }) } func TestCreateProject(t *testing.T) { t.Run("valid name creates directory", func(t *testing.T) { dir := t.TempDir() err := CreateProject(dir, "my-project") if err != nil { t.Fatalf("CreateProject: %v", err) } info, err := os.Stat(filepath.Join(dir, "my-project")) if err != nil { t.Fatalf("directory not created: %v", err) } if !info.IsDir() { t.Error("expected directory, got file") } }) t.Run("valid name with underscore", func(t *testing.T) { dir := t.TempDir() err := CreateProject(dir, "my_project_2") if err != nil { t.Fatalf("CreateProject: %v", err) } if _, err := os.Stat(filepath.Join(dir, "my_project_2")); err != nil { t.Fatalf("directory not created: %v", err) } }) t.Run("duplicate name returns error", func(t *testing.T) { dir := t.TempDir() os.MkdirAll(filepath.Join(dir, "existing"), 0755) err := CreateProject(dir, "existing") if err == nil { t.Fatal("expected error for duplicate name") } }) t.Run("empty name returns error", func(t *testing.T) { dir := t.TempDir() err := CreateProject(dir, "") if err == nil { t.Fatal("expected error for empty name") } }) t.Run("dot returns error", func(t *testing.T) { dir := t.TempDir() err := CreateProject(dir, ".") if err == nil { t.Fatal("expected error for '.'") } }) t.Run("dotdot returns error", func(t *testing.T) { dir := t.TempDir() err := CreateProject(dir, "..") if err == nil { t.Fatal("expected error for '..'") } }) t.Run("hidden name returns error", func(t *testing.T) { dir := t.TempDir() err := CreateProject(dir, ".secret") if err == nil { t.Fatal("expected error for hidden name") } }) t.Run("special characters return error", func(t *testing.T) { dir := t.TempDir() invalid := []string{"foo bar", "foo/bar", "foo..bar", "a@b", "a!b", "проект"} for _, name := range invalid { err := CreateProject(dir, name) if err == nil { t.Errorf("expected error for name %q", name) } } }) t.Run("name starting with hyphen returns error", func(t *testing.T) { dir := t.TempDir() err := CreateProject(dir, "-project") if err == nil { t.Fatal("expected error for name starting with hyphen") } }) t.Run("name starting with underscore returns error", func(t *testing.T) { dir := t.TempDir() err := CreateProject(dir, "_project") if err == nil { t.Fatal("expected error for name starting with underscore") } }) }