package database import ( "context" "database/sql" "fmt" "os" "path/filepath" "testing" "time" ) func setupTestDB(t *testing.T) (*DB, func()) { ctx := context.Background() dbPath := filepath.Join(t.TempDir(), "test.db") db, err := New(ctx, dbPath) if err != nil { t.Fatalf("failed to create database: %v", err) } cleanup := func() { if err := db.Close(); err != nil { t.Errorf("failed to close database: %v", err) } } return db, cleanup } func TestFileRepository(t *testing.T) { db, cleanup := setupTestDB(t) defer cleanup() ctx := context.Background() repo := NewFileRepository(db) // Test Create file := &File{ Path: "/test/file.txt", MTime: time.Now().Truncate(time.Second), CTime: time.Now().Truncate(time.Second), Size: 1024, Mode: 0644, UID: 1000, GID: 1000, LinkTarget: "", } err := repo.Create(ctx, nil, file) if err != nil { t.Fatalf("failed to create file: %v", err) } // Test GetByPath retrieved, err := repo.GetByPath(ctx, file.Path) if err != nil { t.Fatalf("failed to get file: %v", err) } if retrieved == nil { t.Fatal("expected file, got nil") } if retrieved.Path != file.Path { t.Errorf("path mismatch: got %s, want %s", retrieved.Path, file.Path) } if !retrieved.MTime.Equal(file.MTime) { t.Errorf("mtime mismatch: got %v, want %v", retrieved.MTime, file.MTime) } if retrieved.Size != file.Size { t.Errorf("size mismatch: got %d, want %d", retrieved.Size, file.Size) } if retrieved.Mode != file.Mode { t.Errorf("mode mismatch: got %o, want %o", retrieved.Mode, file.Mode) } // Test Update (upsert) file.Size = 2048 file.MTime = time.Now().Truncate(time.Second) err = repo.Create(ctx, nil, file) if err != nil { t.Fatalf("failed to update file: %v", err) } retrieved, err = repo.GetByPath(ctx, file.Path) if err != nil { t.Fatalf("failed to get updated file: %v", err) } if retrieved.Size != 2048 { t.Errorf("size not updated: got %d, want %d", retrieved.Size, 2048) } // Test ListModifiedSince files, err := repo.ListModifiedSince(ctx, time.Now().Add(-1*time.Hour)) if err != nil { t.Fatalf("failed to list files: %v", err) } if len(files) != 1 { t.Errorf("expected 1 file, got %d", len(files)) } // Test Delete err = repo.Delete(ctx, nil, file.Path) if err != nil { t.Fatalf("failed to delete file: %v", err) } retrieved, err = repo.GetByPath(ctx, file.Path) if err != nil { t.Fatalf("error getting deleted file: %v", err) } if retrieved != nil { t.Error("expected nil for deleted file") } } func TestFileRepositorySymlink(t *testing.T) { db, cleanup := setupTestDB(t) defer cleanup() ctx := context.Background() repo := NewFileRepository(db) // Test symlink symlink := &File{ Path: "/test/link", MTime: time.Now().Truncate(time.Second), CTime: time.Now().Truncate(time.Second), Size: 0, Mode: uint32(0777 | os.ModeSymlink), UID: 1000, GID: 1000, LinkTarget: "/test/target", } err := repo.Create(ctx, nil, symlink) if err != nil { t.Fatalf("failed to create symlink: %v", err) } retrieved, err := repo.GetByPath(ctx, symlink.Path) if err != nil { t.Fatalf("failed to get symlink: %v", err) } if !retrieved.IsSymlink() { t.Error("expected IsSymlink() to be true") } if retrieved.LinkTarget != symlink.LinkTarget { t.Errorf("link target mismatch: got %s, want %s", retrieved.LinkTarget, symlink.LinkTarget) } } func TestFileRepositoryTransaction(t *testing.T) { db, cleanup := setupTestDB(t) defer cleanup() ctx := context.Background() repos := NewRepositories(db) // Test transaction rollback err := repos.WithTx(ctx, func(ctx context.Context, tx *sql.Tx) error { file := &File{ Path: "/test/tx_file.txt", MTime: time.Now().Truncate(time.Second), CTime: time.Now().Truncate(time.Second), Size: 1024, Mode: 0644, UID: 1000, GID: 1000, } if err := repos.Files.Create(ctx, tx, file); err != nil { return err } // Return error to trigger rollback return fmt.Errorf("test rollback") }) if err == nil || err.Error() != "test rollback" { t.Fatalf("expected rollback error, got: %v", err) } // Verify file was not created retrieved, err := repos.Files.GetByPath(ctx, "/test/tx_file.txt") if err != nil { t.Fatalf("error checking for file: %v", err) } if retrieved != nil { t.Error("file should not exist after rollback") } }