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) {
|
||||
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 ->
|
||||
db.rawQuery(
|
||||
"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
|
||||
* exact generation and resumes ACK handling; it never recaptures
|
||||
* mutable application state under an existing identity.
|
||||
* INVARIANT: only an active generation for the correlated desktop
|
||||
* consumer is resumable; acknowledged or archived runs remain
|
||||
* excluded from new foreground work. */
|
||||
* INVARIANT: a completed inbound ledger makes the run ineligible;
|
||||
* an acknowledged outbound generation remains resumable only to
|
||||
* finish its missing inbound half. */
|
||||
val captured =
|
||||
resumableGeneration(
|
||||
request.getString("run_id"),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,54 @@ import org.robolectric.annotation.Config
|
|||
class SyncGenerationServiceTest {
|
||||
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
|
||||
fun v19MigrationPreservesDataAndInventsNoGenerationState() {
|
||||
val name = "generation-migration-${UUID.randomUUID()}.db"
|
||||
|
|
|
|||
Loading…
Reference in a new issue