package repository import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" "github.com/gochat/gochat/internal/model" ) // setupNotificationTestDB uses the shared setupTestDB helper. // Notification and NotificationPreference are already in defaultTestModels. func setupNotificationTestDB(t *testing.T) *gorm.DB { t.Helper() return setupTestDB(t) } // createNotificationTestAccount creates a minimal Account row. func createNotificationTestAccount(t *testing.T, db *gorm.DB) *model.Account { t.Helper() account := &model.Account{Name: "Notification Test Account"} err := db.Create(account).Error require.NoError(t, err, "failed to create test account") return account } // createNotificationTestUser creates a minimal User row linked to an account. func createNotificationTestUser(t *testing.T, db *gorm.DB, accountID uint) *model.User { t.Helper() user := &model.User{Name: "Notification Test User", Email: "notif-test@example.com", Password: "hashedpw", AccountID: accountID} err := db.Create(user).Error require.NoError(t, err, "failed to create test user") return user } // createNotificationTestAccountUser links a user to an account. func createNotificationTestAccountUser(t *testing.T, db *gorm.DB, accountID, userID uint) *model.AccountUser { t.Helper() au := &model.AccountUser{AccountID: accountID, UserID: userID, Role: "agent"} err := db.Create(au).Error require.NoError(t, err, "failed to create test account_user") return au } // createTestNotification creates a notification for a user in an account. func createTestNotification(t *testing.T, db *gorm.DB, userID, accountID uint, readAt *time.Time) *model.Notification { t.Helper() notif := &model.Notification{ UserID: userID, AccountID: &accountID, NotificationType: "conversation_created", PrimaryActorType: "User", PrimaryActorID: userID, SecondaryActorType: "Conversation", SecondaryActorID: 1, ReadAt: readAt, } err := db.Create(notif).Error require.NoError(t, err, "failed to create test notification") return notif } // =========================================================================== // Snooze tests // =========================================================================== func TestNotificationRepo_Snooze(t *testing.T) { db := setupNotificationTestDB(t) ctx := context.Background() repo := NewNotificationRepo(db) account := createNotificationTestAccount(t, db) user := createNotificationTestUser(t, db, account.ID) createNotificationTestAccountUser(t, db, account.ID, user.ID) // Create a read notification now := time.Now() notif := createTestNotification(t, db, user.ID, account.ID, &now) snoozedUntil := time.Now().Add(24 * time.Hour) updated, err := repo.Snooze(ctx, notif.ID, user.ID, account.ID, snoozedUntil) require.NoError(t, err) assert.NotNil(t, updated) assert.Equal(t, notif.ID, updated.ID) // Verify snoozed_until was set and read_at was cleared var refreshed model.Notification require.NoError(t, db.First(&refreshed, notif.ID).Error) assert.NotNil(t, refreshed.SnoozedUntil) assert.WithinDuration(t, snoozedUntil, *refreshed.SnoozedUntil, time.Second) assert.Nil(t, refreshed.ReadAt) } func TestNotificationRepo_Snooze_NotFound(t *testing.T) { db := setupNotificationTestDB(t) ctx := context.Background() repo := NewNotificationRepo(db) snoozedUntil := time.Now().Add(24 * time.Hour) _, err := repo.Snooze(ctx, 9999, 1, 1, snoozedUntil) assert.ErrorIs(t, err, gorm.ErrRecordNotFound) } func TestNotificationRepo_Snooze_WrongUser(t *testing.T) { db := setupNotificationTestDB(t) ctx := context.Background() repo := NewNotificationRepo(db) account := createNotificationTestAccount(t, db) user := createNotificationTestUser(t, db, account.ID) createNotificationTestAccountUser(t, db, account.ID, user.ID) notif := createTestNotification(t, db, user.ID, account.ID, nil) // Try to snooze with wrong user ID — should not affect the notification snoozedUntil := time.Now().Add(24 * time.Hour) _, err := repo.Snooze(ctx, notif.ID, 9999, account.ID, snoozedUntil) assert.ErrorIs(t, err, gorm.ErrRecordNotFound) } // =========================================================================== // MarkUnread tests // =========================================================================== func TestNotificationRepo_MarkUnread(t *testing.T) { db := setupNotificationTestDB(t) ctx := context.Background() repo := NewNotificationRepo(db) account := createNotificationTestAccount(t, db) user := createNotificationTestUser(t, db, account.ID) createNotificationTestAccountUser(t, db, account.ID, user.ID) // Create a read notification now := time.Now() notif := createTestNotification(t, db, user.ID, account.ID, &now) updated, err := repo.MarkUnread(ctx, notif.ID, user.ID, account.ID) require.NoError(t, err) assert.NotNil(t, updated) assert.Equal(t, notif.ID, updated.ID) // Verify read_at was cleared var refreshed model.Notification require.NoError(t, db.First(&refreshed, notif.ID).Error) assert.Nil(t, refreshed.ReadAt) } func TestNotificationRepo_MarkUnread_NotFound(t *testing.T) { db := setupNotificationTestDB(t) ctx := context.Background() repo := NewNotificationRepo(db) _, err := repo.MarkUnread(ctx, 9999, 1, 1) assert.ErrorIs(t, err, gorm.ErrRecordNotFound) } func TestNotificationRepo_MarkUnread_AlreadyUnread(t *testing.T) { db := setupNotificationTestDB(t) ctx := context.Background() repo := NewNotificationRepo(db) account := createNotificationTestAccount(t, db) user := createNotificationTestUser(t, db, account.ID) createNotificationTestAccountUser(t, db, account.ID, user.ID) // Create an unread notification (read_at = nil) notif := createTestNotification(t, db, user.ID, account.ID, nil) // Marking unread again should succeed (no-op on read_at) updated, err := repo.MarkUnread(ctx, notif.ID, user.ID, account.ID) require.NoError(t, err) assert.NotNil(t, updated) assert.Nil(t, updated.ReadAt) } // =========================================================================== // DeleteAllByUser tests // =========================================================================== func TestNotificationRepo_DeleteAllByUser(t *testing.T) { db := setupNotificationTestDB(t) ctx := context.Background() repo := NewNotificationRepo(db) account := createNotificationTestAccount(t, db) user := createNotificationTestUser(t, db, account.ID) createNotificationTestAccountUser(t, db, account.ID, user.ID) // Create 3 notifications for the user createTestNotification(t, db, user.ID, account.ID, nil) createTestNotification(t, db, user.ID, account.ID, nil) createTestNotification(t, db, user.ID, account.ID, nil) // Verify they exist var count int64 db.Model(&model.Notification{}).Where("user_id = ? AND account_id = ?", user.ID, account.ID).Count(&count) assert.Equal(t, int64(3), count) err := repo.DeleteAllByUser(ctx, user.ID, account.ID) require.NoError(t, err) // Verify all are soft-deleted db.Model(&model.Notification{}).Where("user_id = ? AND account_id = ?", user.ID, account.ID).Count(&count) assert.Equal(t, int64(0), count) } func TestNotificationRepo_DeleteAllByUser_OnlyDeletesForUserAccount(t *testing.T) { db := setupNotificationTestDB(t) ctx := context.Background() repo := NewNotificationRepo(db) account1 := createNotificationTestAccount(t, db) account2 := createNotificationTestAccount(t, db) user := createNotificationTestUser(t, db, account1.ID) createNotificationTestAccountUser(t, db, account1.ID, user.ID) createNotificationTestAccountUser(t, db, account2.ID, user.ID) // Create notifications in both accounts createTestNotification(t, db, user.ID, account1.ID, nil) createTestNotification(t, db, user.ID, account2.ID, nil) // Delete only for account1 err := repo.DeleteAllByUser(ctx, user.ID, account1.ID) require.NoError(t, err) // Account1 notifications gone, account2 still present var count1, count2 int64 db.Model(&model.Notification{}).Where("user_id = ? AND account_id = ?", user.ID, account1.ID).Count(&count1) db.Model(&model.Notification{}).Where("user_id = ? AND account_id = ?", user.ID, account2.ID).Count(&count2) assert.Equal(t, int64(0), count1) assert.Equal(t, int64(1), count2) }