package services import ( "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/treytartt/casera-api/internal/models" "github.com/treytartt/casera-api/internal/push" "github.com/treytartt/casera-api/internal/repositories" "github.com/treytartt/casera-api/internal/testutil" ) func setupNotificationService(t *testing.T) (*NotificationService, *repositories.NotificationRepository) { db := testutil.SetupTestDB(t) notifRepo := repositories.NewNotificationRepository(db) // pushClient is nil for testing (no actual push sends) service := NewNotificationService(notifRepo, nil) return service, notifRepo } func TestDeleteDevice_WrongUser_Returns403(t *testing.T) { db := testutil.SetupTestDB(t) notifRepo := repositories.NewNotificationRepository(db) service := NewNotificationService(notifRepo, nil) owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") attacker := testutil.CreateTestUser(t, db, "attacker", "attacker@test.com", "password") // Register an iOS device for the owner device := &models.APNSDevice{ UserID: &owner.ID, Name: "Owner iPhone", DeviceID: "device-123", RegistrationID: "token-abc", Active: true, } err := db.Create(device).Error require.NoError(t, err) // Attacker tries to deactivate the owner's device err = service.DeleteDevice(device.ID, push.PlatformIOS, attacker.ID) require.Error(t, err, "should not allow deleting another user's device") testutil.AssertAppErrorCode(t, err, http.StatusForbidden) // Verify the device is still active var found models.APNSDevice err = db.First(&found, device.ID).Error require.NoError(t, err) assert.True(t, found.Active, "device should still be active after failed deletion") } func TestDeleteDevice_CorrectUser_Succeeds(t *testing.T) { db := testutil.SetupTestDB(t) notifRepo := repositories.NewNotificationRepository(db) service := NewNotificationService(notifRepo, nil) owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") // Register an iOS device for the owner device := &models.APNSDevice{ UserID: &owner.ID, Name: "Owner iPhone", DeviceID: "device-123", RegistrationID: "token-abc", Active: true, } err := db.Create(device).Error require.NoError(t, err) // Owner deactivates their own device err = service.DeleteDevice(device.ID, push.PlatformIOS, owner.ID) require.NoError(t, err, "owner should be able to deactivate their own device") // Verify the device is now inactive var found models.APNSDevice err = db.First(&found, device.ID).Error require.NoError(t, err) assert.False(t, found.Active, "device should be deactivated") } func TestDeleteDevice_WrongUser_Android_Returns403(t *testing.T) { db := testutil.SetupTestDB(t) notifRepo := repositories.NewNotificationRepository(db) service := NewNotificationService(notifRepo, nil) owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") attacker := testutil.CreateTestUser(t, db, "attacker", "attacker@test.com", "password") // Register an Android device for the owner device := &models.GCMDevice{ UserID: &owner.ID, Name: "Owner Pixel", DeviceID: "device-456", RegistrationID: "token-def", CloudMessageType: "FCM", Active: true, } err := db.Create(device).Error require.NoError(t, err) // Attacker tries to deactivate the owner's Android device err = service.DeleteDevice(device.ID, push.PlatformAndroid, attacker.ID) require.Error(t, err, "should not allow deleting another user's Android device") testutil.AssertAppErrorCode(t, err, http.StatusForbidden) // Verify the device is still active var found models.GCMDevice err = db.First(&found, device.ID).Error require.NoError(t, err) assert.True(t, found.Active, "Android device should still be active after failed deletion") } func TestDeleteDevice_NonExistent_Returns404(t *testing.T) { db := testutil.SetupTestDB(t) notifRepo := repositories.NewNotificationRepository(db) service := NewNotificationService(notifRepo, nil) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") err := service.DeleteDevice(99999, push.PlatformIOS, user.ID) require.Error(t, err, "should return error for non-existent device") testutil.AssertAppErrorCode(t, err, http.StatusNotFound) }