Napraw synchronizację kursu w aplikacji kierowcy

This commit is contained in:
admin
2026-07-17 13:28:49 +02:00
parent 905711d8de
commit 431436d0db
23 changed files with 448 additions and 65 deletions
@@ -2,12 +2,27 @@ package pl.firmatpp.kierowca.data
import java.io.IOException
import java.net.UnknownHostException
import kotlin.coroutines.cancellation.CancellationException
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Test
class ApiErrorMapperTest {
@Test
fun rethrowsWorkerCancellationInsteadOfMappingItAsApplicationFailure() {
val cancellation = CancellationException("Job was cancelled")
try {
cancellation.rethrowIfCancellation()
fail("CancellationException should be rethrown")
} catch (thrown: CancellationException) {
assertSame(cancellation, thrown)
}
}
@Test
fun mapsNetworkFailuresToRetryableOfflineMessage() {
val error = ApiErrorMapper.map(IOException("timeout"))
@@ -0,0 +1,43 @@
package pl.firmatpp.kierowca.data.upload
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class OfflineOutboxManagerTest {
@Test
fun recognizesLegacyCancellationFailureDetails() {
assertTrue(isWorkerCancellationFailure("Typ: błąd aplikacji\nKomunikat: Job was cancelled"))
assertTrue(isWorkerCancellationFailure("kotlinx.coroutines.JobCancellationException"))
assertFalse(isWorkerCancellationFailure("HTTP 422: Niepoprawne zdjęcie"))
assertFalse(isWorkerCancellationFailure(null))
}
@Test
fun repairsInterruptedAndRetryablePhotoUploadsButNotRealPermanentFailures() {
assertTrue(shouldRepairPhotoUpload(PhotoUploadStatus.Pending.storageValue, null))
assertTrue(shouldRepairPhotoUpload(PhotoUploadStatus.Uploading.storageValue, null))
assertTrue(shouldRepairPhotoUpload(PhotoUploadStatus.Verifying.storageValue, null))
assertTrue(shouldRepairPhotoUpload(PhotoUploadStatus.FailedRetryable.storageValue, "timeout"))
assertTrue(shouldRepairPhotoUpload(PhotoUploadStatus.FailedPermanent.storageValue, "Job was cancelled"))
assertFalse(shouldRepairPhotoUpload(PhotoUploadStatus.FailedPermanent.storageValue, "HTTP 422"))
assertFalse(shouldRepairPhotoUpload(PhotoUploadStatus.Cancelled.storageValue, null))
}
@Test
fun repairsInterruptedRouteActionWithItsOriginalIdempotencyKey() {
assertTrue(shouldRepairRouteAction(RouteActionStatus.Pending, null))
assertTrue(shouldRepairRouteAction(RouteActionStatus.WaitingForPhotos, null))
assertTrue(shouldRepairRouteAction(RouteActionStatus.Syncing, null))
assertTrue(shouldRepairRouteAction(RouteActionStatus.FailedRetryable, "timeout"))
assertTrue(shouldRepairRouteAction(RouteActionStatus.FailedPermanent, "Job was cancelled"))
assertTrue(
shouldRepairRouteAction(
RouteActionStatus.FailedPermanent,
"Wymagane zdjęcie nie zostało wysłane. Popraw zdjęcie i ponów potwierdzenie etapu.",
),
)
assertFalse(shouldRepairRouteAction(RouteActionStatus.FailedPermanent, "Brakuje zdjęcia"))
assertFalse(shouldRepairRouteAction(RouteActionStatus.FailedConflict, "Kurs już rozpoczęty"))
}
}
@@ -0,0 +1,62 @@
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.RoutePhotoDto
class PhotoUploadReconciliationTest {
@Test
fun serverReceiptIdentityClearsFalseLocalFailure() {
val upload = upload(
clientRequestId = "request-1",
contentSha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
)
assertTrue(serverPhotoConfirmsUpload(upload, serverPhoto("request-1", upload.contentSha256.uppercase())))
assertFalse(serverPhotoConfirmsUpload(upload, serverPhoto("request-2", upload.contentSha256)))
assertFalse(serverPhotoConfirmsUpload(upload, serverPhoto("request-1", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")))
}
@Test
fun legacyServerPhotoWithoutHashStillConfirmsMatchingRequestId() {
val upload = upload("request-1", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
assertTrue(serverPhotoConfirmsUpload(upload, serverPhoto("request-1", null)))
}
private fun upload(clientRequestId: String, contentSha256: String): PhotoUploadEntity =
PhotoUploadEntity(
clientRequestId = clientRequestId,
routeId = "10",
localPath = "/tmp/$clientRequestId.jpg",
source = "camera",
takenAt = null,
latitude = null,
longitude = null,
locationAccuracyMeters = null,
mimeType = "image/jpeg",
size = 100,
contentSha256 = contentSha256,
status = PhotoUploadStatus.FailedPermanent.storageValue,
lastError = "Typ: błąd aplikacji\nKomunikat: Job was cancelled",
)
private fun serverPhoto(clientRequestId: String, contentSha256: String?): RoutePhotoDto =
RoutePhotoDto(
id = "42",
routeId = "10",
clientRequestId = clientRequestId,
contentSha256 = contentSha256,
source = "camera",
stage = "loading",
mimeType = "image/jpeg",
size = 100,
url = "https://example.test/photo/42",
takenAt = null,
latitude = null,
longitude = null,
locationAccuracyMeters = null,
createdAt = null,
)
}