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 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 ) `).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) } notifications, err := service.GetNotifications(userID, false) assert.NoError(t, err) assert.Len(t, notifications, 3) assert.Equal(t, userID, notifications[0].UserID) } 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 allNotifications, err := service.GetNotifications(userID, false) assert.NoError(t, err) assert.Len(t, allNotifications, 2) // Get unread only unreadNotifications, err := service.GetNotifications(userID, true) assert.NoError(t, err) assert.Len(t, unreadNotifications, 1) assert.False(t, unreadNotifications[0].Read) } func TestNotificationService_GetNotifications_NoNotifications(t *testing.T) { service, _, _ := setupTestNotificationService(t) userID := uuid.New() notifications, err := service.GetNotifications(userID, false) assert.NoError(t, err) assert.Len(t, notifications, 0) } 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 notifications1, err := service.GetNotifications(userID1, false) assert.NoError(t, err) assert.Len(t, notifications1, 1) assert.Equal(t, userID1, notifications1[0].UserID) // Get notifications for user2 notifications2, err := service.GetNotifications(userID2, false) assert.NoError(t, err) assert.Len(t, notifications2, 1) assert.Equal(t, userID2, notifications2[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) }