package com.peachpasswords.sync import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test class SyncAlertTest { @Test fun routine_entry_activity_is_not_a_user_facing_alert() { assertFalse(isDeviceTrustAlert(SyncAlert(SyncAlertType.REMOTE_PASSWORD_CHANGE, timestamp = 1L))) assertFalse(isDeviceTrustAlert(SyncAlert(SyncAlertType.REMOTE_DELETE, timestamp = 2L))) assertTrue(isDeviceTrustAlert(SyncAlert(SyncAlertType.DEVICE_KEY_ROTATED, timestamp = 3L))) assertTrue(isDeviceTrustAlert(SyncAlert(SyncAlertType.DEVICE_REVOKED, timestamp = 4L))) } @Test fun device_key_rotation_detects_existing_peer_key_changes() { val existing = PairedDevice( deviceId = "browser", deviceName = "Browser", deviceType = "extension", longTermPublicKeyHex = "aa".repeat(32), signingPublicKeyHex = "bb".repeat(32), pairedAt = 1L, ) val incoming = ManifestDeviceEntry( deviceId = "browser", deviceName = "Browser", deviceType = "extension", longTermPublicKeyHex = "aa".repeat(32), signingPublicKeyHex = "cc".repeat(32), pairedAt = 1L, ) val alert = deviceKeyRotationAlert(existing, incoming, nowMs = 99L) assertEquals(SyncAlertType.DEVICE_KEY_ROTATED, alert?.type) assertEquals("Browser", alert?.deviceName) assertNull(deviceKeyRotationAlert(existing, incoming.copy(signingPublicKeyHex = "bb".repeat(32)), nowMs = 99L)) } @Test fun `S5 rotation alert names the device from local state not the manifest`() { // A hostile issuer relabels the rotating device, hoping the alert reads // as a rotation on something the user trusts more. val existing = PairedDevice( deviceId = "browser", deviceName = "Spare Tablet", deviceType = "extension", longTermPublicKeyHex = "aa".repeat(32), signingPublicKeyHex = "bb".repeat(32), pairedAt = 1L, ) val incoming = ManifestDeviceEntry( deviceId = "browser", deviceName = "June's Primary Phone", deviceType = "extension", longTermPublicKeyHex = "aa".repeat(32), signingPublicKeyHex = "cc".repeat(32), pairedAt = 1L, ) val alert = deviceKeyRotationAlert(existing, incoming, nowMs = 99L) assertEquals("Spare Tablet", alert?.deviceName) } @Test fun system_notification_excludes_routine_entry_activity() { val content = syncSecurityNotificationContent( listOf( SyncAlert( SyncAlertType.REMOTE_PASSWORD_CHANGE, entryTitle = "Email", deviceName = "Phone", timestamp = 1L, ), SyncAlert( SyncAlertType.DEVICE_REVOKED, deviceName = "Chrome", timestamp = 2L, ), ), ) assertEquals("Peach Sync security", content?.title) assertEquals(listOf("Chrome revoked a paired device"), content?.lines) assertEquals("Chrome revoked a paired device", content?.summary) } @Test fun multiple_security_changes_use_a_compact_notification_summary() { val content = syncSecurityNotificationContent( listOf( SyncAlert(SyncAlertType.DEVICE_REVOKED, deviceName = "Chrome", timestamp = 1L), SyncAlert(SyncAlertType.DEVICE_KEY_ROTATED, deviceName = "Phone", timestamp = 2L), ), ) assertEquals(2, content?.lines?.size) assertEquals("2 device security updates", content?.summary) } }