package mysql import ( "context" "testing" "wucher/internal/domain/auth" "wucher/internal/domain/user" "wucher/internal/shared/pkg/appctx" "wucher/internal/shared/pkg/uuidv7" ) func setupUserRepoFixture(t *testing.T) (*UserRepository, context.Context, []byte) { t.Helper() db := openDBTestSQLite(t) if err := AutoMigrate(db); err != nil { t.Fatalf("auto migrate: %v", err) } repo := NewUserRepository(db) roleID := uuidv7.MustBytes() if err := db.Create(&auth.Role{ ID: roleID, Code: "admin", Name: "admin", Description: "admin role", }).Error; err != nil { t.Fatalf("seed role: %v", err) } return repo, context.Background(), roleID } func TestUserRepository_CRUDAndLookupBranches(t *testing.T) { repo, baseCtx, roleID := setupUserRepoFixture(t) actorID := uuidv7.MustBytes() ctx := appctx.WithUserID(baseCtx, actorID) u := &user.User{ Email: "john@example.com", Username: "john", FirstName: "John", LastName: "Doe", Timezone: "UTC", RoleID: roleID, } if err := repo.Create(ctx, u); err != nil { t.Fatalf("create: %v", err) } if len(u.CreatedBy) != 16 || len(u.UpdatedBy) != 16 { t.Fatalf("expected created_by and updated_by set by actor") } byEmail, err := repo.GetByEmail(ctx, u.Email) if err != nil || byEmail == nil { t.Fatalf("get by email failed: user=%v err=%v", byEmail, err) } byUsername, err := repo.GetByUsername(ctx, u.Username) if err != nil || byUsername == nil { t.Fatalf("get by username failed: user=%v err=%v", byUsername, err) } lastName, err := repo.GetLastNameByID(ctx, u.ID) if err != nil || lastName != "Doe" { t.Fatalf("get last name failed: lastName=%q err=%v", lastName, err) } // Update branch: RoleIDs nil should skip role-replacement path. byEmail.FirstName = "Johnny" byEmail.RoleIDs = nil if err := repo.Update(ctx, byEmail); err != nil { t.Fatalf("update (roleIDs nil): %v", err) } // Update branch: RoleIDs provided should execute role-replacement path. byEmail.LastName = "Doe-Updated" byEmail.RoleIDs = [][]byte{roleID} if err := repo.Update(ctx, byEmail); err != nil { t.Fatalf("update (roleIDs set): %v", err) } // List branches: with/without filter, sort, and limit. list, total, err := repo.List(ctx, "john", "users.email ASC", 1, 0) if err != nil { t.Fatalf("list with filter: %v", err) } if total < 1 || len(list) != 1 { t.Fatalf("expected one filtered result, total=%d len=%d", total, len(list)) } list, total, err = repo.List(ctx, "", "", 0, 0) if err != nil { t.Fatalf("list without limit: %v", err) } if total < 1 || len(list) < 1 { t.Fatalf("expected at least one user, total=%d len=%d", total, len(list)) } // Not found branches. if got, err := repo.GetByID(ctx, uuidv7.MustBytes()); err != nil || got != nil { t.Fatalf("expected not found by id => nil,nil; got=%v err=%v", got, err) } if got, err := repo.GetByEmail(ctx, "missing@example.com"); err != nil || got != nil { t.Fatalf("expected not found by email => nil,nil; got=%v err=%v", got, err) } if got, err := repo.GetByUsername(ctx, "missing-user"); err != nil || got != nil { t.Fatalf("expected not found by username => nil,nil; got=%v err=%v", got, err) } if got, err := repo.GetLastNameByID(ctx, uuidv7.MustBytes()); err != nil || got != "" { t.Fatalf("expected not found last_name => empty,nil; got=%q err=%v", got, err) } if err := repo.Delete(ctx, u.ID); err != nil { t.Fatalf("delete: %v", err) } // Delete branch with invalid uuid bytes (skip attachment cleanup query branch). if err := repo.Delete(ctx, []byte("short")); err != nil { t.Fatalf("delete short id should be noop-success, err=%v", err) } } func TestUserRepository_ErrorBranchesWhenDatabaseClosed(t *testing.T) { db := openDBTestSQLite(t) if err := AutoMigrate(db); err != nil { t.Fatalf("auto migrate: %v", err) } repo := NewUserRepository(db) sqlDB, err := db.DB() if err != nil { t.Fatalf("db.DB: %v", err) } if err := sqlDB.Close(); err != nil { t.Fatalf("close db: %v", err) } ctx := context.Background() roleID := uuidv7.MustBytes() userID := uuidv7.MustBytes() u := &user.User{ ID: userID, Email: "closed@example.com", Username: "closed", FirstName: "Closed", LastName: "DB", Timezone: "UTC", RoleID: roleID, } if err := repo.Create(ctx, u); err == nil { t.Fatalf("expected create error on closed db") } if err := repo.Update(ctx, u); err == nil { t.Fatalf("expected update error on closed db") } if err := repo.Delete(ctx, userID); err == nil { t.Fatalf("expected delete error on closed db") } if _, err := repo.GetByID(ctx, userID); err == nil { t.Fatalf("expected get by id error on closed db") } if _, err := repo.GetByEmail(ctx, u.Email); err == nil { t.Fatalf("expected get by email error on closed db") } if _, err := repo.GetByUsername(ctx, u.Username); err == nil { t.Fatalf("expected get by username error on closed db") } if _, err := repo.GetLastNameByID(ctx, userID); err == nil { t.Fatalf("expected get last name error on closed db") } if _, _, err := repo.List(ctx, "", "", 10, 0); err == nil { t.Fatalf("expected list error on closed db") } } func TestUserRepository_CreateDuplicateAndDeleteAttachmentCleanupError(t *testing.T) { repo, ctx, roleID := setupUserRepoFixture(t) u := &user.User{ Email: "dup@example.com", Username: "dup-user", FirstName: "Dup", LastName: "User", Timezone: "UTC", RoleID: roleID, } if err := repo.Create(ctx, u); err != nil { t.Fatalf("create first user: %v", err) } dup := &user.User{ Email: "dup@example.com", Username: "dup-user-2", FirstName: "Dup2", LastName: "User2", Timezone: "UTC", RoleID: roleID, } if err := repo.Create(ctx, dup); err == nil { t.Fatalf("expected duplicate-email create error") } // Force attachment cleanup statement to fail so delete returns error branch. if err := repo.db.Exec("DROP TABLE attachments").Error; err != nil { t.Fatalf("drop attachments table: %v", err) } if err := repo.Delete(ctx, u.ID); err == nil { t.Fatalf("expected delete error when attachments table missing") } }