package com.peachpasswords.autofill import com.peachpasswords.autofill.AutofillResponseBuilder.FillMode import com.peachpasswords.vault.VaultEntry import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test /** * E2-fix verification: the 2FA chip and the LOGIN chip are SEPARATE datasets * so the user can verify the TOTP code before it lands. [datasetFor] no longer * fills [parsed.otpId] when identity/password is present; [otpDatasetFor] is * the dedicated 2FA path. * * Real-Dataset construction needs Android instrumentation (AutofillId is * package-private). What we can verify in JVM tests is the label path: the * LOGIN chip renders the entry title, the OTP chip renders the chunked code. */ class OtpDatasetTest { private fun entry( title: String = "GitHub", username: String? = "june", totp: String? = "JBSWY3DPEHPK3PXP", ) = VaultEntry( id = title, title = title, username = username, totp = totp, createdAt = 0, modifiedAt = 0, ) @Test fun otp_chip_shows_chunked_code_when_login_fields_also_present() { // Multi-field form: identity + password + otp. The LOGIN chip carries // the entry title; the OTP chip carries the chunked code -- two // separate affordances, two separate labels. val loginLabels = AutofillResponseBuilder.previewLabels( privatePreview = false, entry = entry(), originDomain = "github.com", fillMode = FillMode.LOGIN, ) val otpLabels = AutofillResponseBuilder.previewLabels( privatePreview = false, entry = entry(), originDomain = "github.com", fillMode = FillMode.OTP_FILL, otpCode = "482901", ) assertEquals("GitHub", loginLabels.title) assertEquals("482 901", otpLabels.title) assertEquals("2FA", otpLabels.subtitle) assertFalse( "LOGIN and OTP chip titles must differ so the user can tell them apart", loginLabels.title == otpLabels.title, ) assertTrue(loginLabels.fillMode == FillMode.LOGIN) assertTrue(otpLabels.fillMode == FillMode.OTP_FILL) } @Test fun otp_chip_regenerates_each_call() { // The TOTP code rotates every 30s. The chip is rebuilt on every // FillRequest, so a fresh code passed in surfaces as the title. val first = AutofillResponseBuilder.previewLabels( privatePreview = false, entry = entry(), originDomain = "github.com", fillMode = FillMode.OTP_FILL, otpCode = "111111", ) val second = AutofillResponseBuilder.previewLabels( privatePreview = false, entry = entry(), originDomain = "github.com", fillMode = FillMode.OTP_FILL, otpCode = "222222", ) assertEquals("111 111", first.title) assertEquals("222 222", second.title) } @Test fun otp_chip_in_private_mode_still_shows_peach_not_the_entry() { // Hide-autofill-previews hides the title too. The 2FA chip stays // generic ("Peach") with no entry title -- the digits would still // leak if we kept them. (Digits ARE shown via the menu RemoteViews // not the inline chip when privatePreview is on.) val labels = AutofillResponseBuilder.previewLabels( privatePreview = true, entry = entry(), originDomain = "github.com", fillMode = FillMode.OTP_FILL, otpCode = "482901", ) assertEquals("Peach", labels.title) assertEquals("github.com", labels.subtitle) } }