diff --git a/experimentation/src/main/java/com/automattic/android/experimentation/local/FileBasedCache.kt b/experimentation/src/main/java/com/automattic/android/experimentation/local/FileBasedCache.kt index 2fe3895..580adc7 100644 --- a/experimentation/src/main/java/com/automattic/android/experimentation/local/FileBasedCache.kt +++ b/experimentation/src/main/java/com/automattic/android/experimentation/local/FileBasedCache.kt @@ -31,20 +31,33 @@ internal class FileBasedCache( init { scope.launch { withContext(context = dispatcher) { - latestMutable = getAssignments() + runCatching { latestMutable = getAssignments() } + .onFailure { throwable -> logger.e("Failed to load cached assignments", throwable) } } } } suspend fun getAssignments(): Assignments? { return withContext(dispatcher) { - assignmentsFile.takeIf { it.exists() }?.readText()?.let { json: String -> - val fromJson = cacheDtoJsonAdapter.fromJson(json) ?: return@let null - - fromJson.assignmentsDto.toAssignments( - fetchedAt = fromJson.fetchedAt, - anonymousId = fromJson.anonymousId, - ) + assignmentsFile.takeIf { it.exists() }?.let { file -> + val json = file.readText() + if (json.isBlank()) { + logger.e("Cached assignments file is empty, deleting: ${file.path}") + file.delete() + return@withContext null + } + runCatching { cacheDtoJsonAdapter.fromJson(json) } + .onFailure { throwable -> + logger.e("Cached assignments file is corrupted, deleting: ${file.path}", throwable) + file.delete() + } + .getOrNull() + ?.let { fromJson -> + fromJson.assignmentsDto.toAssignments( + fetchedAt = fromJson.fetchedAt, + anonymousId = fromJson.anonymousId, + ) + } } } } diff --git a/experimentation/src/test/java/com/automattic/android/experimentation/local/FileBasedCacheTest.kt b/experimentation/src/test/java/com/automattic/android/experimentation/local/FileBasedCacheTest.kt index 166d52c..2ed78e9 100644 --- a/experimentation/src/test/java/com/automattic/android/experimentation/local/FileBasedCacheTest.kt +++ b/experimentation/src/test/java/com/automattic/android/experimentation/local/FileBasedCacheTest.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Rule import org.junit.Test @@ -66,6 +67,30 @@ internal class FileBasedCacheTest { sut.clear() } + @Test + fun `getting assignments from empty file returns null and deletes the file`() = runTest { + val cacheDir = tempDir.newFolder() + val assignmentsFile = File(cacheDir, "assignments.json").apply { createNewFile() } + val sut = fileBasedCache(this, cacheDir = cacheDir) + + val result = sut.getAssignments() + + assertNull(result) + assertFalse(assignmentsFile.exists()) + } + + @Test + fun `getting assignments from corrupted file returns null and deletes the file`() = runTest { + val cacheDir = tempDir.newFolder() + val assignmentsFile = File(cacheDir, "assignments.json").apply { writeText("{corrupted") } + val sut = fileBasedCache(this, cacheDir = cacheDir) + + val result = sut.getAssignments() + + assertNull(result) + assertFalse(assignmentsFile.exists()) + } + @Test fun `saving cache when cache dir doesnt exist is successful`() = runTest { val cacheDir = File(tempDir.newFolder(), "cache").apply { assert(!this.exists()) }