QaFormTwoFactorChipTest.kt

5.0 KB · raw

1package com.peachpasswords.qa23import android.content.ClipData4import android.content.ClipboardManager5import android.content.Context6import androidx.test.ext.junit.runners.AndroidJUnit47import androidx.test.platform.app.InstrumentationRegistry8import androidx.test.uiautomator.By9import androidx.test.uiautomator.UiDevice10import androidx.test.uiautomator.Until11import java.io.BufferedReader12import java.io.InputStreamReader13import org.junit.Assert.assertTrue14import org.junit.Test15import org.junit.runner.RunWith1617/**18 * Emulator QA: does the autofill framework issue a fill request for a form19 * that includes a 2FA field, and does the OTP chip appear in the inline20 * suggestion strip? The chip carries the actual TOTP code -- the regression21 * guard for "2FA fill is not working from the chip" (continuity 2026-08-05).22 *23 * Uses [QaFormActivity] which now has username + password + 2FA fields.24 * Tapping the username field triggers a FillRequest; the IME renders the25 * entry chip and the OTP chip (when a TOTP-enabled entry matches).26 */27@RunWith(AndroidJUnit4::class)28class QaFormTwoFactorChipTest {2930    @Test fun framework_issues_fill_request_for_form_with_2fa_field() {31        shell("am force-stop com.peachpasswords.debug.test")32        shell("am start -n com.peachpasswords.debug.test/com.peachpasswords.qa.QaFormActivity")33        Thread.sleep(2000)34        focusField("Username field")35        Thread.sleep(4000)36        val dump = shell("dumpsys autofill")37        val mentions = dump.lineSequence()38            .filter { it.contains("com.peachpasswords.debug.test") }39            .take(10)40            .joinToString("\n")41        assertTrue(42            "no autofill session for the 2FA form -- framework never issued a fill request.\n" +43                "dumpsys excerpt:\n$mentions",44            mentions.isNotEmpty(),45        )46    }4748    /** A spoofed web domain must not cause any pre-selection clipboard write. */49    @Test50    fun malicious_companion_spoofed_web_domain_does_not_copy_totp() {51        shell("am force-stop com.peachpasswords.debug.test")52        val clipboard = InstrumentationRegistry.getInstrumentation().targetContext53            .getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager54        clipboard.setPrimaryClip(ClipData.newPlainText("sentinel", "user-owned-clipboard"))55        shell("logcat -c")56        shell("am start -n com.peachpasswords.debug.test/com.peachpasswords.qa.QaSpoofedWebViewActivity")57        Thread.sleep(2000)58        focusField("Spoofed username field")59        Thread.sleep(4000)60        val logcat = shell("logcat -d -s PeachAutofill:D")61        assertTrue(62            "spoofed native form did not exercise Peach's unverified-web denial path:\n$logcat",63            logcat.contains("refused unverified web origin"),64        )65        assertTrue(66            "malicious autofill origin must not overwrite the user's clipboard",67            clipboard.primaryClip?.getItemAt(0)?.text?.toString() == "user-owned-clipboard",68        )69    }7071    private fun shell(command: String): String {72        val instrumentation = InstrumentationRegistry.getInstrumentation()73        instrumentation.uiAutomation.executeShellCommand(command).use { pfd ->74            BufferedReader(InputStreamReader(android.os.ParcelFileDescriptor.AutoCloseInputStream(pfd))).use {75                return it.readText()76            }77        }78    }79    /**80     * v2.14.6 regression: when the framework offers a single inline spec81     * (the case after username/password submit on GitHub, or when only a82     * 2FA field is focused), the picker dataset no longer clobbers the83     * OTP chip inline. We focus the 2FA field directly and assert the84     * autofill session survives -- the picker fix matters most on the85     * "single spec" path, which is what the emulator produces here.86     */87    @Test fun framework_issues_fill_request_when_focused_on_2fa_field_only() {88        shell("am force-stop com.peachpasswords.debug.test")89        shell("am start -n com.peachpasswords.debug.test/com.peachpasswords.qa.QaFormActivity")90        Thread.sleep(2000)91        // Select the field by its stable semantic description so layout changes92        // cannot make this probe tap the wrong control.93        focusField("2FA code field")94        Thread.sleep(4000)95        val dump = shell("dumpsys autofill")96        val mentions = dump.lineSequence()97            .filter { it.contains("com.peachpasswords.debug.test") }98            .take(10)99            .joinToString("\n")100        assertTrue(101            "no autofill session when 2FA field is focused -- picker collision regression.\n" +102                "dumpsys excerpt:\n$mentions",103            mentions.isNotEmpty(),104        )105    }106107    private fun focusField(contentDescription: String) {108        val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())109        val selector = By.desc(contentDescription)110        assertTrue("field not found: $contentDescription", device.wait(Until.hasObject(selector), 5_000))111        device.findObject(selector).click()112    }113}