Go 257 lines
package manifest
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestBuild_SkeletonSortedAndKinded(t *testing.T) {
root := t.TempDir()
dir := "frontend"
base := filepath.Join(root, dir)
if err := os.MkdirAll(filepath.Join(base, "routes"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(base, "App.tsx"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(base, "index.ts"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
m, err := Build(root, dir)
if err != nil {
t.Fatal(err)
}
if m.Dir != "frontend" {
t.Fatalf("Dir = %q, want frontend", m.Dir)
}
want := []Item{
{Path: "App.tsx", Kind: "file"},
{Path: "index.ts", Kind: "file"},
{Path: "routes/", Kind: "dir"},
}
if len(m.Items) != len(want) {
t.Fatalf("items = %+v, want %+v", m.Items, want)
}
for i := range want {
if m.Items[i] != want[i] {
t.Fatalf("item %d = %+v, want %+v", i, m.Items[i], want[i])
}
}
}
func TestWriteAndIdempotentRebuild(t *testing.T) {
root := t.TempDir()
dir := "lib"
base := filepath.Join(root, dir)
if err := os.MkdirAll(base, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(base, "a.go"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
m, err := Build(root, dir)
if err != nil {
t.Fatal(err)
}
if err := Write(root, dir, m); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(filepath.Join(base, FileName)); err != nil {
t.Fatalf("manifest not written: %v", err)
}
// The written .ai.json must not appear in a rebuild.
m2, err := Build(root, dir)
if err != nil {
t.Fatal(err)
}
if len(m2.Items) != 1 || m2.Items[0].Path != "a.go" {
t.Fatalf("rebuild not idempotent: %+v", m2.Items)
}
}
func TestKnowledgeDirs_RecursiveExcludingEngine(t *testing.T) {
userDir := t.TempDir()
// Two top-level knowledge dirs with nested subdirs, plus the engine
// workspace which must be excluded along with everything beneath it.
for _, p := range []string{
filepath.Join("management", "knowledge"),
filepath.Join("management", "roadmap"),
filepath.Join("backend"),
filepath.Join(".eeco", "state"),
} {
if err := os.MkdirAll(filepath.Join(userDir, p), 0o755); err != nil {
t.Fatal(err)
}
}
got, err := KnowledgeDirs(userDir, ".eeco")
if err != nil {
t.Fatal(err)
}
want := []string{
"backend",
"management",
filepath.Join("management", "knowledge"),
filepath.Join("management", "roadmap"),
}
if len(got) != len(want) {
t.Fatalf("dirs = %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("dir %d = %q, want %q (full: %v)", i, got[i], want[i], got)
}
}
}
func TestKnowledgeDirs_MissingUserDirIsNoop(t *testing.T) {
got, err := KnowledgeDirs(filepath.Join(t.TempDir(), "absent"), ".eeco")
if err != nil {
t.Fatalf("err = %v, want nil", err)
}
if got != nil {
t.Fatalf("dirs = %v, want nil", got)
}
}
func TestSubtree_DirPlusNested(t *testing.T) {
userDir := t.TempDir()
for _, p := range []string{
filepath.Join("management", "knowledge"),
filepath.Join("management", "roadmap"),
filepath.Join("backend"),
} {
if err := os.MkdirAll(filepath.Join(userDir, p), 0o755); err != nil {
t.Fatal(err)
}
}
got, err := Subtree(userDir, "management")
if err != nil {
t.Fatal(err)
}
want := []string{
"management",
filepath.Join("management", "knowledge"),
filepath.Join("management", "roadmap"),
}
if len(got) != len(want) {
t.Fatalf("subtree = %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("subtree %d = %q, want %q (full: %v)", i, got[i], want[i], got)
}
}
}
func TestBuild_EmptyDirHasEmptyItems(t *testing.T) {
root := t.TempDir()
dir := "empty"
if err := os.MkdirAll(filepath.Join(root, dir), 0o755); err != nil {
t.Fatal(err)
}
m, err := Build(root, dir)
if err != nil {
t.Fatal(err)
}
if m.Items == nil {
t.Fatal("Items should be a non-nil empty slice")
}
if len(m.Items) != 0 {
t.Fatalf("want empty, got %+v", m.Items)
}
if err := Write(root, dir, m); err != nil {
t.Fatal(err)
}
b, err := os.ReadFile(filepath.Join(root, dir, FileName))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(b), `"items": []`) {
t.Fatalf("empty items should marshal as []:\n%s", b)
}
}
func TestKnowledgeDirs_ExcludesGitRepo(t *testing.T) {
userDir := t.TempDir()
// A real knowledge dir alongside the private workspace-history repo. The
// .git tree and everything beneath it must be excluded — a refresh after
// `init` must never enumerate (and so never write into) ajhahnde/.git.
for _, p := range []string{
"backend",
filepath.Join(".git", "objects", "ab"),
filepath.Join(".git", "refs", "heads"),
} {
if err := os.MkdirAll(filepath.Join(userDir, p), 0o755); err != nil {
t.Fatal(err)
}
}
got, err := KnowledgeDirs(userDir, ".eeco")
if err != nil {
t.Fatal(err)
}
for _, d := range got {
if d == vcsDir || strings.HasPrefix(d, vcsDir+string(filepath.Separator)) {
t.Fatalf("KnowledgeDirs leaked a .git path: %q (full: %v)", d, got)
}
}
if len(got) != 1 || got[0] != "backend" {
t.Fatalf("dirs = %v, want [backend]", got)
}
}
func TestSubtree_ExcludesNestedGit(t *testing.T) {
userDir := t.TempDir()
for _, p := range []string{
filepath.Join("backend", "api"),
filepath.Join("backend", ".git", "objects"),
} {
if err := os.MkdirAll(filepath.Join(userDir, p), 0o755); err != nil {
t.Fatal(err)
}
}
got, err := Subtree(userDir, "backend")
if err != nil {
t.Fatal(err)
}
want := []string{"backend", filepath.Join("backend", "api")}
if len(got) != len(want) {
t.Fatalf("subtree = %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("subtree %d = %q, want %q (full: %v)", i, got[i], want[i], got)
}
}
}
func TestBuild_SkipsGitDir(t *testing.T) {
root := t.TempDir()
dir := "backend"
base := filepath.Join(root, dir)
if err := os.MkdirAll(filepath.Join(base, ".git"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(base, "a.go"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
m, err := Build(root, dir)
if err != nil {
t.Fatal(err)
}
if len(m.Items) != 1 || m.Items[0].Path != "a.go" {
t.Fatalf("Build should skip .git: %+v", m.Items)
}
}