Implement Android Home activity and HTTPS synchronization (#97)
Android ARM verification / rust (push) Failing after 46m50s
Android ARM verification / android (push) Failing after 8s
Dependency security audit / rustsec (push) Successful in 23s

This commit is contained in:
hugo committed 2026-10-09 14:45:57 +02:00
1 parent 8c9341f214
commit 5c933a99a3
29 files changed
+2008 -110

No files matched your search

@@ -4,6 +4,9 @@ import androidx.test.platform.app.InstrumentationRegistry
import de.rfc1437.ironstorage.bridge.generated.MobileAuthenticationFfiException
import de.rfc1437.ironstorage.bridge.generated.MobileClock
import de.rfc1437.ironstorage.bridge.generated.MobileEntryEditorInput
import de.rfc1437.ironstorage.bridge.generated.MobileHomeErrorKind
import de.rfc1437.ironstorage.bridge.generated.MobileHomeFfiException
import de.rfc1437.ironstorage.bridge.generated.MobileHomeFreshness
import de.rfc1437.ironstorage.bridge.generated.MobileOnboardingErrorKind
import de.rfc1437.ironstorage.bridge.generated.MobileOnboardingFfiException
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateDirectories
@@ -21,6 +24,80 @@ import org.junit.Assume.assumeTrue
import org.junit.Test
class AndroidOnboardingTest {
@Test fun homeUsesThePrivateRuntimeAndSharesDirtyEditorReservations() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val id = "home-${UUID.randomUUID()}"
val directories = MobilePrivateDirectories(
File(context.filesDir, id).apply { mkdirs() }.canonicalPath,
File(context.noBackupFilesDir, id).apply { mkdirs() }.canonicalPath,
File(context.cacheDir, id).apply { mkdirs() }.canonicalPath
)
val backend = MemoryBackend()
val clock = object : MobileClock {
override fun monotonicMilliseconds(): ULong =
android.os.SystemClock.elapsedRealtime().toULong()
}
NativeRuntime(context).privateStorage(directories).use { runtime ->
runtime.androidOnboarding(backend).use { setup ->
val key = requireNotNull(
setup.generate("Home fixture", "public-test-passphrase").selectedKey
)
setup.setupLocal(key, "public-test-passphrase")
}
runtime.authentication(backend, clock).use { authentication ->
val draft = authentication.beginCreateEntry("", "draft", "public-test-passphrase")
runtime.androidHomeOperation(backend, clock).use { home ->
assertFalse(home.cached().remoteConfigured)
assertFalse(home.activityKnown())
for (action in listOf({ home.pull() }, { home.push() })) {
val error =
assertThrows(MobileHomeFfiException.Failed::class.java) { action() }
assertEquals(MobileHomeErrorKind.DIRTY_LOCAL_CHANGES, error.kind)
}
assertTrue(authentication.entryEditor(draft.id).dirty)
}
authentication.saveEntryEditor(
draft.id,
listOf(
MobileEntryEditorInput(draft.page.fields[0].id, null, "fixture-password")
)
)
// Seed an uncommitted encrypted fixture; application code never reads the store.
val vault = File(runtime.layout().vault)
File(vault, "commit-fixture.gpg").writeBytes(File(vault, "draft.gpg").readBytes())
runtime.androidHomeOperation(backend, clock).use { home ->
assertTrue(home.cached().summaries.any { it.id.startsWith("local:") })
assertEquals(
"Changes Committed",
home.commit("Encrypted Home fixture").notice?.title
)
}
authentication.manualLock()
runtime.androidHomeOperation(backend, clock).use { home ->
assertEquals(MobileHomeFreshness.CURRENT, home.refreshGesture().freshness)
assertTrue(home.identity().name.isNotEmpty())
val error = assertThrows(MobileHomeFfiException.Failed::class.java) {
home.commit("No encrypted changes")
}
assertEquals(MobileHomeErrorKind.NO_CHANGES, error.kind)
for (action in listOf({ home.fetch() }, { home.pull() }, { home.push() })) {
val missing =
assertThrows(MobileHomeFfiException.Failed::class.java) { action() }
assertEquals(MobileHomeErrorKind.CONFIGURATION, missing.kind)
}
assertFalse(authentication.state().unlocked)
}
runtime.androidHomeOperation(backend, clock).use { home ->
home.cancel()
val error = assertThrows(MobileHomeFfiException.Failed::class.java) {
home.commit("Cancelled commit")
}
assertEquals(MobileHomeErrorKind.INTERRUPTED, error.kind)
}
}
}
}
@Test fun privateNavigationTraversesWithoutAuthenticationAndRejectsReplacedEntries() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val id = "navigation-${UUID.randomUUID()}"
@@ -193,6 +270,17 @@ class AndroidOnboardingTest {
assertFalse(config.contains("public-test-token"))
assertFalse(config.contains("public-replacement-token"))
}
val clock = object : MobileClock {
override fun monotonicMilliseconds(): ULong =
android.os.SystemClock.elapsedRealtime().toULong()
}
runtime.androidHomeOperation(backend, clock).use { home ->
assertTrue(home.cached().remoteConfigured)
assertEquals(MobileHomeFreshness.CURRENT, home.fetch().freshness)
// The local main branch is absent from this public repository's master branch.
assertFalse(home.activityKnown())
assertEquals("Tracked Branch Is Unavailable", home.cached().notice?.title)
}
}
}