271 lines
6.6 KiB
Go
271 lines
6.6 KiB
Go
package watcher
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"verstak/internal/core/activity"
|
|
"verstak/internal/core/files"
|
|
"verstak/internal/core/nodes"
|
|
"verstak/internal/core/storage"
|
|
)
|
|
|
|
func setupWatcherTest(t *testing.T) (string, *storage.DB, *nodes.Repository, *files.Service, *activity.Service, func()) {
|
|
t.Helper()
|
|
|
|
vaultRoot, err := os.MkdirTemp("", "verstak-watcher-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dbDir := filepath.Join(vaultRoot, ".verstak")
|
|
if err := os.MkdirAll(dbDir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
db, err := storage.Open(filepath.Join(dbDir, "index.db"))
|
|
if err != nil {
|
|
os.RemoveAll(vaultRoot)
|
|
t.Fatalf("open db: %v", err)
|
|
}
|
|
|
|
nodeRepo := nodes.NewRepository(db)
|
|
fileSvc := files.NewService(db, vaultRoot, nodeRepo)
|
|
activitySvc := activity.NewService(db)
|
|
|
|
cleanup := func() {
|
|
db.Close()
|
|
os.RemoveAll(vaultRoot)
|
|
}
|
|
|
|
return vaultRoot, db, nodeRepo, fileSvc, activitySvc, cleanup
|
|
}
|
|
|
|
// tempFileOutsideVault creates a temp file not inside the vault for import.
|
|
func tempFileOutsideVault(t *testing.T, content string) string {
|
|
t.Helper()
|
|
f, err := os.CreateTemp("", "verstak-import-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := f.WriteString(content); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f.Close()
|
|
return f.Name()
|
|
}
|
|
|
|
func TestScanner_NoChanges(t *testing.T) {
|
|
vaultRoot, _, nodeRepo, fileSvc, activitySvc, cleanup := setupWatcherTest(t)
|
|
defer cleanup()
|
|
|
|
node, err := nodeRepo.Create(nil, nodes.TypeFolder, "test-folder", 0, "", "test-folder")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dir := filepath.Join(vaultRoot, "test-folder")
|
|
if err := os.MkdirAll(dir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Import a file into vault (creates record + copies file).
|
|
src := tempFileOutsideVault(t, "hello world")
|
|
defer os.Remove(src)
|
|
_, err = fileSvc.CopyIntoVault(node.ID, src, "test-folder")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
scanner := NewScanner(vaultRoot, nodeRepo, fileSvc, activitySvc)
|
|
result, err := scanner.Run()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if result.MissingFiles != 0 {
|
|
t.Errorf("expected 0 missing, got %d", result.MissingFiles)
|
|
}
|
|
if result.RestoredFiles != 0 {
|
|
t.Errorf("expected 0 restored, got %d", result.RestoredFiles)
|
|
}
|
|
if result.ModifiedFiles != 0 {
|
|
t.Errorf("expected 0 modified, got %d", result.ModifiedFiles)
|
|
}
|
|
if result.NewFiles != 0 {
|
|
t.Errorf("expected 0 new, got %d", result.NewFiles)
|
|
}
|
|
if result.NodesScanned != 1 {
|
|
t.Errorf("expected 1 node scanned, got %d", result.NodesScanned)
|
|
}
|
|
}
|
|
|
|
func TestScanner_MissingFile(t *testing.T) {
|
|
vaultRoot, _, nodeRepo, fileSvc, activitySvc, cleanup := setupWatcherTest(t)
|
|
defer cleanup()
|
|
|
|
node, err := nodeRepo.Create(nil, nodes.TypeFolder, "test-folder", 0, "", "test-folder")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dir := filepath.Join(vaultRoot, "test-folder")
|
|
if err := os.MkdirAll(dir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Import a file.
|
|
src := tempFileOutsideVault(t, "bye")
|
|
defer os.Remove(src)
|
|
_, err = fileSvc.CopyIntoVault(node.ID, src, "test-folder")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Now remove the physical file from vault.
|
|
vaultFiles, err := fileSvc.ListByNode(node.ID)
|
|
if err != nil || len(vaultFiles) == 0 {
|
|
t.Fatal("no vault files found")
|
|
}
|
|
rec := vaultFiles[0]
|
|
absPath := filepath.Join(vaultRoot, rec.Path)
|
|
if err := os.Remove(absPath); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
scanner := NewScanner(vaultRoot, nodeRepo, fileSvc, activitySvc)
|
|
result, err := scanner.Run()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if result.MissingFiles != 1 {
|
|
t.Errorf("expected 1 missing, got %d", result.MissingFiles)
|
|
}
|
|
|
|
// Verify the file record is now marked missing.
|
|
trashed, err := fileSvc.ListTrashedByNode(node.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(trashed) != 1 {
|
|
t.Errorf("expected 1 trashed record, got %d", len(trashed))
|
|
}
|
|
if !trashed[0].Missing {
|
|
t.Error("expected record to be marked missing")
|
|
}
|
|
}
|
|
|
|
func TestScanner_RestoredFile(t *testing.T) {
|
|
vaultRoot, _, nodeRepo, fileSvc, activitySvc, cleanup := setupWatcherTest(t)
|
|
defer cleanup()
|
|
|
|
node, err := nodeRepo.Create(nil, nodes.TypeFolder, "test-folder", 0, "", "test-folder")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dir := filepath.Join(vaultRoot, "test-folder")
|
|
if err := os.MkdirAll(dir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Import a file.
|
|
src := tempFileOutsideVault(t, "back")
|
|
defer os.Remove(src)
|
|
rec, err := fileSvc.CopyIntoVault(node.ID, src, "test-folder")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Mark as missing and remove from disk.
|
|
if err := fileSvc.MarkMissing(rec.ID, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
absPath := filepath.Join(vaultRoot, rec.Path)
|
|
if err := os.Remove(absPath); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Re-create the file.
|
|
if err := os.WriteFile(absPath, []byte("back again"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
scanner := NewScanner(vaultRoot, nodeRepo, fileSvc, activitySvc)
|
|
result, err := scanner.Run()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if result.RestoredFiles != 1 {
|
|
t.Errorf("expected 1 restored, got %d", result.RestoredFiles)
|
|
}
|
|
}
|
|
|
|
func TestScanner_ModifiedFile(t *testing.T) {
|
|
vaultRoot, _, nodeRepo, fileSvc, activitySvc, cleanup := setupWatcherTest(t)
|
|
defer cleanup()
|
|
|
|
node, err := nodeRepo.Create(nil, nodes.TypeFolder, "test-folder", 0, "", "test-folder")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dir := filepath.Join(vaultRoot, "test-folder")
|
|
if err := os.MkdirAll(dir, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Import a file.
|
|
src := tempFileOutsideVault(t, "original")
|
|
defer os.Remove(src)
|
|
_, err = fileSvc.CopyIntoVault(node.ID, src, "test-folder")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Get the record to know the vault path.
|
|
vaultFiles, err := fileSvc.ListByNode(node.ID)
|
|
if err != nil || len(vaultFiles) == 0 {
|
|
t.Fatal("no vault files found")
|
|
}
|
|
rec := vaultFiles[0]
|
|
absPath := filepath.Join(vaultRoot, rec.Path)
|
|
|
|
// Modify the file content.
|
|
time.Sleep(10 * time.Millisecond)
|
|
if err := os.WriteFile(absPath, []byte("modified"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
scanner := NewScanner(vaultRoot, nodeRepo, fileSvc, activitySvc)
|
|
result, err := scanner.Run()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if result.ModifiedFiles != 1 {
|
|
t.Errorf("expected 1 modified, got %d", result.ModifiedFiles)
|
|
}
|
|
}
|
|
|
|
func TestIsHiddenOrMeta(t *testing.T) {
|
|
tests := []struct {
|
|
path string
|
|
expected bool
|
|
}{
|
|
{"/vault/.verstak/config.yml", true},
|
|
{"/vault/.verstak/trash/file.txt", true},
|
|
{"/vault/my-project/file.txt", false},
|
|
{"/vault/.hidden/file.txt", true},
|
|
{"/vault/project/.hidden/file.txt", true},
|
|
{"/vault/project/file.txt", false},
|
|
}
|
|
for _, tc := range tests {
|
|
got := isHiddenOrMeta(tc.path)
|
|
if got != tc.expected {
|
|
t.Errorf("isHiddenOrMeta(%q) = %v, want %v", tc.path, got, tc.expected)
|
|
}
|
|
}
|
|
}
|