package repository import ( "context" "fmt" "testing" "gorm.io/gorm" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/pkg/crypto" ) // createBenchmarkAccount creates a test account for benchmarks. func createBenchmarkAccount(b *testing.B, db *gorm.DB, name string) *model.Account { b.Helper() account := &model.Account{Name: name, Locale: "en", Active: true} if err := db.Create(account).Error; err != nil { b.Fatalf("failed to create account: %v", err) } return account } // createBenchmarkUser creates a test user for benchmarks. func createBenchmarkUser(b *testing.B, db *gorm.DB, accountID uint, email string) *model.User { b.Helper() password, err := crypto.HashPassword("BenchmarkPass123!") if err != nil { b.Fatalf("failed to hash password: %v", err) } user := &model.User{ AccountID: accountID, Name: "Benchmark User", Email: email, Password: password, Role: "agent", Active: true, } if err := db.Create(user).Error; err != nil { b.Fatalf("failed to create user: %v", err) } return user } // createBenchmarkInbox creates a test inbox for benchmarks. func createBenchmarkInbox(b *testing.B, db *gorm.DB, accountID uint, name string) *model.Inbox { b.Helper() inbox := &model.Inbox{ AccountID: accountID, Name: name, ChannelType: "web_widget", EnableAutoAssignment: false, } if err := db.Create(inbox).Error; err != nil { b.Fatalf("failed to create inbox: %v", err) } return inbox } // createBenchmarkContact creates a test contact for benchmarks. func createBenchmarkContact(b *testing.B, db *gorm.DB, accountID uint, email string) *model.Contact { b.Helper() contact := &model.Contact{ AccountID: accountID, Name: "Benchmark Contact", Email: email, } if err := db.Create(contact).Error; err != nil { b.Fatalf("failed to create contact: %v", err) } return contact } // ============================================================================ // User Repository Benchmarks // ============================================================================ func BenchmarkUserRepo_Create(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "User Bench Org") repo := NewUserRepo(db) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { user := &model.User{ AccountID: account.ID, Name: fmt.Sprintf("BenchUser_%d", i), Email: fmt.Sprintf("bench_user_%d@test.com", i), Password: "hashed_password_placeholder", Role: "agent", Active: true, } if err := repo.Create(ctx, user); err != nil { b.Fatal(err) } } } func BenchmarkUserRepo_FindByID(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "User FindBench Org") repo := NewUserRepo(db) ctx := context.Background() // Pre-create a user to find user := createBenchmarkUser(b, db, account.ID, "find_by_id@test.com") b.ResetTimer() for i := 0; i < b.N; i++ { _, err := repo.FindByID(ctx, user.ID) if err != nil { b.Fatal(err) } } } func BenchmarkUserRepo_FindByEmail(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "User EmailBench Org") repo := NewUserRepo(db) ctx := context.Background() _ = createBenchmarkUser(b, db, account.ID, "find_by_email@test.com") b.ResetTimer() for i := 0; i < b.N; i++ { _, err := repo.FindByEmail(ctx, "find_by_email@test.com") if err != nil { b.Fatal(err) } } } func BenchmarkUserRepo_Update(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "User UpdateBench Org") repo := NewUserRepo(db) ctx := context.Background() user := createBenchmarkUser(b, db, account.ID, "update_user@test.com") b.ResetTimer() for i := 0; i < b.N; i++ { user.Name = fmt.Sprintf("UpdatedName_%d", i) if err := repo.Update(ctx, user); err != nil { b.Fatal(err) } } } func BenchmarkUserRepo_Delete(b *testing.B) { // For delete benchmark, we need to create a user per iteration db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "User DeleteBench Org") repo := NewUserRepo(db) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { user := &model.User{ AccountID: account.ID, Name: fmt.Sprintf("DeleteUser_%d", i), Email: fmt.Sprintf("delete_user_%d@test.com", i), Password: "hashed_placeholder", Role: "agent", Active: true, } if err := repo.Create(ctx, user); err != nil { b.Fatal(err) } if err := repo.Delete(ctx, user.ID); err != nil { b.Fatal(err) } } } func BenchmarkUserRepo_List(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "User ListBench Org") repo := NewUserRepo(db) ctx := context.Background() // Pre-create 100 users for list benchmark for i := 0; i < 100; i++ { user := &model.User{ AccountID: account.ID, Name: fmt.Sprintf("ListUser_%d", i), Email: fmt.Sprintf("list_user_%d@test.com", i), Password: "hashed_placeholder", Role: "agent", Active: true, } if err := repo.Create(ctx, user); err != nil { b.Fatal(err) } } b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := repo.List(ctx, 0, 25) if err != nil { b.Fatal(err) } } } // ============================================================================ // Conversation Repository Benchmarks // ============================================================================ func BenchmarkConversationRepo_Create(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Conv Bench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Conv Bench Inbox") contact := createBenchmarkContact(b, db, account.ID, "conv_bench@contact.com") repo := NewConversationRepo(db) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { conv := &model.Conversation{ AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget", } if err := repo.Create(ctx, conv); err != nil { b.Fatal(err) } } } func BenchmarkConversationRepo_FindByID(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Conv FindBench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Conv FindBench Inbox") contact := createBenchmarkContact(b, db, account.ID, "conv_find@contact.com") repo := NewConversationRepo(db) ctx := context.Background() // Pre-create a conversation conv := &model.Conversation{ AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget", } if err := db.Create(conv).Error; err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := repo.FindByID(ctx, conv.ID) if err != nil { b.Fatal(err) } } } func BenchmarkConversationRepo_FindByAccount(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Conv AcctBench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Conv AcctBench Inbox") contact := createBenchmarkContact(b, db, account.ID, "conv_acct@contact.com") repo := NewConversationRepo(db) ctx := context.Background() // Pre-create 50 conversations for i := 0; i < 50; i++ { conv := &model.Conversation{ AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget", } if err := db.Create(conv).Error; err != nil { b.Fatal(err) } } b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := repo.FindByAccount(ctx, account.ID, 0, 25) if err != nil { b.Fatal(err) } } } func BenchmarkConversationRepo_Update(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Conv UpdateBench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Conv UpdateBench Inbox") contact := createBenchmarkContact(b, db, account.ID, "conv_update@contact.com") repo := NewConversationRepo(db) ctx := context.Background() conv := &model.Conversation{ AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget", } if err := db.Create(conv).Error; err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { conv.Status = "resolved" if err := repo.Update(ctx, conv); err != nil { b.Fatal(err) } conv.Status = "open" } } func BenchmarkConversationRepo_Delete(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Conv DelBench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Conv DelBench Inbox") contact := createBenchmarkContact(b, db, account.ID, "conv_del@contact.com") repo := NewConversationRepo(db) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { conv := &model.Conversation{ AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget", } if err := db.Create(conv).Error; err != nil { b.Fatal(err) } if err := repo.Delete(ctx, conv.ID); err != nil { b.Fatal(err) } } } // ============================================================================ // Message Repository Benchmarks // ============================================================================ func BenchmarkMessageRepo_Create(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Msg Bench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Msg Bench Inbox") contact := createBenchmarkContact(b, db, account.ID, "msg_bench@contact.com") conv := &model.Conversation{ AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget", } if err := db.Create(conv).Error; err != nil { b.Fatal(err) } repo := NewMessageRepo(db) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { msg := &model.Message{ ConversationID: conv.ID, AccountID: account.ID, InboxID: inbox.ID, SenderType: "contact", Content: fmt.Sprintf("Benchmark message %d", i), ContentType: "text", MessageType: "incoming", } if err := repo.Create(ctx, msg); err != nil { b.Fatal(err) } } } func BenchmarkMessageRepo_FindByID(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Msg FindBench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Msg FindBench Inbox") contact := createBenchmarkContact(b, db, account.ID, "msg_find@contact.com") conv := &model.Conversation{ AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget", } if err := db.Create(conv).Error; err != nil { b.Fatal(err) } repo := NewMessageRepo(db) ctx := context.Background() msg := &model.Message{ ConversationID: conv.ID, AccountID: account.ID, InboxID: inbox.ID, SenderType: "contact", Content: "Find benchmark message", ContentType: "text", MessageType: "incoming", } if err := db.Create(msg).Error; err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := repo.FindByID(ctx, msg.ID) if err != nil { b.Fatal(err) } } } func BenchmarkMessageRepo_FindByConversation(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Msg ConvBench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Msg ConvBench Inbox") contact := createBenchmarkContact(b, db, account.ID, "msg_conv@contact.com") conv := &model.Conversation{ AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget", } if err := db.Create(conv).Error; err != nil { b.Fatal(err) } repo := NewMessageRepo(db) ctx := context.Background() // Pre-create 50 messages for i := 0; i < 50; i++ { msg := &model.Message{ ConversationID: conv.ID, AccountID: account.ID, InboxID: inbox.ID, SenderType: "contact", Content: fmt.Sprintf("Pre-created message %d", i), ContentType: "text", MessageType: "incoming", } if err := db.Create(msg).Error; err != nil { b.Fatal(err) } } b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := repo.FindByConversation(ctx, conv.ID, 0, 25) if err != nil { b.Fatal(err) } } } // ============================================================================ // Notification Repository Benchmarks // ============================================================================ func BenchmarkNotificationRepo_Create(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Notif Bench Org") user := createBenchmarkUser(b, db, account.ID, "notif_bench@test.com") repo := NewNotificationRepo(db) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { notif := &model.Notification{ AccountID: &account.ID, UserID: user.ID, NotificationType: "conversation_created", PrimaryActorType: "Conversation", PrimaryActorID: uint(i % 1000), } if err := repo.Create(ctx, notif); err != nil { b.Fatal(err) } } } func BenchmarkNotificationRepo_FindByID(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Notif FindBench Org") user := createBenchmarkUser(b, db, account.ID, "notif_find@test.com") repo := NewNotificationRepo(db) ctx := context.Background() notif := &model.Notification{ AccountID: &account.ID, UserID: user.ID, NotificationType: "conversation_created", PrimaryActorType: "Conversation", PrimaryActorID: 1, } if err := db.Create(notif).Error; err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := repo.FindByID(ctx, notif.ID) if err != nil { b.Fatal(err) } } } func BenchmarkNotificationRepo_ListByUser(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Notif ListBench Org") user := createBenchmarkUser(b, db, account.ID, "notif_list@test.com") repo := NewNotificationRepo(db) ctx := context.Background() // Pre-create 50 notifications for i := 0; i < 50; i++ { notif := &model.Notification{ AccountID: &account.ID, UserID: user.ID, NotificationType: "conversation_created", PrimaryActorType: "Conversation", PrimaryActorID: uint(i), } if err := db.Create(notif).Error; err != nil { b.Fatal(err) } } b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := repo.ListByUser(ctx, user.ID, 0, 25) if err != nil { b.Fatal(err) } } } // ============================================================================ // BaseRepository Generic CRUD Benchmarks // ============================================================================ func BenchmarkBaseRepository_Create(b *testing.B) { db := setupBenchmarkDB(b) repo := NewBaseRepository[model.Account](db) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { account := &model.Account{Name: fmt.Sprintf("BenchOrg_%d", i), Locale: "en", Active: true} if err := repo.Create(ctx, account); err != nil { b.Fatal(err) } } } func BenchmarkBaseRepository_GetByID(b *testing.B) { db := setupBenchmarkDB(b) repo := NewBaseRepository[model.Account](db) ctx := context.Background() account := &model.Account{Name: "BenchGetOrg", Locale: "en", Active: true} if err := repo.Create(ctx, account); err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := repo.GetByID(ctx, account.ID) if err != nil { b.Fatal(err) } } } func BenchmarkBaseRepository_Update(b *testing.B) { db := setupBenchmarkDB(b) repo := NewBaseRepository[model.Account](db) ctx := context.Background() account := &model.Account{Name: "BenchUpdateOrg", Locale: "en", Active: true} if err := repo.Create(ctx, account); err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { account.Name = fmt.Sprintf("UpdatedOrg_%d", i) if err := repo.Update(ctx, account); err != nil { b.Fatal(err) } } } func BenchmarkBaseRepository_Delete(b *testing.B) { db := setupBenchmarkDB(b) repo := NewBaseRepository[model.Account](db) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { account := &model.Account{Name: fmt.Sprintf("BenchDelOrg_%d", i), Locale: "en", Active: true} if err := repo.Create(ctx, account); err != nil { b.Fatal(err) } if err := repo.Delete(ctx, account.ID); err != nil { b.Fatal(err) } } } func BenchmarkBaseRepository_List(b *testing.B) { db := setupBenchmarkDB(b) repo := NewBaseRepository[model.Account](db) ctx := context.Background() // Pre-create 100 accounts for i := 0; i < 100; i++ { account := &model.Account{Name: fmt.Sprintf("BenchListOrg_%d", i), Locale: "en", Active: true} if err := repo.Create(ctx, account); err != nil { b.Fatal(err) } } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := repo.List(ctx, 0, 25) if err != nil { b.Fatal(err) } } }