SHA256
feat(android): replace WebView with tested native Compose API slice
Add real native onboarding/auth/dashboard/tasks/notes/shopping, origin-bound HTTPS and encrypted AndroidKeyStore session persistence. Archive the superseded browser renderer and tests. Verify 10 JVM tests, 6 native API-35 emulator tests, lintDebug/lintRelease and debug/unsigned release APK/AAB builds; the real upstream suite passes 7 tests and exports a nonblank native fixture screenshot. Tink host-tool update removes the old generated-protobuf warning. Full module/visual parity remains explicitly incomplete.
This commit is contained in:
1 parent
81973c3c45
commit
8c480ddca3
28 files changed
+2628
-742
No files matched your search
@@ -0,0 +1,111 @@
|
||||
package community.yuvomi.mobile
|
||||
|
||||
import android.content.Context
|
||||
import android.webkit.WebView
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import java.io.File
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.json.JSONArray
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class BlobExportTest {
|
||||
@Test fun rejectsBlobExportFromOpaqueNativePageEvenWithClaimedOrigin() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
context.getSharedPreferences("server", Context.MODE_PRIVATE).edit()
|
||||
.putString("origin", "https://family.example").commit()
|
||||
val file = File(File(context.cacheDir, "captures").apply { mkdirs() }, "blob-test.csv")
|
||||
val uri = FileProvider.getUriForFile(context, "${context.packageName}.files", file)
|
||||
val worker = Executors.newSingleThreadExecutor()
|
||||
try {
|
||||
ActivityScenario.launch(MainActivity::class.java).use { scenario ->
|
||||
lateinit var web: WebView
|
||||
val created = CountDownLatch(1)
|
||||
var blob = ""
|
||||
scenario.onActivity {
|
||||
// A real WebView with a controlled document, not a network mock.
|
||||
// The HTTPS upstream suite separately exercises the app's client.
|
||||
web = WebView(it)
|
||||
web.settings.javaScriptEnabled = true
|
||||
web.loadDataWithBaseURL(
|
||||
"https://family.example/",
|
||||
"<!doctype html><title>Export test</title>",
|
||||
"text/html",
|
||||
"UTF-8",
|
||||
null,
|
||||
)
|
||||
}
|
||||
val deadline = System.nanoTime() + 10_000_000_000L
|
||||
var ready = false
|
||||
while (!ready && System.nanoTime() < deadline) {
|
||||
val checked = CountDownLatch(1)
|
||||
scenario.onActivity {
|
||||
web.evaluateJavascript("location.origin") {
|
||||
ready =
|
||||
it == "\"https://family.example\""
|
||||
checked.countDown()
|
||||
}
|
||||
}
|
||||
assertTrue(checked.await(2, TimeUnit.SECONDS))
|
||||
if (!ready) Thread.sleep(50)
|
||||
}
|
||||
assertTrue("Trusted document loaded", ready)
|
||||
scenario.onActivity {
|
||||
web.evaluateJavascript(
|
||||
"URL.createObjectURL(new Blob(['name,value\\nYuvomi,Grüße\\n'],{type:'text/csv'}))",
|
||||
) {
|
||||
blob = JSONArray("[$it]").getString(0)
|
||||
created.countDown()
|
||||
}
|
||||
}
|
||||
assertTrue(created.await(10, TimeUnit.SECONDS))
|
||||
assertTrue(
|
||||
"Blob inherits trusted origin: $blob",
|
||||
BlobTransfer.owns(ServerAddress.parse("https://family.example"), blob),
|
||||
)
|
||||
assertNotNull(
|
||||
context.contentResolver.openOutputStream(uri, "w")?.also {
|
||||
it.close()
|
||||
},
|
||||
)
|
||||
val saved = CountDownLatch(1)
|
||||
var success = false
|
||||
scenario.onActivity {
|
||||
assertFalse(
|
||||
"Native page URL remains opaque despite loadDataWithBaseURL",
|
||||
ServerAddress.parse("https://family.example").owns(web.url.orEmpty()),
|
||||
)
|
||||
BlobTransfer.save(
|
||||
ServerAddress.parse("https://family.example"),
|
||||
web,
|
||||
blob,
|
||||
context.contentResolver,
|
||||
uri,
|
||||
worker,
|
||||
{ true },
|
||||
) {
|
||||
success = it
|
||||
saved.countDown()
|
||||
}
|
||||
}
|
||||
assertTrue(saved.await(50, TimeUnit.SECONDS))
|
||||
assertFalse(success)
|
||||
assertEquals(0L, file.length())
|
||||
scenario.onActivity { web.destroy() }
|
||||
}
|
||||
} finally {
|
||||
worker.shutdownNow()
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user