package services import ( "context" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/database" ) func setupTestNotificationService(t *testing.T) (*NotificationService, *gorm.DB, *database.Database) { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) db.Exec("PRAGMA foreign_keys = ON") // Create notifications table manually (incl. F554 grouping columns) err = db.Exec(` CREATE TABLE notifications ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, type TEXT NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL, link TEXT NOT NULL, read INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, group_key TEXT, actor_count INTEGER DEFAULT 1, metadata TEXT ) `).Error require.NoError(t, err) sqlDB, err := db.DB() require.NoError(t, err) testDB := &database.Database{ DB: sqlDB, } logger := zap.NewNop() service := NewNotificationService(testDB, logger) return service, db, testDB } func TestNotificationService_CreateNotification_Success(t *testing.T) { service, _, _ := setupTestNotificationService(t) userID := uuid.New() notificationType := "test_type" title := "Test Title" content := "Test Content" link := "https://example.com" err := service.CreateNotification(userID, notificationType, title, content, link) assert.NoError(t, err) } func TestNotificationService_CreateNotification_EmptyFields(t *testing.T) { service, _, _ := setupTestNotificationService(t) userID := uuid.New() notificationType := "" title := "" content := "" link := "" // SQLite allows empty strings, so this should not error err := service.CreateNotification(userID, notificationType, title, content, link) assert.NoError(t, err) } func TestNotificationService_GetNotifications_AllNotifications(t *testing.T) { service, _, testDB := setupTestNotificationService(t) userID := uuid.New() ctx := context.Background() // Create test notifications for i := 0; i < 3; i++ { notificationID := uuid.New() _, err := testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", false) require.NoError(t, err) } result, err := service.GetNotifications(userID, GetNotificationsParams{Page: 1, Limit: 20}) assert.NoError(t, err) require.NotNil(t, result) assert.Len(t, result.Notifications, 3) assert.Equal(t, userID, result.Notifications[0].UserID) assert.Equal(t, 3, result.Total) } func TestNotificationService_GetNotifications_UnreadOnly(t *testing.T) { service, _, testDB := setupTestNotificationService(t) userID := uuid.New() ctx := context.Background() // Create read notification readID := uuid.New() _, err := testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, readID.String(), userID.String(), "test_type", "Read Title", "Content", "", true) require.NoError(t, err) // Create unread notification unreadID := uuid.New() _, err = testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, unreadID.String(), userID.String(), "test_type", "Unread Title", "Content", "", false) require.NoError(t, err) // Get all notifications allResult, err := service.GetNotifications(userID, GetNotificationsParams{Page: 1, Limit: 20}) assert.NoError(t, err) require.NotNil(t, allResult) assert.Len(t, allResult.Notifications, 2) // Get unread only unreadResult, err := service.GetNotifications(userID, GetNotificationsParams{UnreadOnly: true, Page: 1, Limit: 20}) assert.NoError(t, err) require.NotNil(t, unreadResult) assert.Len(t, unreadResult.Notifications, 1) assert.False(t, unreadResult.Notifications[0].Read) } func TestNotificationService_GetNotifications_NoNotifications(t *testing.T) { service, _, _ := setupTestNotificationService(t) userID := uuid.New() result, err := service.GetNotifications(userID, GetNotificationsParams{Page: 1, Limit: 20}) assert.NoError(t, err) require.NotNil(t, result) assert.Len(t, result.Notifications, 0) assert.Equal(t, 0, result.Total) } func TestNotificationService_GetNotifications_DifferentUsers(t *testing.T) { service, _, testDB := setupTestNotificationService(t) userID1 := uuid.New() userID2 := uuid.New() ctx := context.Background() // Create notification for user1 notificationID1 := uuid.New() _, err := testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, notificationID1.String(), userID1.String(), "test_type", "Title", "Content", "", false) require.NoError(t, err) // Create notification for user2 notificationID2 := uuid.New() _, err = testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, notificationID2.String(), userID2.String(), "test_type", "Title", "Content", "", false) require.NoError(t, err) // Get notifications for user1 result1, err := service.GetNotifications(userID1, GetNotificationsParams{Page: 1, Limit: 20}) assert.NoError(t, err) require.NotNil(t, result1) assert.Len(t, result1.Notifications, 1) assert.Equal(t, userID1, result1.Notifications[0].UserID) // Get notifications for user2 result2, err := service.GetNotifications(userID2, GetNotificationsParams{Page: 1, Limit: 20}) assert.NoError(t, err) require.NotNil(t, result2) assert.Len(t, result2.Notifications, 1) assert.Equal(t, userID2, result2.Notifications[0].UserID) } func TestNotificationService_MarkAsRead_Success(t *testing.T) { service, _, testDB := setupTestNotificationService(t) userID := uuid.New() notificationID := uuid.New() ctx := context.Background() // Create unread notification _, err := testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", false) require.NoError(t, err) // Mark as read err = service.MarkAsRead(userID, notificationID) assert.NoError(t, err) // Verify it's marked as read var read bool err = testDB.QueryRowContext(ctx, ` SELECT read FROM notifications WHERE id = $1 `, notificationID.String()).Scan(&read) assert.NoError(t, err) assert.True(t, read) } func TestNotificationService_MarkAsRead_WrongUser(t *testing.T) { service, _, testDB := setupTestNotificationService(t) userID1 := uuid.New() userID2 := uuid.New() notificationID := uuid.New() ctx := context.Background() // Create notification for user1 _, err := testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, notificationID.String(), userID1.String(), "test_type", "Title", "Content", "", false) require.NoError(t, err) // Try to mark as read with wrong user (should not error, but won't update) err = service.MarkAsRead(userID2, notificationID) assert.NoError(t, err) // Verify it's still unread (because user_id didn't match) var read bool err = testDB.QueryRowContext(ctx, ` SELECT read FROM notifications WHERE id = $1 `, notificationID.String()).Scan(&read) assert.NoError(t, err) assert.False(t, read) } func TestNotificationService_MarkAsRead_NonExistent(t *testing.T) { service, _, _ := setupTestNotificationService(t) userID := uuid.New() notificationID := uuid.New() // Try to mark non-existent notification as read (should not error) err := service.MarkAsRead(userID, notificationID) assert.NoError(t, err) } func TestNotificationService_MarkAllAsRead_Success(t *testing.T) { service, _, testDB := setupTestNotificationService(t) userID := uuid.New() ctx := context.Background() // Create multiple unread notifications for i := 0; i < 3; i++ { notificationID := uuid.New() _, err := testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", false) require.NoError(t, err) } // Mark all as read err := service.MarkAllAsRead(userID) assert.NoError(t, err) // Verify all are marked as read var count int err = testDB.QueryRowContext(ctx, ` SELECT COUNT(*) FROM notifications WHERE user_id = $1 AND read = FALSE `, userID.String()).Scan(&count) assert.NoError(t, err) assert.Equal(t, 0, count) } func TestNotificationService_MarkAllAsRead_NoUnread(t *testing.T) { service, _, testDB := setupTestNotificationService(t) userID := uuid.New() ctx := context.Background() // Create read notification notificationID := uuid.New() _, err := testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", true) require.NoError(t, err) // Mark all as read (should not error even if none are unread) err = service.MarkAllAsRead(userID) assert.NoError(t, err) } func TestNotificationService_MarkAllAsRead_NoNotifications(t *testing.T) { service, _, _ := setupTestNotificationService(t) userID := uuid.New() // Mark all as read when user has no notifications (should not error) err := service.MarkAllAsRead(userID) assert.NoError(t, err) } func TestNotificationService_GetUnreadCount_Success(t *testing.T) { service, _, testDB := setupTestNotificationService(t) userID := uuid.New() ctx := context.Background() // Create unread notifications for i := 0; i < 3; i++ { notificationID := uuid.New() _, err := testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", false) require.NoError(t, err) } // Create read notification readID := uuid.New() _, err := testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, readID.String(), userID.String(), "test_type", "Title", "Content", "", true) require.NoError(t, err) count, err := service.GetUnreadCount(userID) assert.NoError(t, err) assert.Equal(t, 3, count) } func TestNotificationService_GetUnreadCount_Zero(t *testing.T) { service, _, _ := setupTestNotificationService(t) userID := uuid.New() count, err := service.GetUnreadCount(userID) assert.NoError(t, err) assert.Equal(t, 0, count) } func TestNotificationService_GetUnreadCount_AfterMarkAllAsRead(t *testing.T) { service, _, testDB := setupTestNotificationService(t) userID := uuid.New() ctx := context.Background() // Create unread notifications for i := 0; i < 2; i++ { notificationID := uuid.New() _, err := testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", false) require.NoError(t, err) } // Mark all as read err := service.MarkAllAsRead(userID) assert.NoError(t, err) // Verify unread count is 0 count, err := service.GetUnreadCount(userID) assert.NoError(t, err) assert.Equal(t, 0, count) } func TestNotificationService_GetUnreadCount_DifferentUsers(t *testing.T) { service, _, testDB := setupTestNotificationService(t) userID1 := uuid.New() userID2 := uuid.New() ctx := context.Background() // Create unread notification for user1 notificationID1 := uuid.New() _, err := testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, notificationID1.String(), userID1.String(), "test_type", "Title", "Content", "", false) require.NoError(t, err) // Create unread notification for user2 notificationID2 := uuid.New() _, err = testDB.ExecContext(ctx, ` INSERT INTO notifications (id, user_id, type, title, content, link, read) VALUES ($1, $2, $3, $4, $5, $6, $7) `, notificationID2.String(), userID2.String(), "test_type", "Title", "Content", "", false) require.NoError(t, err) // Get unread count for user1 count1, err := service.GetUnreadCount(userID1) assert.NoError(t, err) assert.Equal(t, 1, count1) // Get unread count for user2 count2, err := service.GetUnreadCount(userID2) assert.NoError(t, err) assert.Equal(t, 1, count2) }