Napraw synchronizację statusu kursu kierowcy
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package pl.firmatpp.kierowca.data.upload
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class RouteActionPrerequisiteTest {
|
||||
@Test
|
||||
fun finishWaitsUntilEarlierStartIsConfirmed() {
|
||||
val finish = action(RouteActionType.Finish, RouteActionStatus.Pending, createdAt = 2_000)
|
||||
val pendingStart = action(RouteActionType.Start, RouteActionStatus.Syncing, createdAt = 1_000)
|
||||
|
||||
assertEquals(
|
||||
RouteActionPrerequisiteState.Waiting,
|
||||
routeActionPrerequisiteState(finish, pendingStart),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun finishRunsAfterEarlierStartIsConfirmed() {
|
||||
val finish = action(RouteActionType.Finish, RouteActionStatus.Pending, createdAt = 2_000)
|
||||
val confirmedStart = action(RouteActionType.Start, RouteActionStatus.Confirmed, createdAt = 1_000)
|
||||
|
||||
assertEquals(
|
||||
RouteActionPrerequisiteState.Ready,
|
||||
routeActionPrerequisiteState(finish, confirmedStart),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun failedStartBlocksFinishInsteadOfSendingItOutOfOrder() {
|
||||
val finish = action(RouteActionType.Finish, RouteActionStatus.Pending, createdAt = 2_000)
|
||||
val failedStart = action(RouteActionType.Start, RouteActionStatus.FailedConflict, createdAt = 1_000)
|
||||
|
||||
assertEquals(
|
||||
RouteActionPrerequisiteState.Failed,
|
||||
routeActionPrerequisiteState(finish, failedStart),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun finishWithoutLocalStartCanUseServerLifecycleState() {
|
||||
val finish = action(RouteActionType.Finish, RouteActionStatus.Pending, createdAt = 2_000)
|
||||
|
||||
assertEquals(
|
||||
RouteActionPrerequisiteState.Ready,
|
||||
routeActionPrerequisiteState(finish, null),
|
||||
)
|
||||
}
|
||||
|
||||
private fun action(type: String, status: String, createdAt: Long): RouteActionEntity =
|
||||
RouteActionEntity(
|
||||
clientActionId = "$type-$createdAt",
|
||||
routeId = "route-1",
|
||||
action = type,
|
||||
weight = 12.5,
|
||||
occurredAt = "2026-07-23T10:00:00Z",
|
||||
photoClientRequestIdsJson = "[]",
|
||||
status = status,
|
||||
createdAtEpochMillis = createdAt,
|
||||
)
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package pl.firmatpp.kierowca.data.upload
|
||||
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import pl.firmatpp.kierowca.data.model.DriverRouteDto
|
||||
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
|
||||
|
||||
class RouteActionServerReconciliationTest {
|
||||
@Test
|
||||
fun matchingFinishedRouteConfirmsDuplicateFinishSafely() {
|
||||
assertTrue(
|
||||
routeActionMatchesServer(
|
||||
action = finish(weight = 11.8, notes = "Uszkodzona plomba."),
|
||||
photoClientRequestIds = listOf("photo-1"),
|
||||
serverRoute = route(
|
||||
status = "ZAKOŃCZONA",
|
||||
weight = 11.8,
|
||||
notes = "Uszkodzona plomba.",
|
||||
photoClientRequestIds = listOf("photo-1"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun differingNotesKeepConflictVisibleAndPreserveLocalData() {
|
||||
assertFalse(
|
||||
routeActionMatchesServer(
|
||||
action = finish(weight = 11.8, notes = "Uwagi z telefonu"),
|
||||
photoClientRequestIds = listOf("photo-1"),
|
||||
serverRoute = route(
|
||||
status = "ZAKOŃCZONA",
|
||||
weight = 11.8,
|
||||
notes = "Inna uwaga na serwerze",
|
||||
photoClientRequestIds = listOf("photo-1"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingServerPhotoDoesNotDiscardLocalFinish() {
|
||||
assertFalse(
|
||||
routeActionMatchesServer(
|
||||
action = finish(weight = null, notes = null),
|
||||
photoClientRequestIds = listOf("photo-1"),
|
||||
serverRoute = route(status = "ZAKOŃCZONA", weight = null, notes = null),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun finish(weight: Double?, notes: String?): RouteActionEntity =
|
||||
RouteActionEntity(
|
||||
clientActionId = "finish-1",
|
||||
routeId = "route-1",
|
||||
action = RouteActionType.Finish,
|
||||
weight = weight,
|
||||
occurredAt = "2026-07-23T10:00:00Z",
|
||||
photoClientRequestIdsJson = """["photo-1"]""",
|
||||
notes = notes,
|
||||
)
|
||||
|
||||
private fun route(
|
||||
status: String,
|
||||
weight: Double?,
|
||||
notes: String?,
|
||||
photoClientRequestIds: List<String> = emptyList(),
|
||||
): DriverRouteDto =
|
||||
DriverRouteDto(
|
||||
id = "route-1",
|
||||
routeDate = "2026-07-23",
|
||||
startsAt = "",
|
||||
originName = "Baza",
|
||||
destinationName = "Cel",
|
||||
contractorName = "TPP",
|
||||
contractName = "Kontrakt",
|
||||
contractCode = "TPP-1",
|
||||
relationLabel = "Baza → Cel",
|
||||
status = status,
|
||||
driverStatus = status,
|
||||
unloadingWeight = weight,
|
||||
unloadingNotes = notes,
|
||||
distanceKm = 1.0,
|
||||
notes = null,
|
||||
truck = null,
|
||||
photos = photoClientRequestIds.mapIndexed { index, requestId ->
|
||||
RoutePhotoDto(
|
||||
id = "photo-$index",
|
||||
routeId = "route-1",
|
||||
clientRequestId = requestId,
|
||||
source = "camera",
|
||||
stage = "unloading",
|
||||
mimeType = "image/jpeg",
|
||||
size = 1,
|
||||
url = "https://example.test/photo-$index",
|
||||
takenAt = null,
|
||||
latitude = null,
|
||||
longitude = null,
|
||||
locationAccuracyMeters = null,
|
||||
createdAt = null,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -125,16 +125,25 @@ class DriverRouteProjectionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun conflictKeepsLocalRouteValuesAndMarksStageAsAttention() {
|
||||
fun conflictKeepsLocalRouteValuesButPreservesServerLifecycleStatus() {
|
||||
val projection = projectDriverRoute(
|
||||
route = route(status = "W TRAKCIE", loadingWeight = 12.5),
|
||||
actions = listOf(action(RouteActionType.Finish, RouteActionStatus.FailedConflict, weight = 11.8)),
|
||||
actions = listOf(
|
||||
action(
|
||||
RouteActionType.Finish,
|
||||
RouteActionStatus.FailedConflict,
|
||||
weight = 11.8,
|
||||
notes = "Uszkodzona plomba.",
|
||||
),
|
||||
),
|
||||
)
|
||||
val steps = routeFlowSteps(projection.route, projection.visibleActions)
|
||||
val callout = routeSyncCallout(projection.visibleActions)
|
||||
|
||||
assertEquals("ZAKOŃCZONA", projection.route.status)
|
||||
assertEquals("W TRAKCIE", projection.route.status)
|
||||
assertEquals(11.8, projection.route.unloadingWeight)
|
||||
assertEquals("Uszkodzona plomba.", projection.route.unloadingNotes)
|
||||
assertTrue(projection.unloadingWeightPending)
|
||||
assertEquals(RouteFlowStepState.NeedsAttention, steps[3].state)
|
||||
assertEquals("Kurs wymaga obsługi. Dane wpisane w telefonie zostały zachowane.", callout?.text)
|
||||
assertTrue(callout?.isConflict ?: false)
|
||||
@@ -148,8 +157,9 @@ class DriverRouteProjectionTest {
|
||||
)
|
||||
val steps = routeFlowSteps(projection.route, projection.visibleActions)
|
||||
|
||||
assertEquals("W TRAKCIE", projection.route.status)
|
||||
assertEquals("ZAPLANOWANA", projection.route.status)
|
||||
assertEquals(12.5, projection.route.loadingWeight)
|
||||
assertTrue(projection.loadingWeightPending)
|
||||
assertEquals(RouteFlowStepState.NeedsAttention, steps[1].state)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user