package repository import ( "context" "testing" "gorm.io/driver/sqlite" "gorm.io/gorm" "github.com/dhao2001/mygo/internal/model" ) func setupFileRepo(t *testing.T) FileRepository { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("open db: %v", err) } if err := db.AutoMigrate(&model.File{}); err != nil { t.Fatalf("migrate: %v", err) } return NewFileRepository(db) } func TestFileRepository_Create(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() file := &model.File{ ID: "file-1", UserID: "user-1", Name: "test.txt", Size: 1024, Status: model.StatusActive, } if err := repo.Create(ctx, file); err != nil { t.Fatalf("Create = %v", err) } } func TestFileRepository_FindByID(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() file := &model.File{ ID: "file-1", UserID: "user-1", Name: "test.txt", Status: model.StatusActive, } if err := repo.Create(ctx, file); err != nil { t.Fatalf("Create = %v", err) } found, err := repo.FindByID(ctx, "file-1") if err != nil { t.Fatalf("FindByID = %v", err) } if found.Name != "test.txt" { t.Errorf("name = %q, want %q", found.Name, "test.txt") } } func TestFileRepository_FindByIDNotFound(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() _, err := repo.FindByID(ctx, "nonexistent") if err != model.ErrNotFound { t.Fatalf("expected ErrNotFound, got %v", err) } } func TestFileRepository_FindByUserID(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() files := []*model.File{ {ID: "f-1", UserID: "user-1", Name: "a.txt", Status: model.StatusActive}, {ID: "f-2", UserID: "user-1", Name: "b.txt", Status: model.StatusActive}, {ID: "f-3", UserID: "user-2", Name: "c.txt", Status: model.StatusActive}, } for _, f := range files { if err := repo.Create(ctx, f); err != nil { t.Fatalf("Create = %v", err) } } result, total, err := repo.FindByUserID(ctx, "user-1", 0, 10) if err != nil { t.Fatalf("FindByUserID = %v", err) } if len(result) != 2 { t.Errorf("len(result) = %d, want 2", len(result)) } if total != 2 { t.Errorf("total = %d, want 2", total) } } func TestFileRepository_FindByParentID(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() parentID := "dir-1" files := []*model.File{ {ID: "f-1", UserID: "user-1", ParentID: &parentID, Name: "a.txt", Status: model.StatusActive}, {ID: "f-2", UserID: "user-1", ParentID: &parentID, Name: "b.txt", Status: model.StatusActive}, {ID: "f-3", UserID: "user-1", Name: "c.txt", Status: model.StatusActive}, } for _, f := range files { if err := repo.Create(ctx, f); err != nil { t.Fatalf("Create = %v", err) } } children, err := repo.FindByParentID(ctx, "user-1", &parentID) if err != nil { t.Fatalf("FindByParentID = %v", err) } if len(children) != 2 { t.Errorf("len(children) = %d, want 2", len(children)) } } func TestFileRepository_FindByParentIDNull(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() parentID := "dir-1" files := []*model.File{ {ID: "f-1", UserID: "user-1", ParentID: &parentID, Name: "a.txt", Status: model.StatusActive}, {ID: "f-2", UserID: "user-1", Name: "root.txt", Status: model.StatusActive}, } for _, f := range files { if err := repo.Create(ctx, f); err != nil { t.Fatalf("Create = %v", err) } } children, err := repo.FindByParentID(ctx, "user-1", nil) if err != nil { t.Fatalf("FindByParentID(nil) = %v", err) } if len(children) != 1 { t.Errorf("len(children) = %d, want 1", len(children)) } } func TestFileRepository_Update(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() file := &model.File{ID: "file-1", UserID: "user-1", Name: "original.txt", Status: model.StatusActive} if err := repo.Create(ctx, file); err != nil { t.Fatalf("Create = %v", err) } file.Name = "renamed.txt" file.Size = 2048 if err := repo.Update(ctx, file); err != nil { t.Fatalf("Update = %v", err) } found, err := repo.FindByID(ctx, "file-1") if err != nil { t.Fatalf("FindByID = %v", err) } if found.Name != "renamed.txt" { t.Errorf("name = %q, want %q", found.Name, "renamed.txt") } if found.Size != 2048 { t.Errorf("size = %d, want %d", found.Size, 2048) } } func TestFileRepository_Delete(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() file := &model.File{ID: "file-1", UserID: "user-1", Name: "test.txt", Status: model.StatusActive} if err := repo.Create(ctx, file); err != nil { t.Fatalf("Create = %v", err) } if err := repo.Delete(ctx, "file-1"); err != nil { t.Fatalf("Delete = %v", err) } _, err := repo.FindByID(ctx, "file-1") if err != model.ErrNotFound { t.Fatalf("expected ErrNotFound after delete, got %v", err) } } func TestFileRepository_SoftDelete(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() file := &model.File{ ID: "file-1", UserID: "user-1", Name: "test.txt", Status: model.StatusActive, } if err := repo.Create(ctx, file); err != nil { t.Fatalf("Create = %v", err) } if err := repo.Delete(ctx, "file-1"); err != nil { t.Fatalf("Delete = %v", err) } _, err := repo.FindByID(ctx, "file-1") if err != model.ErrNotFound { t.Fatalf("expected ErrNotFound after soft-delete, got %v", err) } } func TestFileRepository_StatusFilter(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() activeFile := &model.File{ ID: "f-1", UserID: "user-1", Name: "active.txt", Status: model.StatusActive, } deletedFile := &model.File{ ID: "f-2", UserID: "user-1", Name: "deleted.txt", Status: model.StatusUserDeleted, } if err := repo.Create(ctx, activeFile); err != nil { t.Fatalf("Create active = %v", err) } if err := repo.Create(ctx, deletedFile); err != nil { t.Fatalf("Create deleted = %v", err) } result, total, err := repo.FindByUserID(ctx, "user-1", 0, 10) if err != nil { t.Fatalf("FindByUserID = %v", err) } if len(result) != 1 { t.Errorf("len(result) = %d, want 1", len(result)) } if total != 1 { t.Errorf("total = %d, want 1", total) } if result[0].ID != "f-1" { t.Errorf("result[0].ID = %q, want %q", result[0].ID, "f-1") } } func TestFileRepository_DeleteIdempotent(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() file := &model.File{ ID: "file-1", UserID: "user-1", Name: "test.txt", Status: model.StatusActive, } if err := repo.Create(ctx, file); err != nil { t.Fatalf("Create = %v", err) } if err := repo.Delete(ctx, "file-1"); err != nil { t.Fatalf("first Delete = %v", err) } if err := repo.Delete(ctx, "file-1"); err != nil { t.Fatalf("second Delete = %v", err) } } func TestFileRepository_StatusFilterCount(t *testing.T) { repo := setupFileRepo(t) ctx := context.Background() activeFile := &model.File{ ID: "f-1", UserID: "user-1", Name: "active.txt", Status: model.StatusActive, } deletedFile := &model.File{ ID: "f-2", UserID: "user-1", Name: "deleted.txt", Status: model.StatusUserDeleted, } if err := repo.Create(ctx, activeFile); err != nil { t.Fatalf("Create active = %v", err) } if err := repo.Create(ctx, deletedFile); err != nil { t.Fatalf("Create deleted = %v", err) } _, total, err := repo.FindByUserID(ctx, "user-1", 0, 10) if err != nil { t.Fatalf("FindByUserID = %v", err) } if total != 1 { t.Errorf("total = %d, want 1 (soft-deleted excluded from count)", total) } }