PeachClipboardTest.kt

3.6 KB · raw

1package com.peachpasswords.shared23import kotlinx.coroutines.CoroutineScope4import kotlinx.coroutines.test.StandardTestDispatcher5import kotlinx.coroutines.test.advanceTimeBy6import kotlinx.coroutines.test.runCurrent7import kotlinx.coroutines.test.runTest8import org.junit.Assert.assertEquals9import org.junit.Assert.assertFalse10import org.junit.Assert.assertTrue11import org.junit.Test1213/**14 * MF-8: a sensitive copy auto-clears after 30s — but only if our value is still on15 * the clipboard. Drives the pure [PeachClipboard.scheduleClear] seam on a virtual16 * scheduler (the Android ClipboardManager is faked with a plain string box).17 */18@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)19class PeachClipboardTest {2021    @Test fun clears_after_delay_when_value_unchanged() = runTest {22        var clipboard: String? = "s3cret"23        PeachClipboard.scheduleClear(24            scope = CoroutineScope(StandardTestDispatcher(testScheduler)),25            expected = "s3cret",26            readCurrent = { clipboard },27            clear = { clipboard = "" },28        )29        advanceTimeBy(PeachClipboard.CLEAR_DELAY_MS - 1_000); runCurrent()30        assertEquals("not cleared before the window elapses", "s3cret", clipboard)31        advanceTimeBy(2_000); runCurrent()32        assertEquals("cleared once the 30s window elapses", "", clipboard)33    }3435    @Test fun does_not_clear_when_user_copied_something_else() = runTest {36        var clipboard: String? = "s3cret"37        var cleared = false38        PeachClipboard.scheduleClear(39            scope = CoroutineScope(StandardTestDispatcher(testScheduler)),40            expected = "s3cret",41            readCurrent = { clipboard },42            clear = { cleared = true },43        )44        clipboard = "user typed this after copying" // user copied something else within 30s45        advanceTimeBy(PeachClipboard.CLEAR_DELAY_MS + 1_000); runCurrent()46        assertFalse("must never wipe what the user copied afterwards", cleared)47        assertEquals("user typed this after copying", clipboard)48    }4950    @Test fun clears_when_the_clipboard_cannot_be_read() = runTest {51        // From API 29 a backgrounded process reads null instead of the value it52        // wrote. That is the ordinary state after pasting into another app, and53        // it must not be mistaken for "someone else copied" (PB-26-119).54        var cleared = false55        PeachClipboard.scheduleClear(56            scope = CoroutineScope(StandardTestDispatcher(testScheduler)),57            expected = "s3cret",58            readCurrent = { null },59            clear = { cleared = true },60        )61        advanceTimeBy(PeachClipboard.CLEAR_DELAY_MS + 1_000); runCurrent()62        assertTrue("a denied read must still clear our value", cleared)63    }6465    @Test fun superseded_copy_is_left_to_its_own_timer() = runTest {66        var cleared = false67        PeachClipboard.scheduleClear(68            scope = CoroutineScope(StandardTestDispatcher(testScheduler)),69            expected = "s3cret",70            readCurrent = { "newer peach copy" },71            clear = { cleared = true },72            superseded = { true },73        )74        advanceTimeBy(PeachClipboard.CLEAR_DELAY_MS + 1_000); runCurrent()75        assertFalse("an older timer must not fire after a newer copy", cleared)76    }7778    @Test fun thirty_second_default_matches_browser() {79        assertTrue(PeachClipboard.CLEAR_DELAY_MS == 30_000L)80    }8182    @Test fun copy_feedback_bus_pulses_monotonically() {83        val before = CopyFeedbackBus.pulses.value84        CopyFeedbackBus.pulse()85        CopyFeedbackBus.pulse()8687        assertEquals(before + 2, CopyFeedbackBus.pulses.value)88    }89}