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("setUserSecure"))79 assertTrue(source.contains("acquire"))80 assertTrue(source.contains("release"))81 assertTrue(source.contains("userChoiceApplied"))82 assertTrue(source.contains("leases"))83 assertTrue(source.contains("clearFlags"))84 85 86 listOf(87 "app/src/main/java/com/peachpasswords/MainActivity.kt",88 "app/src/main/java/com/peachpasswords/autofill/AutofillEntryPickerActivity.kt",89 "app/src/main/java/com/peachpasswords/passkeys/PasskeyCredentialSelectionActivity.kt",90 ).forEach { relativePath ->91 val file = java.io.File(relativePath).takeIf { it.exists() }92 ?: java.io.File("../$relativePath")93 val activitySource = file.readText()94 assertTrue(95 "$relativePath must delegate secure policy ownership",96 activitySource.contains("SecureWindowPolicy"),97 )98 assertFalse(99 "$relativePath must not clear FLAG_SECURE directly",100 activitySource.contains("clearFlags("),101 )102 }103 }104105 private fun parseActivityNames(relativePath: String): Set<String> {106 val file = java.io.File(relativePath).takeIf { it.exists() }107 ?: java.io.File("../$relativePath")108 val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()109 .parse(file)110 val activities = doc.getElementsByTagName("activity")111 return (0 until activities.length)112 .mapNotNull { (activities.item(it) as? Element)?.getAttribute("android:name") }113 .toSet()114 }115116 117 118 119 private fun fqcnToSourcePath(fqcn: String): String {120 val pkg = fqcn.substringBeforeLast('.')121 val cls = fqcn.substringAfterLast('.')122 return "app/src/main/java/" + pkg.replace('.', '/') + "/$cls.kt"123 }124}