Test terminal generation resume boundary
This commit is contained in:
parent
1fc15d6214
commit
823184d99e
2 changed files with 52 additions and 4 deletions
|
|
@ -17,7 +17,7 @@ internal sealed interface ForegroundGenerationResult {
|
||||||
internal class SyncGenerationForegroundCoordinator(private val repository: TrainlogRepository) {
|
internal class SyncGenerationForegroundCoordinator(private val repository: TrainlogRepository) {
|
||||||
private val service = SyncGenerationService(repository)
|
private val service = SyncGenerationService(repository)
|
||||||
|
|
||||||
private fun resumableGeneration(runId: String, consumerPeerId: String? = null): CapturedSyncGeneration? =
|
internal fun resumableGeneration(runId: String, consumerPeerId: String? = null): CapturedSyncGeneration? =
|
||||||
repository.inSyncGenerationTransaction { db ->
|
repository.inSyncGenerationTransaction { db ->
|
||||||
db.rawQuery(
|
db.rawQuery(
|
||||||
"SELECT generation_id,manifest_sha256,staging_path FROM sync_generations " +
|
"SELECT generation_id,manifest_sha256,staging_path FROM sync_generations " +
|
||||||
|
|
@ -209,9 +209,9 @@ internal class SyncGenerationForegroundCoordinator(private val repository: Train
|
||||||
* CONTRACT: an explicit retry for the same run republishes that
|
* CONTRACT: an explicit retry for the same run republishes that
|
||||||
* exact generation and resumes ACK handling; it never recaptures
|
* exact generation and resumes ACK handling; it never recaptures
|
||||||
* mutable application state under an existing identity.
|
* mutable application state under an existing identity.
|
||||||
* INVARIANT: only an active generation for the correlated desktop
|
* INVARIANT: a completed inbound ledger makes the run ineligible;
|
||||||
* consumer is resumable; acknowledged or archived runs remain
|
* an acknowledged outbound generation remains resumable only to
|
||||||
* excluded from new foreground work. */
|
* finish its missing inbound half. */
|
||||||
val captured =
|
val captured =
|
||||||
resumableGeneration(
|
resumableGeneration(
|
||||||
request.getString("run_id"),
|
request.getString("run_id"),
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,54 @@ import org.robolectric.annotation.Config
|
||||||
class SyncGenerationServiceTest {
|
class SyncGenerationServiceTest {
|
||||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun acknowledgedOutboundRemainsResumableUntilInboundIsTerminal() {
|
||||||
|
val sourceName = "generation-resume-source-${UUID.randomUUID()}.db"
|
||||||
|
val destinationName = "generation-resume-destination-${UUID.randomUUID()}.db"
|
||||||
|
val root = Files.createTempDirectory("trainlog-generation-resume-").toFile()
|
||||||
|
val source = TrainlogRepository(context, sourceName)
|
||||||
|
val destination = TrainlogRepository(context, destinationName)
|
||||||
|
try {
|
||||||
|
val sourceService = SyncGenerationService(source)
|
||||||
|
val destinationService = SyncGenerationService(destination)
|
||||||
|
val runId = "sy_${UUID.randomUUID()}"
|
||||||
|
val outbound =
|
||||||
|
sourceService.capture(
|
||||||
|
java.io.File(root, "source-owned"),
|
||||||
|
destinationService.peerId(),
|
||||||
|
runId,
|
||||||
|
)
|
||||||
|
val outboundDirectory =
|
||||||
|
sourceService.publish(outbound, java.io.File(root, "source-objects"))
|
||||||
|
sourceService.acceptAcknowledgement(
|
||||||
|
destinationService.consume(outboundDirectory).toByteArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
val coordinator = SyncGenerationForegroundCoordinator(source)
|
||||||
|
assertEquals(
|
||||||
|
outbound.generationId,
|
||||||
|
coordinator.resumableGeneration(runId, destinationService.peerId())?.generationId,
|
||||||
|
)
|
||||||
|
|
||||||
|
val inbound =
|
||||||
|
destinationService.capture(
|
||||||
|
java.io.File(root, "destination-owned"),
|
||||||
|
sourceService.peerId(),
|
||||||
|
runId,
|
||||||
|
)
|
||||||
|
sourceService.consume(
|
||||||
|
destinationService.publish(inbound, java.io.File(root, "destination-objects"))
|
||||||
|
)
|
||||||
|
assertEquals(null, coordinator.resumableGeneration(runId, destinationService.peerId()))
|
||||||
|
} finally {
|
||||||
|
source.close()
|
||||||
|
destination.close()
|
||||||
|
context.deleteDatabase(sourceName)
|
||||||
|
context.deleteDatabase(destinationName)
|
||||||
|
root.deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun v19MigrationPreservesDataAndInventsNoGenerationState() {
|
fun v19MigrationPreservesDataAndInventsNoGenerationState() {
|
||||||
val name = "generation-migration-${UUID.randomUUID()}.db"
|
val name = "generation-migration-${UUID.randomUUID()}.db"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue