verstak/internal/core/nodes/repository_test.go

285 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package nodes
import (
"os"
"path/filepath"
"testing"
"verstak/internal/core/storage"
)
func openTestDB(t *testing.T) *storage.DB {
t.Helper()
dir := t.TempDir()
db, err := storage.Open(filepath.Join(dir, "test.db"))
if err != nil {
t.Fatalf("open db: %v", err)
}
t.Cleanup(func() { db.Close() })
return db
}
func TestSlugify(t *testing.T) {
cases := []struct{ in, want string }{
{"ООО Ромашка", "ооо-ромашка"},
{"My Project!", "my-project"},
{"---", "untitled"},
{"", "untitled"},
{"A B", "a-b"},
{"hello_world", "hello-world"},
}
for _, c := range cases {
got := Slugify(c.in)
if got != c.want {
t.Errorf("Slugify(%q) = %q, want %q", c.in, got, c.want)
}
}
}
func TestCreateAndGet(t *testing.T) {
db := openTestDB(t)
repo := NewRepository(db)
n, err := repo.Create("", TypeCase, "Test Case")
if err != nil {
t.Fatalf("Create: %v", err)
}
if n.ID == "" {
t.Fatal("empty ID")
}
if n.Slug != "test-case" {
t.Errorf("slug = %q, want test-case", n.Slug)
}
got, err := repo.Get(n.ID)
if err != nil {
t.Fatalf("Get: %v", err)
}
if got.Title != "Test Case" {
t.Errorf("title = %q", got.Title)
}
// GetActive on a live node.
if _, err := repo.GetActive(n.ID); err != nil {
t.Errorf("GetActive: %v", err)
}
}
func TestCreateChild(t *testing.T) {
db := openTestDB(t)
repo := NewRepository(db)
parent, err := repo.Create("", TypeFolder, "Folder")
if err != nil {
t.Fatal(err)
}
child, err := repo.Create(parent.ID, TypeCase, "Child")
if err != nil {
t.Fatal(err)
}
if child.ParentID == nil || *child.ParentID != parent.ID {
t.Errorf("parent_id = %v, want %s", child.ParentID, parent.ID)
}
}
func TestListChildren(t *testing.T) {
db := openTestDB(t)
repo := NewRepository(db)
parent, _ := repo.Create("", TypeFolder, "Folder")
repo.Create(parent.ID, TypeCase, "A")
repo.Create(parent.ID, TypeCase, "B")
children, err := repo.ListChildren(parent.ID, false)
if err != nil {
t.Fatal(err)
}
if len(children) != 2 {
t.Errorf("children = %d, want 2", len(children))
}
// Ordered by title.
if children[0].Title != "A" || children[1].Title != "B" {
t.Errorf("order: %s, %s", children[0].Title, children[1].Title)
}
}
func TestListRoots(t *testing.T) {
db := openTestDB(t)
repo := NewRepository(db)
repo.Create("", TypeCase, "One")
repo.Create("", TypeCase, "Two")
roots, err := repo.ListRoots(false)
if err != nil {
t.Fatal(err)
}
if len(roots) != 2 {
t.Errorf("roots = %d, want 2", len(roots))
}
}
func TestUpdateTitle(t *testing.T) {
db := openTestDB(t)
repo := NewRepository(db)
n, _ := repo.Create("", TypeCase, "Old")
if err := repo.UpdateTitle(n.ID, "New Title"); err != nil {
t.Fatal(err)
}
got, _ := repo.Get(n.ID)
if got.Title != "New Title" {
t.Errorf("title = %q", got.Title)
}
if got.Revision != 2 {
t.Errorf("revision = %d, want 2", got.Revision)
}
}
func TestMove(t *testing.T) {
db := openTestDB(t)
repo := NewRepository(db)
a, _ := repo.Create("", TypeFolder, "A")
b, _ := repo.Create("", TypeFolder, "B")
child, _ := repo.Create(a.ID, TypeCase, "Child")
// Move child from A to B.
if err := repo.Move(child.ID, b.ID, 0); err != nil {
t.Fatal(err)
}
got, _ := repo.Get(child.ID)
if got.ParentID == nil || *got.ParentID != b.ID {
t.Errorf("parent = %v, want %s", got.ParentID, b.ID)
}
// Move to root.
if err := repo.Move(child.ID, "", 0); err != nil {
t.Fatal(err)
}
got2, _ := repo.Get(child.ID)
if got2.ParentID != nil {
t.Errorf("parent = %v, want nil", got2.ParentID)
}
}
func TestSoftDelete(t *testing.T) {
db := openTestDB(t)
repo := NewRepository(db)
n, _ := repo.Create("", TypeCase, "To Delete")
if err := repo.SoftDelete(n.ID); err != nil {
t.Fatal(err)
}
if _, err := repo.GetActive(n.ID); err != ErrNotFound {
t.Errorf("GetActive returned %v, want ErrNotFound", err)
}
// Should still be fetchable via Get.
got, err := repo.Get(n.ID)
if err != nil {
t.Fatal(err)
}
if !got.IsDeleted() {
t.Error("node should be deleted")
}
// ListChildren without includeDeleted must skip it.
parent, _ := repo.Create("", TypeFolder, "P")
child, _ := repo.Create(parent.ID, TypeCase, "Kid")
repo.SoftDelete(child.ID)
kids, _ := repo.ListChildren(parent.ID, false)
if len(kids) != 0 {
t.Errorf("expected 0 children, got %d", len(kids))
}
kidsAll, _ := repo.ListChildren(parent.ID, true)
if len(kidsAll) != 1 {
t.Errorf("expected 1 child with deleted, got %d", len(kidsAll))
}
}
func TestMetaKV(t *testing.T) {
db := openTestDB(t)
repo := NewRepository(db)
n, _ := repo.Create("", TypeCase, "M")
if err := repo.MetaSet(n.ID, "status", "active"); err != nil {
t.Fatal(err)
}
v, ok, err := repo.MetaGet(n.ID, "status")
if err != nil {
t.Fatal(err)
}
if !ok || v != "active" {
t.Errorf("meta got %q, ok=%v", v, ok)
}
// Overwrite.
repo.MetaSet(n.ID, "status", "archived")
v, _, _ = repo.MetaGet(n.ID, "status")
if v != "archived" {
t.Errorf("meta = %q, want archived", v)
}
list, err := repo.MetaList(n.ID)
if err != nil {
t.Fatal(err)
}
if len(list) != 1 {
t.Errorf("meta count = %d", len(list))
}
}
func TestNotFound(t *testing.T) {
db := openTestDB(t)
repo := NewRepository(db)
if _, err := repo.Get("nonexistent"); err != ErrNotFound {
t.Errorf("Get returned %v, want ErrNotFound", err)
}
if err := repo.UpdateTitle("nonexistent", "x"); err != ErrNotFound {
t.Errorf("UpdateTitle returned %v, want ErrNotFound", err)
}
if err := repo.SoftDelete("nonexistent"); err != ErrNotFound {
t.Errorf("SoftDelete returned %v, want ErrNotFound", err)
}
if err := repo.Move("nonexistent", "", 0); err != ErrNotFound {
t.Errorf("Move returned %v, want ErrNotFound", err)
}
}
func TestInitEndToEnd(t *testing.T) {
// Integration-like test: open a temp vault through storage, create and
// read a node — proves the migration + repository stack works.
dir := t.TempDir()
dbPath := filepath.Join(dir, "index.db")
db, err := storage.Open(dbPath)
if err != nil {
t.Fatalf("open db: %v", err)
}
defer db.Close()
repo := NewRepository(db)
n, err := repo.Create("", TypeCase, "Integration Case")
if err != nil {
t.Fatal(err)
}
got, err := repo.Get(n.ID)
if err != nil {
t.Fatal(err)
}
if got.Title != "Integration Case" {
t.Errorf("title = %q", got.Title)
}
}
// Silence "os" import; keep unused-reference guard from breaking.
var _ = os.Args