Git Repository

Stackyard

Stackyard scannt lokale Entwicklungsordner und bündelt Projekte, Git-Status, TODOs, Toolchains, Docker-Compose-Setups, Datei- und Launcher-Funktionen sowie Operations-Informationen in einer lokalen WebUI.

Projektseite ↗
HTTPShttps://zanvex.de/git/stackyard.git

internal/app/service_test.go

Zum Verzeichnis
package app

import (
	"context"
	"os"
	"os/exec"
	"path/filepath"
	"testing"

	"stackyard/internal/config"
	"stackyard/internal/project"
	"stackyard/internal/storage"
)

func TestServiceCreateAndCheckoutBranch(t *testing.T) {
	root := t.TempDir()
	repo := filepath.Join(root, "repo")
	if err := os.MkdirAll(repo, 0o755); err != nil {
		t.Fatalf("mkdir repo: %v", err)
	}

	runGit(t, repo, "init")
	runGit(t, repo, "config", "user.email", "test@example.com")
	runGit(t, repo, "config", "user.name", "tester")
	if err := os.WriteFile(filepath.Join(repo, "go.mod"), []byte("module repo\n"), 0o644); err != nil {
		t.Fatalf("write go.mod: %v", err)
	}
	runGit(t, repo, "add", "go.mod")
	runGit(t, repo, "commit", "-m", "init")

	store, err := storage.Open(filepath.Join(root, "stackyard.db"))
	if err != nil {
		t.Fatalf("open store: %v", err)
	}
	defer store.Close()

	cfg := config.Config{
		Roots: []config.RootConfig{{Name: "DEV", Path: root}},
		Scanner: config.ScannerConfig{
			MaxDepth:   2,
			IgnoreDirs: []string{"node_modules"},
		},
		Launchers: config.LaunchersConfig{
			VSCode:   config.LauncherConfig{Enabled: false},
			Sublime:  config.LauncherConfig{Enabled: false},
			Terminal: config.LauncherConfig{Enabled: false},
			Explorer: config.LauncherConfig{Enabled: false},
		},
	}

	svc := NewService(cfg, store)
	projects, err := svc.Refresh(context.Background())
	if err != nil {
		t.Fatalf("refresh: %v", err)
	}
	if len(projects) != 1 {
		t.Fatalf("expected 1 project, got %d", len(projects))
	}

	id := projects[0].ID
	if err := svc.CreateBranch(context.Background(), id, "feature/test"); err != nil {
		t.Fatalf("create branch: %v", err)
	}

	p, ok, err := svc.Get(context.Background(), id)
	if err != nil || !ok {
		t.Fatalf("get project after create: ok=%v err=%v", ok, err)
	}
	if p.Git.Branch != "feature/test" {
		t.Fatalf("expected current branch feature/test, got %q", p.Git.Branch)
	}

	if err := svc.CheckoutBranch(context.Background(), id, "master"); err != nil && err.Error() != "branch does not exist" {
		t.Fatalf("unexpected checkout error: %v", err)
	}

	mainBranch := "master"
	if branchExists(t, repo, "main") {
		mainBranch = "main"
	}
	if err := svc.CheckoutBranch(context.Background(), id, mainBranch); err != nil {
		t.Fatalf("checkout branch: %v", err)
	}

	p, ok, err = svc.Get(context.Background(), id)
	if err != nil || !ok {
		t.Fatalf("get project after checkout: ok=%v err=%v", ok, err)
	}
	if p.Git.Branch != mainBranch {
		t.Fatalf("expected current branch %q, got %q", mainBranch, p.Git.Branch)
	}
}

func runGit(t *testing.T, dir string, args ...string) {
	t.Helper()
	cmd := exec.Command("git", args...)
	cmd.Dir = dir
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("git %v failed: %v\n%s", args, err, string(out))
	}
}

func branchExists(t *testing.T, dir, branch string) bool {
	t.Helper()
	cmd := exec.Command("git", "branch", "--list", branch)
	cmd.Dir = dir
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("branch list failed: %v\n%s", err, string(out))
	}
	return string(out) != ""
}

func TestServiceCheckoutRejectsNonRepo(t *testing.T) {
	root := t.TempDir()
	projectDir := filepath.Join(root, "plain")
	if err := os.MkdirAll(projectDir, 0o755); err != nil {
		t.Fatalf("mkdir project: %v", err)
	}
	if err := os.WriteFile(filepath.Join(projectDir, "go.mod"), []byte("module plain\n"), 0o644); err != nil {
		t.Fatalf("write go.mod: %v", err)
	}

	store, err := storage.Open(filepath.Join(root, "stackyard.db"))
	if err != nil {
		t.Fatalf("open store: %v", err)
	}
	defer store.Close()

	svc := NewService(config.Config{
		Roots: []config.RootConfig{{Name: "DEV", Path: root}},
		Scanner: config.ScannerConfig{
			MaxDepth:   2,
			IgnoreDirs: []string{"node_modules"},
		},
	}, store)

	projects, err := svc.Refresh(context.Background())
	if err != nil {
		t.Fatalf("refresh: %v", err)
	}
	if len(projects) != 1 {
		t.Fatalf("expected 1 project, got %d", len(projects))
	}

	err = svc.CheckoutBranch(context.Background(), projects[0].ID, "main")
	if err == nil || err.Error() != "project is not a git repository" {
		t.Fatalf("expected non-repo error, got %v", err)
	}
}

func TestServiceRefreshPreservesFavoriteAndState(t *testing.T) {
	root := t.TempDir()
	repo := filepath.Join(root, "repo")
	if err := os.MkdirAll(repo, 0o755); err != nil {
		t.Fatalf("mkdir repo: %v", err)
	}
	if err := os.WriteFile(filepath.Join(repo, "go.mod"), []byte("module repo\n"), 0o644); err != nil {
		t.Fatalf("write go.mod: %v", err)
	}

	store, err := storage.Open(filepath.Join(root, "stackyard.db"))
	if err != nil {
		t.Fatalf("open store: %v", err)
	}
	defer store.Close()

	svc := NewService(config.Config{
		Roots:   []config.RootConfig{{Name: "DEV", Path: root}},
		Scanner: config.ScannerConfig{MaxDepth: 2},
	}, store)

	projects, err := svc.Refresh(context.Background())
	if err != nil {
		t.Fatalf("refresh: %v", err)
	}
	id := projects[0].ID
	if err := svc.SetFavorite(context.Background(), id, true); err != nil {
		t.Fatalf("set favorite: %v", err)
	}
	if err := svc.SetState(context.Background(), id, project.StatePaused); err != nil {
		t.Fatalf("set state: %v", err)
	}

	projects, err = svc.Refresh(context.Background())
	if err != nil {
		t.Fatalf("refresh second: %v", err)
	}
	if !projects[0].Favorite || projects[0].State != project.StatePaused {
		t.Fatalf("expected favorite/state preserved, got %+v", projects[0])
	}
}