package link.roadio.riff.player.storage import android.os.SystemClock import androidx.test.platform.app.InstrumentationRegistry import link.roadio.riff.storage.root.ApplicationDataLocation import link.roadio.riff.storage.root.ApplicationDataSelection import link.roadio.riff.storage.root.ApplicationDataStorage import link.roadio.riff.storage.root.RootBootstrap import link.roadio.riff.userstate.UserStateReplica import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Assume.assumeTrue import org.junit.Test import java.io.File /** Explicitly destructive opt-in fixture, isolated to the disposable application only. */ class ConfirmedResetInstrumentedTest { @Test fun confirmedUnavailableRootResetDiscardsAcceptedRamAndCannotPopulateFreshInternalWithOldState() = exercise(false) @Test fun lostResetReplyReconcilesCommittedBootstrapWithoutResurrectingOldRam() = exercise(true) private fun exercise(lostReply: Boolean) { assumeTrue(InstrumentationRegistry.getArguments().getString("riffRootResetProbe") == "true") val context = InstrumentationRegistry.getInstrumentation().targetContext check(context.packageName == "link.roadio.riff.instrumented") val storage = ApplicationDataStorage(context) assertTrue(storage.prepareForStartup()) assertEquals(ApplicationDataLocation.HU, storage.selection().location) PlayerProcessStore.closeForRootChange() val store = checkNotNull(PlayerProcessStore.open(context, 15)) val field = PlayerStateStore::class.java.getDeclaredField("state").apply { isAccessible = true } val replica = field.get(store) as UserStateReplica val oldRoot = replica.rootId val settings = store.loadSettings(link.roadio.riff.player.core.settings.PlayerDataLocation.HU) store.saveSettings(settings.copy(autoSearch = !settings.autoSearch)) val pending = replica.completion() val deadline = SystemClock.elapsedRealtime() + 4000 while (replica.next() != null && SystemClock.elapsedRealtime() < deadline) SystemClock.sleep(10) assertTrue(replica.next() == null) assertFalse(pending.isDone) val missing = ApplicationDataSelection(ApplicationDataLocation.SD, "missing-reset-probe") try { RootBootstrap(context) .edit() .putString("location", missing.location.name) .putString("portable_volume_id", missing.portableVolumeId) .commit() assertEquals(missing, storage.selection()) assertFalse(storage.selectedRootAvailable()) if (lostReply) { context.contentResolver.call( android.net.Uri.parse("content://${context.packageName}.configuration"), "reset-internal", null, android.os.Bundle().apply { putString("location", missing.location.name) putString("volume", missing.portableVolumeId) }, ) // Deliberately discard the reply, then run the exact production exception recovery. assertTrue(PlayerProcessStore.recoverConfirmedReset(context, missing)) } else { assertTrue(PlayerProcessStore.resetToInternal(context, missing)) } assertTrue(pending.isCompletedExceptionally) assertEquals(ApplicationDataLocation.HU, storage.selection().location) var fresh: PlayerStateStore? = null val reopen = SystemClock.elapsedRealtime() + 30_000 while (fresh == null && SystemClock.elapsedRealtime() < reopen) { fresh = PlayerProcessStore.open(context, 15) if (fresh == null) SystemClock.sleep(50) } assertNotNull(fresh) val next = field.get(checkNotNull(fresh)) as UserStateReplica assertNotEquals(oldRoot, next.rootId) assertEquals(0L, next.profile(1).header.generation) assertTrue(fresh.listPlaylists().isEmpty()) assertTrue(fresh.loadSettings(link.roadio.riff.player.core.settings.PlayerDataLocation.HU).autoSearch) assertTrue(File(context.noBackupFilesDir.path).listFiles().orEmpty().any { it.name.startsWith(".riff-data-v1.reset-") }) PlayerProcessStore.closeForRootChange() } finally { if (storage.selection().location != ApplicationDataLocation.HU) { RootBootstrap(context) .edit() .putString("location", "HU") .remove("portable_volume_id") .commit() } } } }