1package com.peachpasswords.sync23import org.junit.Assert.assertEquals4import org.junit.Assert.assertArrayEquals5import org.junit.Assert.assertTrue6import org.junit.Test7891011121314class SyncServiceStartTest {1516 17 private fun backgroundStartRefused(): IllegalStateException = IllegalStateException(18 "Not allowed to start service Intent { cmp=com.peachpasswords/.sync.PeachSyncService }: " +19 "app is in background",20 )2122 @Test fun `a start Android refuses is reported, not thrown`() {23 val reported = mutableListOf<SyncOutcomeSummary>()24 val started = startOrReportBlocked({ reported += it }) { throw backgroundStartRefused() }2526 assertEquals(false, started)27 assertEquals(listOf(SyncOutcomeReason.FOREGROUND_START_BLOCKED), reported.map { it.reason })28 assertTrue(reported.single().detail?.contains("app is in background") == true)29 }3031 @Test fun `the refusal says what the person holding the phone can do`() {32 assertEquals(33 "Android blocked Peach from starting sync in the background — open Peach and sync again",34 describeSyncOutcome(SyncOutcomeSummary(reason = SyncOutcomeReason.FOREGROUND_START_BLOCKED)),35 )36 assertEquals(37 SyncIndicator.ERROR,38 syncIndicator(39 SyncOutcomeSummary(reason = SyncOutcomeReason.FOREGROUND_START_BLOCKED),40 pairedCount = 1,41 ),42 )43 }4445 46 @Test fun `anything else the platform throws is reported too, and never escapes`() {47 val reported = mutableListOf<SyncOutcomeSummary>()48 val started = startOrReportBlocked({ reported += it }) {49 throw SecurityException("missing FOREGROUND_SERVICE_DATA_SYNC")50 }5152 assertEquals(false, started)53 assertEquals(listOf(SyncOutcomeReason.RUN_FAILED), reported.map { it.reason })54 }5556 @Test fun `a start that works reports nothing`() {57 val reported = mutableListOf<SyncOutcomeSummary>()58 var ran = false5960 assertEquals(true, startOrReportBlocked({ reported += it }) { ran = true })61 assertTrue(ran)62 assertTrue(reported.isEmpty())63 }6465 @Test fun `only a nudge to an existing listener replaces its waiters`() {66 assertEquals(false, shouldRefreshExistingWaiters(listenerStarted = true))67 assertEquals(true, shouldRefreshExistingWaiters(listenerStarted = false))68 }6970 @Test fun `an unlocked vault derives transient listener credentials`() {71 val credentials = randomLocalIdentityCredentials("phone-id")72 val expected = localSyncKeypair(credentials)73 val actual = deriveUnlockedListenerCredentials(credentials, "Phone")7475 assertEquals("phone-id", actual.identity.deviceId)76 assertEquals("Phone", actual.identity.deviceName)77 assertEquals(pubkeyToHex(expected.publicKey), actual.identity.longTermPublicKeyHex)78 assertArrayEquals(expected.publicKey, actual.keys.publicKey)79 assertArrayEquals(expected.secretKey, actual.keys.secretKey)80 }81}