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/storage/sqlite_test.go

Zum Verzeichnis
package storage

import (
	"context"
	"path/filepath"
	"testing"
	"time"

	"stackyard/internal/project"
)

func TestStoreRoundTripProjects(t *testing.T) {
	store, err := Open(filepath.Join(t.TempDir(), "stackyard.db"))
	if err != nil {
		t.Fatalf("open store: %v", err)
	}
	defer store.Close()

	input := []project.Project{
		{
			ID:            "p1",
			Name:          "api",
			RootName:      "DEV",
			Path:          "/tmp/api",
			PrimaryType:   "go",
			State:         project.StateActive,
			MiseTools:     []string{"go 1.24.0"},
			Ports:         []int{3000, 8080},
			ComposeFiles:  []string{"docker-compose.yml"},
			TodoPath:      "/tmp/api/.stackyard/todo.md",
			Markers:       []string{"go.mod", ".git"},
			LastModified:  time.Date(2026, 5, 27, 12, 0, 0, 0, time.UTC),
			LastScannedAt: time.Date(2026, 5, 27, 13, 0, 0, 0, time.UTC),
			Git: project.GitInfo{
				IsRepo:            true,
				Branch:            "main",
				Dirty:             true,
				Ahead:             2,
				Behind:            1,
				LastCommitHash:    "abc",
				LastCommitSubject: "Initial",
				LastCommitAt:      time.Date(2026, 5, 27, 11, 0, 0, 0, time.UTC),
			},
		},
	}

	if err := store.UpsertProjects(context.Background(), input); err != nil {
		t.Fatalf("upsert projects: %v", err)
	}

	got, err := store.ListProjects(context.Background())
	if err != nil {
		t.Fatalf("list projects: %v", err)
	}
	if len(got) != 1 {
		t.Fatalf("expected 1 project, got %d", len(got))
	}
	if got[0].Git.Branch != "main" || got[0].Markers[0] != "go.mod" || got[0].MiseTools[0] != "go 1.24.0" || got[0].Ports[0] != 3000 {
		t.Fatalf("unexpected project: %+v", got[0])
	}
}

func TestStorePrunesMissingProjects(t *testing.T) {
	store, err := Open(filepath.Join(t.TempDir(), "stackyard.db"))
	if err != nil {
		t.Fatalf("open store: %v", err)
	}
	defer store.Close()

	ctx := context.Background()
	if err := store.UpsertProjects(ctx, []project.Project{{ID: "one", Name: "one", RootName: "DEV", Path: "/tmp/one", State: project.StateActive}}); err != nil {
		t.Fatalf("first upsert: %v", err)
	}
	if err := store.UpsertProjects(ctx, []project.Project{{ID: "two", Name: "two", RootName: "DEV", Path: "/tmp/two", State: project.StateActive}}); err != nil {
		t.Fatalf("second upsert: %v", err)
	}

	got, err := store.ListProjects(ctx)
	if err != nil {
		t.Fatalf("list projects: %v", err)
	}
	if len(got) != 1 || got[0].ID != "two" {
		t.Fatalf("expected only pruned project 'two', got %+v", got)
	}
}

func TestStoreSetFavoriteAndState(t *testing.T) {
	store, err := Open(filepath.Join(t.TempDir(), "stackyard.db"))
	if err != nil {
		t.Fatalf("open store: %v", err)
	}
	defer store.Close()

	ctx := context.Background()
	if err := store.UpsertProjects(ctx, []project.Project{{ID: "one", Name: "one", RootName: "DEV", Path: "/tmp/one", State: project.StateActive}}); err != nil {
		t.Fatalf("upsert: %v", err)
	}
	if err := store.SetFavorite(ctx, "one", true); err != nil {
		t.Fatalf("set favorite: %v", err)
	}
	if err := store.SetState(ctx, "one", project.StatePaused); err != nil {
		t.Fatalf("set state: %v", err)
	}

	p, ok, err := store.GetProject(ctx, "one")
	if err != nil || !ok {
		t.Fatalf("get project: ok=%v err=%v", ok, err)
	}
	if !p.Favorite || p.State != project.StatePaused {
		t.Fatalf("unexpected project state: %+v", p)
	}
}

func TestStoreProjectCollections(t *testing.T) {
	store, err := Open(filepath.Join(t.TempDir(), "stackyard.db"))
	if err != nil {
		t.Fatalf("open store: %v", err)
	}
	defer store.Close()

	ctx := context.Background()
	if err := store.UpsertProjects(ctx, []project.Project{{ID: "one", Name: "one", RootName: "DEV", Path: "/tmp/one", State: project.StateActive}}); err != nil {
		t.Fatalf("upsert project: %v", err)
	}
	if err := store.CreateCollection(ctx, "Kundenprojekte"); err != nil {
		t.Fatalf("create collection: %v", err)
	}
	collections, err := store.ListCollections(ctx)
	if err != nil || len(collections) != 1 {
		t.Fatalf("list collections: collections=%+v err=%v", collections, err)
	}
	if err := store.AssignProjectCollection(ctx, "one", collections[0].ID); err != nil {
		t.Fatalf("assign collection: %v", err)
	}
	collections, err = store.ListCollections(ctx)
	if err != nil || len(collections[0].ProjectIDs) != 1 || collections[0].ProjectIDs[0] != "one" {
		t.Fatalf("unexpected assignment: collections=%+v err=%v", collections, err)
	}
	if err := store.DeleteCollection(ctx, collections[0].ID); err != nil {
		t.Fatalf("delete collection: %v", err)
	}
	collections, err = store.ListCollections(ctx)
	if err != nil || len(collections) != 0 {
		t.Fatalf("expected collection deletion, got %+v err=%v", collections, err)
	}
	if _, ok, err := store.GetProject(ctx, "one"); err != nil || !ok {
		t.Fatalf("project must survive collection deletion: ok=%v err=%v", ok, err)
	}
}

func TestStoreProjectMetadata(t *testing.T) {
	store, err := Open(filepath.Join(t.TempDir(), "stackyard.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer store.Close()
	ctx := context.Background()
	if err := store.UpsertProjects(ctx, []project.Project{{ID: "one", Name: "one", RootName: "DEV", Path: "/tmp/one", State: project.StateActive}}); err != nil {
		t.Fatal(err)
	}
	if err := store.SetProjectMetadata(ctx, "one", "Beschreibung", "Notiz", "Go, wichtig, go"); err != nil {
		t.Fatal(err)
	}
	got, ok, err := store.GetProject(ctx, "one")
	if err != nil || !ok {
		t.Fatalf("get metadata: ok=%v err=%v", ok, err)
	}
	if got.Description != "Beschreibung" || got.Notes != "Notiz" || len(got.Labels) != 2 {
		t.Fatalf("unexpected metadata: %+v", got)
	}
}

func TestStoreOperationsMetadata(t *testing.T) {
	store, err := Open(filepath.Join(t.TempDir(), "stackyard.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer store.Close()
	ctx := context.Background()
	if err := store.CreateSnapshot(ctx, "Workspace", []string{"one", "two"}); err != nil {
		t.Fatal(err)
	}
	snapshots, err := store.ListSnapshots(ctx)
	if err != nil || len(snapshots) != 1 || len(snapshots[0].ProjectIDs) != 2 {
		t.Fatalf("unexpected snapshots: %+v err=%v", snapshots, err)
	}
	if err := store.SetScannerRule(ctx, "/tmp/manual", "manual"); err != nil {
		t.Fatal(err)
	}
	if rules, err := store.ListScannerRules(ctx); err != nil || len(rules) != 1 {
		t.Fatalf("unexpected scanner rules: %+v err=%v", rules, err)
	}
	if err := store.SetDependency(ctx, "one", "two"); err != nil {
		t.Fatal(err)
	}
	if dependencies, err := store.ListDependencies(ctx); err != nil || len(dependencies) != 1 {
		t.Fatalf("unexpected dependencies: %+v err=%v", dependencies, err)
	}
	if err := store.AddEvent(ctx, "warning", "test event"); err != nil {
		t.Fatal(err)
	}
	if events, err := store.ListEvents(ctx, 10); err != nil || len(events) != 1 || events[0].Level != "warning" {
		t.Fatalf("unexpected events: %+v err=%v", events, err)
	}
}