1package com.peachpasswords.security23import javax.xml.parsers.DocumentBuilderFactory4import org.junit.Assert.assertFalse5import org.junit.Assert.assertTrue6import org.junit.Test7import org.w3c.dom.Element89class SecureFlagCoverageTest {1011 12 13 14 15 private val secretScreens = listOf(16 "app/src/main/java/com/peachpasswords/ui/vault/EntryDetailScreen.kt",17 "app/src/main/java/com/peachpasswords/ui/vault/EntryEditScreen.kt",18 "app/src/main/java/com/peachpasswords/ui/vault/NotesModeScreen.kt",19 "app/src/main/java/com/peachpasswords/ui/vault/ExportScreen.kt",20 "app/src/main/java/com/peachpasswords/ui/vault/RecoveryWordsScreen.kt",21 "app/src/main/java/com/peachpasswords/ui/recovery/RecoveryWordEntryScreen.kt",22 "app/src/main/java/com/peachpasswords/ui/codex/CodexGenerateScreen.kt",23 )2425 26 27 28 private val secretActivities = listOf(29 "com.peachpasswords.autofill.AutofillEntryPickerActivity",30 "com.peachpasswords.passkeys.PasskeyCredentialSelectionActivity",31 )3233 @Test fun secret_screens_keep_flag_secure_coverage() {34 secretScreens.forEach { relativePath ->35 val file = java.io.File(relativePath).takeIf { it.exists() }36 ?: java.io.File("../$relativePath")37 val source = file.readText()38 assertTrue(39 "$relativePath must protect screenshots while visible",40 source.contains("SecureFlagEffect()") ||41 source.contains("WindowManager.LayoutParams.FLAG_SECURE"),42 )43 }44 }4546 @Test fun secret_bearing_activities_set_flag_secure() {47 48 49 50 val manifestActivities = parseActivityNames(51 "app/src/main/AndroidManifest.xml",52 )53 secretActivities.forEach { fqcn ->54 val shortName = fqcn.substringAfterLast('.')55 assertTrue(56 "$fqcn must be declared in the manifest",57 manifestActivities.any { it == fqcn || it.endsWith(".$shortName") },58 )59 val relativePath = fqcnToSourcePath(fqcn)60 val file = java.io.File(relativePath).takeIf { it.exists() }61 ?: java.io.File("../$relativePath")62 val source = file.readText()63 assertTrue(64 "$fqcn must delegate secure ownership " +65 "(separate window from MainActivity)",66 source.contains("SecureWindowPolicy"),67 )68 }69 }7071 @Test fun all_window_policy_transitions_are_centralized() {72 val helper = java.io.File(73 "app/src/main/java/com/peachpasswords/ui/components/SecureWindowPolicy.kt",74 ).takeIf { it.exists() } ?: java.io.File(75 "../app/src/main/java/com/peachpasswords/ui/components/SecureWindowPolicy.kt",76 )77 val source = helper.readText()78 assertTrue(source.contains("installRelease"))79 assertTrue(source.contains("leases"))80 assertTrue(source.contains("releaseOwned"))81 assertTrue(source.contains("clearFlags"))82 83 84 listOf(85 "app/src/main/java/com/peachpasswords/MainActivity.kt",86 "app/src/main/java/com/peachpasswords/autofill/AutofillEntryPickerActivity.kt",87 "app/src/main/java/com/peachpasswords/passkeys/PasskeyCredentialSelectionActivity.kt",88 ).forEach { relativePath ->89 val file = java.io.File(relativePath).takeIf { it.exists() }90 ?: java.io.File("../$relativePath")91 val activitySource = file.readText()92 assertTrue(93 "$relativePath must delegate secure policy ownership",94 activitySource.contains("SecureWindowPolicy"),95 )96 assertFalse(97 "$relativePath must not clear FLAG_SECURE directly",98 activitySource.contains("clearFlags("),99 )100 }101 }102103 private fun parseActivityNames(relativePath: String): Set<String> {104 val file = java.io.File(relativePath).takeIf { it.exists() }105 ?: java.io.File("../$relativePath")106 val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()107 .parse(file)108 val activities = doc.getElementsByTagName("activity")109 return (0 until activities.length)110 .mapNotNull { (activities.item(it) as? Element)?.getAttribute("android:name") }111 .toSet()112 }113114 115 116 117 private fun fqcnToSourcePath(fqcn: String): String {118 val pkg = fqcn.substringBeforeLast('.')119 val cls = fqcn.substringAfterLast('.')120 return "app/src/main/java/" + pkg.replace('.', '/') + "/$cls.kt"121 }122}