package store import ( "context" "testing" "gcy_hpc_server/internal/model" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func setupFileTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate(&model.File{}, &model.FileBlob{}); err != nil { t.Fatalf("migrate: %v", err) } return db } func TestFileStore_CreateAndGetByID(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() file := &model.File{ Name: "test.bin", BlobSHA256: "abc123", } if err := store.Create(ctx, file); err != nil { t.Fatalf("Create() error = %v", err) } if file.ID == 0 { t.Fatal("Create() did not set ID") } got, err := store.GetByID(ctx, file.ID) if err != nil { t.Fatalf("GetByID() error = %v", err) } if got == nil { t.Fatal("GetByID() returned nil") } if got.Name != "test.bin" { t.Errorf("Name = %q, want %q", got.Name, "test.bin") } if got.BlobSHA256 != "abc123" { t.Errorf("BlobSHA256 = %q, want %q", got.BlobSHA256, "abc123") } } func TestFileStore_GetByID_NotFound(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() got, err := store.GetByID(ctx, 999) if err != nil { t.Fatalf("GetByID() error = %v", err) } if got != nil { t.Error("GetByID() should return nil for not found") } } func TestFileStore_ListByFolder(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() folderID := int64(1) store.Create(ctx, &model.File{Name: "f1.bin", BlobSHA256: "a1", FolderID: &folderID}) store.Create(ctx, &model.File{Name: "f2.bin", BlobSHA256: "a2", FolderID: &folderID}) store.Create(ctx, &model.File{Name: "root.bin", BlobSHA256: "a3"}) // root (folder_id=nil) files, total, err := store.List(ctx, &folderID, 1, 10) if err != nil { t.Fatalf("List() error = %v", err) } if total != 2 { t.Errorf("total = %d, want 2", total) } if len(files) != 2 { t.Errorf("len(files) = %d, want 2", len(files)) } } func TestFileStore_ListRootFolder(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() store.Create(ctx, &model.File{Name: "root.bin", BlobSHA256: "a1"}) folderID := int64(1) store.Create(ctx, &model.File{Name: "sub.bin", BlobSHA256: "a2", FolderID: &folderID}) files, total, err := store.List(ctx, nil, 1, 10) if err != nil { t.Fatalf("List() error = %v", err) } if total != 1 { t.Errorf("total = %d, want 1", total) } if len(files) != 1 { t.Errorf("len(files) = %d, want 1", len(files)) } if files[0].Name != "root.bin" { t.Errorf("files[0].Name = %q, want %q", files[0].Name, "root.bin") } } func TestFileStore_Pagination(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() for i := 0; i < 25; i++ { store.Create(ctx, &model.File{Name: "file.bin", BlobSHA256: "hash"}) } files, total, err := store.List(ctx, nil, 1, 10) if err != nil { t.Fatalf("List() error = %v", err) } if total != 25 { t.Errorf("total = %d, want 25", total) } if len(files) != 10 { t.Errorf("page 1 len = %d, want 10", len(files)) } files, _, _ = store.List(ctx, nil, 3, 10) if len(files) != 5 { t.Errorf("page 3 len = %d, want 5", len(files)) } } func TestFileStore_Search(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() store.Create(ctx, &model.File{Name: "experiment_results.csv", BlobSHA256: "a1"}) store.Create(ctx, &model.File{Name: "training_log.txt", BlobSHA256: "a2"}) store.Create(ctx, &model.File{Name: "model_weights.bin", BlobSHA256: "a3"}) files, total, err := store.Search(ctx, "results", 1, 10) if err != nil { t.Fatalf("Search() error = %v", err) } if total != 1 { t.Errorf("total = %d, want 1", total) } if len(files) != 1 || files[0].Name != "experiment_results.csv" { t.Errorf("expected experiment_results.csv, got %v", files) } } func TestFileStore_Delete_SoftDelete(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() file := &model.File{Name: "deleteme.bin", BlobSHA256: "abc"} store.Create(ctx, file) if err := store.Delete(ctx, file.ID); err != nil { t.Fatalf("Delete() error = %v", err) } // GetByID should return nil (soft deleted) got, err := store.GetByID(ctx, file.ID) if err != nil { t.Fatalf("GetByID() error = %v", err) } if got != nil { t.Error("GetByID() should return nil after soft delete") } // List should not include soft deleted _, total, _ := store.List(ctx, nil, 1, 10) if total != 0 { t.Errorf("total after delete = %d, want 0", total) } } func TestFileStore_CountByBlobSHA256(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() store.Create(ctx, &model.File{Name: "a.bin", BlobSHA256: "shared_hash"}) store.Create(ctx, &model.File{Name: "b.bin", BlobSHA256: "shared_hash"}) store.Create(ctx, &model.File{Name: "c.bin", BlobSHA256: "shared_hash"}) count, err := store.CountByBlobSHA256(ctx, "shared_hash") if err != nil { t.Fatalf("CountByBlobSHA256() error = %v", err) } if count != 3 { t.Errorf("count = %d, want 3", count) } // Soft delete one store.Delete(ctx, 1) count, _ = store.CountByBlobSHA256(ctx, "shared_hash") if count != 2 { t.Errorf("count after soft delete = %d, want 2", count) } } func TestFileStore_GetBlobSHA256ByID(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() file := &model.File{Name: "test.bin", BlobSHA256: "my_hash"} store.Create(ctx, file) sha256, err := store.GetBlobSHA256ByID(ctx, file.ID) if err != nil { t.Fatalf("GetBlobSHA256ByID() error = %v", err) } if sha256 != "my_hash" { t.Errorf("sha256 = %q, want %q", sha256, "my_hash") } } func TestFileStore_GetByIDs(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() store.Create(ctx, &model.File{Name: "a.bin", BlobSHA256: "h1"}) store.Create(ctx, &model.File{Name: "b.bin", BlobSHA256: "h2"}) store.Create(ctx, &model.File{Name: "c.bin", BlobSHA256: "h3"}) files, err := store.GetByIDs(ctx, []int64{1, 3}) if err != nil { t.Fatalf("GetByIDs() error = %v", err) } if len(files) != 2 { t.Fatalf("len(files) = %d, want 2", len(files)) } names := map[string]bool{} for _, f := range files { names[f.Name] = true } if !names["a.bin"] || !names["c.bin"] { t.Errorf("expected a.bin and c.bin, got %v", files) } } func TestFileStore_GetByIDs_Empty(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() files, err := store.GetByIDs(ctx, []int64{}) if err != nil { t.Fatalf("GetByIDs() error = %v", err) } if len(files) != 0 { t.Errorf("len(files) = %d, want 0", len(files)) } } func TestFileStore_GetByIDs_NotFound(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() files, err := store.GetByIDs(ctx, []int64{999}) if err != nil { t.Fatalf("GetByIDs() error = %v", err) } if len(files) != 0 { t.Errorf("len(files) = %d, want 0 for non-existent IDs", len(files)) } } func TestFileStore_GetByIDs_SoftDeleteExcluded(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() store.Create(ctx, &model.File{Name: "a.bin", BlobSHA256: "h1"}) store.Create(ctx, &model.File{Name: "b.bin", BlobSHA256: "h2"}) store.Create(ctx, &model.File{Name: "c.bin", BlobSHA256: "h3"}) store.Delete(ctx, 2) files, err := store.GetByIDs(ctx, []int64{1, 2, 3}) if err != nil { t.Fatalf("GetByIDs() error = %v", err) } if len(files) != 2 { t.Fatalf("len(files) = %d, want 2 (soft-deleted excluded)", len(files)) } for _, f := range files { if f.ID == 2 { t.Error("soft-deleted file ID 2 should not appear") } } } func TestFileStore_GetBlobSHA256ByID_NotFound(t *testing.T) { db := setupFileTestDB(t) store := NewFileStore(db) ctx := context.Background() sha256, err := store.GetBlobSHA256ByID(ctx, 999) if err != nil { t.Fatalf("GetBlobSHA256ByID() error = %v", err) } if sha256 != "" { t.Errorf("sha256 = %q, want empty for not found", sha256) } }