324 lines
8.5 KiB
Go
324 lines
8.5 KiB
Go
package files
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"verstak/internal/core/nodes"
|
|
"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 TestAddExternal(t *testing.T) {
|
|
db := openTestDB(t)
|
|
// Run migration 002 manually since storage.Open already applied it.
|
|
// We can verify the table exists by inserting.
|
|
filesSvc := NewService(db, t.TempDir(), nodes.NewRepository(db))
|
|
|
|
// Create a real temp file to register.
|
|
tmpDir := t.TempDir()
|
|
tmpFile := filepath.Join(tmpDir, "test.txt")
|
|
if err := os.WriteFile(tmpFile, []byte("hello world"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rec, err := filesSvc.AddExternal("node-1", tmpFile)
|
|
if err != nil {
|
|
t.Fatalf("AddExternal: %v", err)
|
|
}
|
|
if rec.ID == "" {
|
|
t.Fatal("empty id")
|
|
}
|
|
if rec.Filename != "test.txt" {
|
|
t.Errorf("filename = %q", rec.Filename)
|
|
}
|
|
if rec.StorageMode != "external" {
|
|
t.Errorf("mode = %q", rec.StorageMode)
|
|
}
|
|
if rec.Size != 11 {
|
|
t.Errorf("size = %d, want 11", rec.Size)
|
|
}
|
|
|
|
// Verify stored.
|
|
got, err := filesSvc.Get(rec.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.Filename != "test.txt" {
|
|
t.Errorf("got filename = %q", got.Filename)
|
|
}
|
|
}
|
|
|
|
func TestCopyIntoVault(t *testing.T) {
|
|
db := openTestDB(t)
|
|
vaultRoot := t.TempDir()
|
|
svc := NewService(db, vaultRoot, nodes.NewRepository(db))
|
|
|
|
// Source file.
|
|
srcDir := t.TempDir()
|
|
srcFile := filepath.Join(srcDir, "doc.pdf")
|
|
os.WriteFile(srcFile, []byte("PDF content here"), 0o640)
|
|
|
|
rec, err := svc.CopyIntoVault("node-1", srcFile, "")
|
|
if err != nil {
|
|
t.Fatalf("CopyIntoVault: %v", err)
|
|
}
|
|
if rec.SHA256 == "" {
|
|
t.Error("expected sha256")
|
|
}
|
|
if rec.StorageMode != "vault" {
|
|
t.Errorf("mode = %q", rec.StorageMode)
|
|
}
|
|
|
|
// Verify file on disk.
|
|
if _, err := os.Stat(filepath.Join(vaultRoot, rec.Path)); err != nil {
|
|
t.Errorf("file on disk: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestListByNode(t *testing.T) {
|
|
db := openTestDB(t)
|
|
svc := NewService(db, t.TempDir(), nodes.NewRepository(db))
|
|
|
|
os.WriteFile(filepath.Join(t.TempDir(), "a.txt"), []byte("a"), 0o640)
|
|
f1 := filepath.Join(t.TempDir(), "a1.txt")
|
|
f2 := filepath.Join(t.TempDir(), "a2.txt")
|
|
os.WriteFile(f1, []byte("a"), 0o640)
|
|
os.WriteFile(f2, []byte("bb"), 0o640)
|
|
|
|
svc.AddExternal("node-a", f1)
|
|
svc.AddExternal("node-a", f2)
|
|
|
|
list, err := svc.ListByNode("node-a")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(list) != 2 {
|
|
t.Errorf("list len = %d, want 2", len(list))
|
|
}
|
|
}
|
|
|
|
func TestDeleteToTrash(t *testing.T) {
|
|
db := openTestDB(t)
|
|
vaultRoot := t.TempDir()
|
|
svc := NewService(db, vaultRoot, nodes.NewRepository(db))
|
|
|
|
src := filepath.Join(t.TempDir(), "important.pdf")
|
|
os.WriteFile(src, []byte("important data"), 0o640)
|
|
|
|
rec, _ := svc.CopyIntoVault("node-x", src, "")
|
|
|
|
if err := svc.DeleteToTrash(rec.ID); err != nil {
|
|
t.Fatalf("DeleteToTrash: %v", err)
|
|
}
|
|
|
|
// File record should be gone.
|
|
if _, err := svc.Get(rec.ID); err == nil {
|
|
t.Error("expected error after trash")
|
|
}
|
|
|
|
// Original file should not exist anymore (moved to trash).
|
|
if _, err := os.Stat(filepath.Join(vaultRoot, rec.Path)); !os.IsNotExist(err) {
|
|
t.Error("expected file to be moved from original location")
|
|
}
|
|
|
|
// Trash dir should have it.
|
|
trashDir := filepath.Join(vaultRoot, ".verstak", "trash")
|
|
entries, _ := os.ReadDir(trashDir)
|
|
if len(entries) != 1 {
|
|
t.Errorf("trash entries = %d, want 1", len(entries))
|
|
}
|
|
}
|
|
|
|
func TestAddPathCopySingleFile(t *testing.T) {
|
|
db := openTestDB(t)
|
|
vaultRoot := t.TempDir()
|
|
nodeRepo := nodes.NewRepository(db)
|
|
svc := NewService(db, vaultRoot, nodeRepo)
|
|
|
|
parent, _ := nodeRepo.Create(nil, "case", "Test Case", 0, "", "")
|
|
src := filepath.Join(t.TempDir(), "doc.pdf")
|
|
os.WriteFile(src, []byte("file content"), 0o640)
|
|
|
|
nodes, err := svc.AddPathCopy(parent.ID, src)
|
|
if err != nil {
|
|
t.Fatalf("AddPathCopy: %v", err)
|
|
}
|
|
if len(nodes) != 1 {
|
|
t.Fatalf("got %d nodes, want 1", len(nodes))
|
|
}
|
|
if nodes[0].Type != "file" {
|
|
t.Errorf("type = %q", nodes[0].Type)
|
|
}
|
|
// Source intact.
|
|
if _, err := os.Stat(src); err != nil {
|
|
t.Error("source should remain intact")
|
|
}
|
|
// File record created.
|
|
records, _ := svc.ListByNode(nodes[0].ID)
|
|
if len(records) != 1 {
|
|
t.Errorf("file records = %d", len(records))
|
|
}
|
|
}
|
|
|
|
func TestAddPathLinkSingleFile(t *testing.T) {
|
|
db := openTestDB(t)
|
|
vaultRoot := t.TempDir()
|
|
nodeRepo := nodes.NewRepository(db)
|
|
svc := NewService(db, vaultRoot, nodeRepo)
|
|
|
|
parent, _ := nodeRepo.Create(nil, "case", "Test Case", 0, "", "")
|
|
src := filepath.Join(t.TempDir(), "linked.pdf")
|
|
os.WriteFile(src, []byte("linked"), 0o640)
|
|
|
|
nodes, err := svc.AddPathLink(parent.ID, src)
|
|
if err != nil {
|
|
t.Fatalf("AddPathLink: %v", err)
|
|
}
|
|
if len(nodes) != 1 {
|
|
t.Fatalf("got %d nodes, want 1", len(nodes))
|
|
}
|
|
// File record should have external storage mode.
|
|
records, _ := svc.ListByNode(nodes[0].ID)
|
|
if len(records) != 1 {
|
|
t.Fatalf("file records = %d", len(records))
|
|
}
|
|
if records[0].StorageMode != "external" {
|
|
t.Errorf("storage mode = %q, want external", records[0].StorageMode)
|
|
}
|
|
}
|
|
|
|
func TestAddPathCopyDirectory(t *testing.T) {
|
|
db := openTestDB(t)
|
|
vaultRoot := t.TempDir()
|
|
nodeRepo := nodes.NewRepository(db)
|
|
svc := NewService(db, vaultRoot, nodeRepo)
|
|
|
|
parent, _ := nodeRepo.Create(nil, "case", "Test Case", 0, "", "")
|
|
srcDir := t.TempDir()
|
|
os.MkdirAll(filepath.Join(srcDir, "sub"), 0o750)
|
|
os.WriteFile(filepath.Join(srcDir, "a.txt"), []byte("a"), 0o640)
|
|
os.WriteFile(filepath.Join(srcDir, "sub", "b.txt"), []byte("bb"), 0o640)
|
|
|
|
nodes, err := svc.AddPathCopy(parent.ID, srcDir)
|
|
if err != nil {
|
|
t.Fatalf("AddPathCopy dir: %v", err)
|
|
}
|
|
// Should create: folder node + file node + sub folder node + file node in sub.
|
|
if len(nodes) < 3 {
|
|
t.Errorf("expected 3+ nodes, got %d", len(nodes))
|
|
}
|
|
// Verify structure: root folder + children.
|
|
var folders, files int
|
|
for i := range nodes {
|
|
if nodes[i].Type == "folder" {
|
|
folders++
|
|
} else {
|
|
files++
|
|
}
|
|
}
|
|
if folders < 1 {
|
|
t.Error("expected at least 1 folder")
|
|
}
|
|
if files < 1 {
|
|
t.Error("expected at least 1 file")
|
|
}
|
|
}
|
|
|
|
func TestDeleteNodeAndChildren(t *testing.T) {
|
|
db := openTestDB(t)
|
|
vaultRoot := t.TempDir()
|
|
nodeRepo := nodes.NewRepository(db)
|
|
svc := NewService(db, vaultRoot, nodeRepo)
|
|
|
|
parent, _ := nodeRepo.Create(nil, "case", "To Delete", 0, "", "")
|
|
child, _ := nodeRepo.Create(&parent.ID, "file", "child.txt", 0, "", "")
|
|
// Add file record to child.
|
|
src := filepath.Join(t.TempDir(), "child.txt")
|
|
os.WriteFile(src, []byte("data"), 0o640)
|
|
svc.CopyIntoVault(child.ID, src, "")
|
|
|
|
if err := svc.DeleteNodeAndChildren(parent.ID); err != nil {
|
|
t.Fatalf("DeleteNodeAndChildren: %v", err)
|
|
}
|
|
// Parent should be soft-deleted.
|
|
if _, err := nodeRepo.GetActive(parent.ID); err == nil {
|
|
t.Error("parent should be deleted")
|
|
}
|
|
// Child should be soft-deleted.
|
|
if _, err := nodeRepo.GetActive(child.ID); err == nil {
|
|
t.Error("child should be deleted")
|
|
}
|
|
}
|
|
|
|
func TestNameConflict(t *testing.T) {
|
|
db := openTestDB(t)
|
|
vaultRoot := t.TempDir()
|
|
nodeRepo := nodes.NewRepository(db)
|
|
svc := NewService(db, vaultRoot, nodeRepo)
|
|
|
|
parent, _ := nodeRepo.Create(nil, "case", "Test", 0, "", "")
|
|
src := filepath.Join(t.TempDir(), "conflict.pdf")
|
|
os.WriteFile(src, []byte("data"), 0o640)
|
|
|
|
// Import twice with same filename.
|
|
n1, _ := svc.AddPathCopy(parent.ID, src)
|
|
n2, _ := svc.AddPathCopy(parent.ID, src)
|
|
if n1[0].Title == n2[0].Title {
|
|
t.Error("expected unique name on conflict")
|
|
}
|
|
if n2[0].Title == "conflict.pdf" {
|
|
t.Errorf("title unchanged = %q", n2[0].Title)
|
|
}
|
|
}
|
|
|
|
func TestPreviewImportDir(t *testing.T) {
|
|
db := openTestDB(t)
|
|
vaultRoot := t.TempDir()
|
|
svc := NewService(db, vaultRoot, nodes.NewRepository(db))
|
|
|
|
srcDir := t.TempDir()
|
|
os.MkdirAll(filepath.Join(srcDir, "sub"), 0o750)
|
|
os.WriteFile(filepath.Join(srcDir, "f1.txt"), []byte("hello"), 0o640)
|
|
os.WriteFile(filepath.Join(srcDir, "f2.txt"), []byte("world"), 0o640)
|
|
|
|
sum, err := svc.PreviewImport(srcDir)
|
|
if err != nil {
|
|
t.Fatalf("PreviewImport: %v", err)
|
|
}
|
|
if sum.Files != 2 {
|
|
t.Errorf("files = %d, want 2", sum.Files)
|
|
}
|
|
if sum.Folders != 2 { // root + sub
|
|
t.Errorf("folders = %d, want 2", sum.Folders)
|
|
}
|
|
}
|
|
|
|
func TestGuessMIME(t *testing.T) {
|
|
cases := map[string]string{
|
|
"a.md": "text/plain",
|
|
"a.png": "image/png",
|
|
"a.pdf": "application/pdf",
|
|
"a.docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
"a.go": "text/plain",
|
|
"a.unknown": "application/octet-stream",
|
|
}
|
|
for name, want := range cases {
|
|
got := guessMIME(name)
|
|
if got != want {
|
|
t.Errorf("guessMIME(%q) = %q, want %q", name, got, want)
|
|
}
|
|
}
|
|
}
|