SyncAlertTest.kt

3.8 KB · raw

1package com.peachpasswords.sync23import org.junit.Assert.assertEquals4import org.junit.Assert.assertFalse5import org.junit.Assert.assertNull6import org.junit.Assert.assertTrue7import org.junit.Test89class SyncAlertTest {10    @Test fun routine_entry_activity_is_not_a_user_facing_alert() {11        assertFalse(isDeviceTrustAlert(SyncAlert(SyncAlertType.REMOTE_PASSWORD_CHANGE, timestamp = 1L)))12        assertFalse(isDeviceTrustAlert(SyncAlert(SyncAlertType.REMOTE_DELETE, timestamp = 2L)))13        assertTrue(isDeviceTrustAlert(SyncAlert(SyncAlertType.DEVICE_KEY_ROTATED, timestamp = 3L)))14        assertTrue(isDeviceTrustAlert(SyncAlert(SyncAlertType.DEVICE_REVOKED, timestamp = 4L)))15    }1617    @Test fun device_key_rotation_detects_existing_peer_key_changes() {18        val existing = PairedDevice(19            deviceId = "browser",20            deviceName = "Browser",21            deviceType = "extension",22            longTermPublicKeyHex = "aa".repeat(32),23            signingPublicKeyHex = "bb".repeat(32),24            pairedAt = 1L,25        )26        val incoming = ManifestDeviceEntry(27            deviceId = "browser",28            deviceName = "Browser",29            deviceType = "extension",30            longTermPublicKeyHex = "aa".repeat(32),31            signingPublicKeyHex = "cc".repeat(32),32            pairedAt = 1L,33        )3435        val alert = deviceKeyRotationAlert(existing, incoming, nowMs = 99L)3637        assertEquals(SyncAlertType.DEVICE_KEY_ROTATED, alert?.type)38        assertEquals("Browser", alert?.deviceName)39        assertNull(deviceKeyRotationAlert(existing, incoming.copy(signingPublicKeyHex = "bb".repeat(32)), nowMs = 99L))40    }4142    @Test fun `S5 rotation alert names the device from local state not the manifest`() {43        // A hostile issuer relabels the rotating device, hoping the alert reads44        // as a rotation on something the user trusts more.45        val existing = PairedDevice(46            deviceId = "browser",47            deviceName = "Spare Tablet",48            deviceType = "extension",49            longTermPublicKeyHex = "aa".repeat(32),50            signingPublicKeyHex = "bb".repeat(32),51            pairedAt = 1L,52        )53        val incoming = ManifestDeviceEntry(54            deviceId = "browser",55            deviceName = "June's Primary Phone",56            deviceType = "extension",57            longTermPublicKeyHex = "aa".repeat(32),58            signingPublicKeyHex = "cc".repeat(32),59            pairedAt = 1L,60        )6162        val alert = deviceKeyRotationAlert(existing, incoming, nowMs = 99L)6364        assertEquals("Spare Tablet", alert?.deviceName)65    }6667    @Test fun system_notification_excludes_routine_entry_activity() {68        val content = syncSecurityNotificationContent(69            listOf(70                SyncAlert(71                    SyncAlertType.REMOTE_PASSWORD_CHANGE,72                    entryTitle = "Email",73                    deviceName = "Phone",74                    timestamp = 1L,75                ),76                SyncAlert(77                    SyncAlertType.DEVICE_REVOKED,78                    deviceName = "Chrome",79                    timestamp = 2L,80                ),81            ),82        )8384        assertEquals("Peach Sync security", content?.title)85        assertEquals(listOf("Chrome revoked a paired device"), content?.lines)86        assertEquals("Chrome revoked a paired device", content?.summary)87    }8889    @Test fun multiple_security_changes_use_a_compact_notification_summary() {90        val content = syncSecurityNotificationContent(91            listOf(92                SyncAlert(SyncAlertType.DEVICE_REVOKED, deviceName = "Chrome", timestamp = 1L),93                SyncAlert(SyncAlertType.DEVICE_KEY_ROTATED, deviceName = "Phone", timestamp = 2L),94            ),95        )9697        assertEquals(2, content?.lines?.size)98        assertEquals("2 device security updates", content?.summary)99    }100}