Files
hpc/internal/store/file_store_test.go
dailz bf89de12f0 feat(store): add blob, file, folder, and upload stores
Add BlobStore (ref counting), FileStore (soft delete + pagination), FolderStore (materialized path), UploadStore (idempotent upsert), and update AutoMigrate.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-15 09:22:44 +08:00

246 lines
6.1 KiB
Go

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_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)
}
}