Files
kandev/apps/backend/internal/worktree/store_test.go
T
2026-06-25 10:28:42 +01:00

136 lines
3.4 KiB
Go

package worktree
import (
"context"
"path/filepath"
"sort"
"testing"
"time"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
dbutil "github.com/kandev/kandev/internal/db"
tasksqlite "github.com/kandev/kandev/internal/task/repository/sqlite"
)
// newTestStore opens an in-memory SQLite DB and constructs a *SQLiteStore on it.
// initSchema runs inside NewSQLiteStore so the table is ready.
func newTestStore(t *testing.T) *SQLiteStore {
t.Helper()
db, err := sqlx.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
t.Cleanup(func() { _ = db.Close() })
store, err := NewSQLiteStore(db, db)
if err != nil {
t.Fatalf("new store: %v", err)
}
return store
}
func TestSQLiteStore_ReinitializesSchema(t *testing.T) {
dbConn, err := dbutil.OpenSQLite(filepath.Join(t.TempDir(), "worktree-replay.db"))
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
db := sqlx.NewDb(dbConn, "sqlite3")
t.Cleanup(func() { _ = db.Close() })
if _, err := tasksqlite.NewWithDB(db, db, nil); err != nil {
t.Fatalf("first task schema init: %v", err)
}
if _, err := NewSQLiteStore(db, db); err != nil {
t.Fatalf("first worktree schema init: %v", err)
}
if _, err := tasksqlite.NewWithDB(db, db, nil); err != nil {
t.Fatalf("second task schema init: %v", err)
}
if _, err := NewSQLiteStore(db, db); err != nil {
t.Fatalf("second worktree schema init: %v", err)
}
}
func TestSQLiteStore_ListActiveWorktreePaths(t *testing.T) {
store := newTestStore(t)
ctx := context.Background()
now := time.Now().UTC()
mustCreate := func(wt *Worktree) {
t.Helper()
if err := store.CreateWorktree(ctx, wt); err != nil {
t.Fatalf("create %s: %v", wt.ID, err)
}
}
// Active worktree with a real path — should appear.
mustCreate(&Worktree{
ID: "wt-active-1",
SessionID: "sess-1",
RepositoryID: "repo-1",
Path: "/tmp/kandev/tasks/task1/repoA",
Branch: "feature/task1",
Status: StatusActive,
CreatedAt: now, UpdatedAt: now,
})
// Active worktree with empty path — must be filtered out (the GC
// can't act on an empty path anyway, and the SQL guard prevents
// accidental wildcard matches).
mustCreate(&Worktree{
ID: "wt-active-empty",
SessionID: "sess-2",
RepositoryID: "repo-2",
Path: "",
Branch: "feature/task2",
Status: StatusActive,
CreatedAt: now, UpdatedAt: now,
})
// "Deleted" status worktree — must be filtered out.
deletedAt := now
mustCreate(&Worktree{
ID: "wt-deleted",
SessionID: "sess-3",
RepositoryID: "repo-3",
Path: "/tmp/kandev/tasks/task3/repoA",
Branch: "feature/task3",
Status: "deleted",
CreatedAt: now, UpdatedAt: now,
DeletedAt: &deletedAt,
})
// Second active worktree to confirm ordering doesn't matter.
mustCreate(&Worktree{
ID: "wt-active-2",
SessionID: "sess-4",
RepositoryID: "repo-4",
Path: "/tmp/kandev/tasks/task4/repoB",
Branch: "feature/task4",
Status: StatusActive,
CreatedAt: now, UpdatedAt: now,
})
got, err := store.ListActiveWorktreePaths(ctx)
if err != nil {
t.Fatalf("list paths: %v", err)
}
sort.Strings(got)
want := []string{
"/tmp/kandev/tasks/task1/repoA",
"/tmp/kandev/tasks/task4/repoB",
}
if len(got) != len(want) {
t.Fatalf("got %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Errorf("got[%d] = %q, want %q", i, got[i], want[i])
}
}
}