Wrap delete and save operations in database transactions (#2450)

This commit is contained in:
Mikołaj Pich 2024-03-02 17:25:27 +01:00 committed by GitHub
parent fb240938ed
commit 333306e7ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 587 additions and 366 deletions

View File

@ -2,24 +2,14 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction
import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.AdminMessage
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
abstract class AdminMessageDao : BaseDao<AdminMessage> { interface AdminMessageDao : BaseDao<AdminMessage> {
@Query("SELECT * FROM AdminMessages") @Query("SELECT * FROM AdminMessages")
abstract fun loadAll(): Flow<List<AdminMessage>> fun loadAll(): Flow<List<AdminMessage>>
@Transaction
open suspend fun removeOldAndSaveNew(
oldMessages: List<AdminMessage>,
newMessages: List<AdminMessage>
) {
deleteAll(oldMessages)
insertAll(newMessages)
}
} }

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
interface BaseDao<T> { interface BaseDao<T> {
@ -15,4 +16,10 @@ interface BaseDao<T> {
@Delete @Delete
suspend fun deleteAll(items: List<T>) suspend fun deleteAll(items: List<T>)
@Transaction
suspend fun removeOldAndSaveNew(oldItems: List<T>, newItems: List<T>) {
deleteAll(oldItems)
insertAll(newItems)
}
} }

View File

@ -65,12 +65,13 @@ class AttendanceRepository @Inject constructor(
.mapToEntities(semester, lessons) .mapToEntities(semester, lessons)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
attendanceDb.deleteAll(old uniqueSubtract new)
val attendanceToAdd = (new uniqueSubtract old).map { newAttendance -> val attendanceToAdd = (new uniqueSubtract old).map { newAttendance ->
newAttendance.apply { if (notify) isNotified = false } newAttendance.apply { if (notify) isNotified = false }
} }
attendanceDb.insertAll(attendanceToAdd) attendanceDb.removeOldAndSaveNew(
oldItems = old uniqueSubtract new,
newItems = attendanceToAdd,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
}, },
filterResult = { it.filter { item -> item.date in start..end } } filterResult = { it.filter { item -> item.date in start..end } }

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import androidx.room.withTransaction
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
@ -20,6 +22,7 @@ class AttendanceSummaryRepository @Inject constructor(
private val attendanceDb: AttendanceSummaryDao, private val attendanceDb: AttendanceSummaryDao,
private val sdk: Sdk, private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
private val appDatabase: AppDatabase,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
@ -46,8 +49,10 @@ class AttendanceSummaryRepository @Inject constructor(
.mapToEntities(semester, subjectId) .mapToEntities(semester, subjectId)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
attendanceDb.deleteAll(old uniqueSubtract new) appDatabase.withTransaction {
attendanceDb.insertAll(new uniqueSubtract old) attendanceDb.deleteAll(old uniqueSubtract new)
attendanceDb.insertAll(new uniqueSubtract old)
}
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
} }
) )

View File

@ -6,7 +6,13 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -53,8 +59,10 @@ class CompletedLessonsRepository @Inject constructor(
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
completedLessonsDb.deleteAll(old uniqueSubtract new) completedLessonsDb.removeOldAndSaveNew(
completedLessonsDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
}, },
filterResult = { it.filter { item -> item.date in start..end } } filterResult = { it.filter { item -> item.date in start..end } }

View File

@ -53,12 +53,12 @@ class ConferenceRepository @Inject constructor(
.filter { it.date >= startDate } .filter { it.date >= startDate }
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
val conferencesToSave = (new uniqueSubtract old).onEach { conferenceDb.removeOldAndSaveNew(
if (notify) it.isNotified = false oldItems = old uniqueSubtract new,
} newItems = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
conferenceDb.deleteAll(old uniqueSubtract new) },
conferenceDb.insertAll(conferencesToSave) )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
} }
) )

View File

@ -62,12 +62,12 @@ class ExamRepository @Inject constructor(
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
val examsToSave = (new uniqueSubtract old).onEach { examDb.removeOldAndSaveNew(
if (notify) it.isNotified = false oldItems = old uniqueSubtract new,
} newItems = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
examDb.deleteAll(old uniqueSubtract new) },
examDb.insertAll(examsToSave) )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
}, },
filterResult = { it.filter { item -> item.date in start..end } } filterResult = { it.filter { item -> item.date in start..end } }

View File

@ -87,10 +87,12 @@ class GradeRepository @Inject constructor(
new: List<GradeDescriptive>, new: List<GradeDescriptive>,
notify: Boolean notify: Boolean
) { ) {
gradeDescriptiveDb.deleteAll(old uniqueSubtract new) gradeDescriptiveDb.removeOldAndSaveNew(
gradeDescriptiveDb.insertAll((new uniqueSubtract old).onEach { oldItems = old uniqueSubtract new,
if (notify) it.isNotified = false newItems = (new uniqueSubtract old).onEach {
}) if (notify) it.isNotified = false
},
)
} }
private suspend fun refreshGradeDetails( private suspend fun refreshGradeDetails(
@ -101,13 +103,16 @@ class GradeRepository @Inject constructor(
) { ) {
val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date
?: student.registrationDate.toLocalDate() ?: student.registrationDate.toLocalDate()
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach { gradeDb.removeOldAndSaveNew(
if (it.date >= notifyBreakDate) it.apply { oldItems = oldGrades uniqueSubtract newDetails,
isRead = false newItems = (newDetails uniqueSubtract oldGrades).onEach {
if (notify) isNotified = false if (it.date >= notifyBreakDate) it.apply {
} isRead = false
}) if (notify) isNotified = false
}
},
)
} }
private suspend fun refreshGradeSummaries( private suspend fun refreshGradeSummaries(
@ -115,31 +120,43 @@ class GradeRepository @Inject constructor(
newSummary: List<GradeSummary>, newSummary: List<GradeSummary>,
notify: Boolean notify: Boolean
) { ) {
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary) gradeSummaryDb.removeOldAndSaveNew(
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary -> oldItems = oldSummaries uniqueSubtract newSummary,
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject } newItems = (newSummary uniqueSubtract oldSummaries).onEach { summary ->
summary.isPredictedGradeNotified = when { getGradeSummaryWithUpdatedNotificationState(
summary.predictedGrade.isEmpty() -> true summary = summary,
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false oldSummary = oldSummaries.find { it.subject == summary.subject },
else -> true notify = notify,
} )
summary.isFinalGradeNotified = when { },
summary.finalGrade.isEmpty() -> true )
notify && oldSummary?.finalGrade != summary.finalGrade -> false }
else -> true
}
summary.predictedGradeLastChange = when { private fun getGradeSummaryWithUpdatedNotificationState(
oldSummary == null -> Instant.now() summary: GradeSummary,
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now() oldSummary: GradeSummary?,
else -> oldSummary.predictedGradeLastChange notify: Boolean,
} ) {
summary.finalGradeLastChange = when { summary.isPredictedGradeNotified = when {
oldSummary == null -> Instant.now() summary.predictedGrade.isEmpty() -> true
summary.finalGrade != oldSummary.finalGrade -> Instant.now() notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
else -> oldSummary.finalGradeLastChange else -> true
} }
}) summary.isFinalGradeNotified = when {
summary.finalGrade.isEmpty() -> true
notify && oldSummary?.finalGrade != summary.finalGrade -> false
else -> true
}
summary.predictedGradeLastChange = when {
oldSummary == null -> Instant.now()
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
else -> oldSummary.predictedGradeLastChange
}
summary.finalGradeLastChange = when {
oldSummary == null -> Instant.now()
summary.finalGrade != oldSummary.finalGrade -> Instant.now()
else -> oldSummary.finalGradeLastChange
}
} }
fun getUnreadGrades(semester: Semester): Flow<List<Grade>> { fun getUnreadGrades(semester: Semester): Flow<List<Grade>> {

View File

@ -19,7 +19,7 @@ import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.switchSemester import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.util.* import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -62,8 +62,10 @@ class GradeStatisticsRepository @Inject constructor(
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
gradePartialStatisticsDb.deleteAll(old uniqueSubtract new) gradePartialStatisticsDb.removeOldAndSaveNew(
gradePartialStatisticsDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(partialCacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(partialCacheKey, semester))
}, },
mapResult = { items -> mapResult = { items ->
@ -80,6 +82,7 @@ class GradeStatisticsRepository @Inject constructor(
) )
listOf(summaryItem) + items listOf(summaryItem) + items
} }
else -> items.filter { it.subject == subjectName } else -> items.filter { it.subject == subjectName }
}.mapPartialToStatisticItems() }.mapPartialToStatisticItems()
} }
@ -107,8 +110,10 @@ class GradeStatisticsRepository @Inject constructor(
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
gradeSemesterStatisticsDb.deleteAll(old uniqueSubtract new) gradeSemesterStatisticsDb.removeOldAndSaveNew(
gradeSemesterStatisticsDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(semesterCacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(semesterCacheKey, semester))
}, },
mapResult = { items -> mapResult = { items ->
@ -138,6 +143,7 @@ class GradeStatisticsRepository @Inject constructor(
} }
listOf(summaryItem) + itemsWithAverage listOf(summaryItem) + itemsWithAverage
} }
else -> itemsWithAverage.filter { it.subject == subjectName } else -> itemsWithAverage.filter { it.subject == subjectName }
}.mapSemesterToStatisticItems() }.mapSemesterToStatisticItems()
} }
@ -163,8 +169,10 @@ class GradeStatisticsRepository @Inject constructor(
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
gradePointsStatisticsDb.deleteAll(old uniqueSubtract new) gradePointsStatisticsDb.removeOldAndSaveNew(
gradePointsStatisticsDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(pointsCacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(pointsCacheKey, semester))
}, },
mapResult = { items -> mapResult = { items ->

View File

@ -61,14 +61,14 @@ class HomeworkRepository @Inject constructor(
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
val homeWorkToSave = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
}
val filteredOld = old.filterNot { it.isAddedByUser } val filteredOld = old.filterNot { it.isAddedByUser }
homeworkDb.deleteAll(filteredOld uniqueSubtract new) homeworkDb.removeOldAndSaveNew(
homeworkDb.insertAll(homeWorkToSave) oldItems = filteredOld uniqueSubtract new,
newItems = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
},
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
} }
) )

View File

@ -18,7 +18,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class LuckyNumberRepository @Inject constructor( class LuckyNumberRepository @Inject constructor(
private val luckyNumberDb: LuckyNumberDao, private val luckyNumberDb: LuckyNumberDao,
private val sdk: Sdk private val sdk: Sdk,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
@ -39,11 +39,10 @@ class LuckyNumberRepository @Inject constructor(
newLuckyNumber ?: return@networkBoundResource newLuckyNumber ?: return@networkBoundResource
if (newLuckyNumber != oldLuckyNumber) { if (newLuckyNumber != oldLuckyNumber) {
val updatedLuckNumberList = luckyNumberDb.removeOldAndSaveNew(
listOf(newLuckyNumber.apply { if (notify) isNotified = false }) oldItems = listOfNotNull(oldLuckyNumber),
newItems = listOf(newLuckyNumber.apply { if (notify) isNotified = false }),
oldLuckyNumber?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) } )
luckyNumberDb.insertAll(updatedLuckNumberList)
} }
} }
) )

View File

@ -89,12 +89,13 @@ class MessageRepository @Inject constructor(
}, },
saveFetchResult = { oldWithAuthors, new -> saveFetchResult = { oldWithAuthors, new ->
val old = oldWithAuthors.map { it.message } val old = oldWithAuthors.map { it.message }
messagesDb.deleteAll(old uniqueSubtract new) messagesDb.removeOldAndSaveNew(
messagesDb.insertAll((new uniqueSubtract old).onEach { oldItems = old uniqueSubtract new,
val muted = isMuted(it.correspondents) newItems = (new uniqueSubtract old).onEach {
it.isNotified = !notify || muted val muted = isMuted(it.correspondents)
}) it.isNotified = !notify || muted
},
)
refreshHelper.updateLastRefreshTimestamp( refreshHelper.updateLastRefreshTimestamp(
getRefreshKey(messagesCacheKey, mailbox, folder) getRefreshKey(messagesCacheKey, mailbox, folder)
) )

View File

@ -48,9 +48,10 @@ class MobileDeviceRepository @Inject constructor(
.mapToEntities(student) .mapToEntities(student)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
mobileDb.deleteAll(old uniqueSubtract new) mobileDb.removeOldAndSaveNew(
mobileDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
} }
) )

View File

@ -7,7 +7,12 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.switchSemester
import io.github.wulkanowy.utils.toLocalDate
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
@ -46,14 +51,16 @@ class NoteRepository @Inject constructor(
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
noteDb.deleteAll(old uniqueSubtract new) val notesToAdd = (new uniqueSubtract old).onEach {
noteDb.insertAll((new uniqueSubtract old).onEach {
if (it.date >= student.registrationDate.toLocalDate()) it.apply { if (it.date >= student.registrationDate.toLocalDate()) it.apply {
isRead = false isRead = false
if (notify) isNotified = false if (notify) isNotified = false
} }
}) }
noteDb.removeOldAndSaveNew(
oldItems = old uniqueSubtract new,
newItems = notesToAdd,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
} }
) )

View File

@ -1,7 +1,11 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.RecipientDao import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
@ -25,8 +29,10 @@ class RecipientRepository @Inject constructor(
.mapToEntities(mailbox.globalKey) .mapToEntities(mailbox.globalKey)
val old = recipientDb.loadAll(type, mailbox.globalKey) val old = recipientDb.loadAll(type, mailbox.globalKey)
recipientDb.deleteAll(old uniqueSubtract new) recipientDb.removeOldAndSaveNew(
recipientDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
} }

View File

@ -47,12 +47,12 @@ class SchoolAnnouncementRepository @Inject constructor(
lastAnnouncements + directorInformation lastAnnouncements + directorInformation
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
val schoolAnnouncementsToSave = (new uniqueSubtract old).onEach { schoolAnnouncementDb.removeOldAndSaveNew(
if (notify) it.isNotified = false oldItems = old uniqueSubtract new,
} newItems = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
schoolAnnouncementDb.deleteAll(old uniqueSubtract new) },
schoolAnnouncementDb.insertAll(schoolAnnouncementsToSave) )
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
} }
) )

View File

@ -47,10 +47,10 @@ class SchoolRepository @Inject constructor(
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
if (old != null && new != old) { if (old != null && new != old) {
with(schoolDb) { schoolDb.removeOldAndSaveNew(
deleteAll(listOf(old)) oldItems = listOf(old),
insertAll(listOf(new)) newItems = listOf(new)
} )
} else if (old == null) { } else if (old == null) {
schoolDb.insertAll(listOf(new)) schoolDb.insertAll(listOf(new))
} }

View File

@ -5,7 +5,11 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.isCurrent
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -15,7 +19,7 @@ import javax.inject.Singleton
class SemesterRepository @Inject constructor( class SemesterRepository @Inject constructor(
private val semesterDb: SemesterDao, private val semesterDb: SemesterDao,
private val sdk: Sdk, private val sdk: Sdk,
private val dispatchers: DispatchersProvider private val dispatchers: DispatchersProvider,
) { ) {
suspend fun getSemesters( suspend fun getSemesters(
@ -45,6 +49,7 @@ class SemesterRepository @Inject constructor(
0 == it.diaryId && 0 == it.kindergartenDiaryId 0 == it.diaryId && 0 == it.kindergartenDiaryId
} == true } == true
} }
else -> false else -> false
} }
@ -59,8 +64,10 @@ class SemesterRepository @Inject constructor(
if (new.isEmpty()) return Timber.i("Empty semester list!") if (new.isEmpty()) return Timber.i("Empty semester list!")
val old = semesterDb.loadAll(student.studentId, student.classId) val old = semesterDb.loadAll(student.studentId, student.classId)
semesterDb.deleteAll(old.uniqueSubtract(new)) semesterDb.removeOldAndSaveNew(
semesterDb.insertSemesters(new.uniqueSubtract(old)) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
} }
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =

View File

@ -15,7 +15,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class StudentInfoRepository @Inject constructor( class StudentInfoRepository @Inject constructor(
private val studentInfoDao: StudentInfoDao, private val studentInfoDao: StudentInfoDao,
private val sdk: Sdk private val sdk: Sdk,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
@ -36,10 +36,10 @@ class StudentInfoRepository @Inject constructor(
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
if (old != null && new != old) { if (old != null && new != old) {
with(studentInfoDao) { studentInfoDao.removeOldAndSaveNew(
deleteAll(listOf(old)) oldItems = listOf(old),
insertAll(listOf(new)) newItems = listOf(new),
} )
} else if (old == null) { } else if (old == null) {
studentInfoDao.insertAll(listOf(new)) studentInfoDao.insertAll(listOf(new))
} }

View File

@ -45,9 +45,10 @@ class SubjectRepository @Inject constructor(
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
subjectDao.deleteAll(old uniqueSubtract new) subjectDao.removeOldAndSaveNew(
subjectDao.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
} }
) )

View File

@ -45,9 +45,10 @@ class TeacherRepository @Inject constructor(
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
teacherDb.deleteAll(old uniqueSubtract new) teacherDb.removeOldAndSaveNew(
teacherDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
} }
) )

View File

@ -154,8 +154,10 @@ class TimetableRepository @Inject constructor(
new.apply { if (notify) isNotified = false } new.apply { if (notify) isNotified = false }
} }
timetableDb.deleteAll(lessonsToRemove) timetableDb.removeOldAndSaveNew(
timetableDb.insertAll(lessonsToAdd) oldItems = lessonsToRemove,
newItems = lessonsToAdd,
)
schedulerHelper.cancelScheduled(lessonsToRemove, student) schedulerHelper.cancelScheduled(lessonsToRemove, student)
schedulerHelper.scheduleNotifications(lessonsToAdd, student) schedulerHelper.scheduleNotifications(lessonsToAdd, student)
@ -166,13 +168,17 @@ class TimetableRepository @Inject constructor(
new: List<TimetableAdditional> new: List<TimetableAdditional>
) { ) {
val oldFiltered = old.filter { !it.isAddedByUser } val oldFiltered = old.filter { !it.isAddedByUser }
timetableAdditionalDb.deleteAll(oldFiltered uniqueSubtract new) timetableAdditionalDb.removeOldAndSaveNew(
timetableAdditionalDb.insertAll(new uniqueSubtract old) oldItems = oldFiltered uniqueSubtract new,
newItems = new uniqueSubtract old,
)
} }
private suspend fun refreshDayHeaders(old: List<TimetableHeader>, new: List<TimetableHeader>) { private suspend fun refreshDayHeaders(old: List<TimetableHeader>, new: List<TimetableHeader>) {
timetableHeaderDb.deleteAll(old uniqueSubtract new) timetableHeaderDb.removeOldAndSaveNew(
timetableHeaderDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
} }
fun getLastRefreshTimestamp(semester: Semester, start: LocalDate, end: LocalDate): Instant { fun getLastRefreshTimestamp(semester: Semester, start: LocalDate, end: LocalDate): Instant {

View File

@ -10,11 +10,17 @@ import io.github.wulkanowy.getSemesterEntity
import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.getStudentEntity
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.mockk.* import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK import io.mockk.impl.annotations.SpyK
import io.mockk.just
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -61,26 +67,36 @@ class AttendanceRepositoryTest {
} }
@Test @Test
fun `force refresh without difference`() { fun `force refresh without difference`() = runTest {
// prepare // prepare
coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
flowOf(remoteList.mapToEntities(semester, emptyList())), flowOf(remoteList.mapToEntities(semester, emptyList())),
flowOf(remoteList.mapToEntities(semester, emptyList())) flowOf(remoteList.mapToEntities(semester, emptyList()))
) )
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { attendanceDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } val res = attendanceRepository.getAttendance(
student = student,
semester = semester,
start = startDate,
end = endDate,
forceRefresh = true,
).toFirstResult()
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
assertEquals(2, res.dataOrNull?.size) assertEquals(2, res.dataOrNull?.size)
coVerify { sdk.getAttendance(startDate, endDate) } coVerify { sdk.getAttendance(startDate, endDate) }
coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) }
coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } coVerify {
coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) } attendanceDb.removeOldAndSaveNew(
oldItems = match { it.isEmpty() },
newItems = match { it.isEmpty() },
)
}
} }
@Test @Test
@ -89,14 +105,23 @@ class AttendanceRepositoryTest {
coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())),
flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), // after fetch end before save result flowOf(
remoteList.dropLast(1).mapToEntities(semester, emptyList())
), // after fetch end before save result
flowOf(remoteList.mapToEntities(semester, emptyList())) flowOf(remoteList.mapToEntities(semester, emptyList()))
) )
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { attendanceDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } val res = runBlocking {
attendanceRepository.getAttendance(
student,
semester,
startDate,
endDate,
true
).toFirstResult()
}
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
@ -104,11 +129,13 @@ class AttendanceRepositoryTest {
coVerify { sdk.getAttendance(startDate, endDate) } coVerify { sdk.getAttendance(startDate, endDate) }
coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) }
coVerify { coVerify {
attendanceDb.insertAll(match { attendanceDb.removeOldAndSaveNew(
it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1] oldItems = match { it.isEmpty() },
}) newItems = match {
it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1]
},
)
} }
coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) }
} }
@Test @Test
@ -117,25 +144,39 @@ class AttendanceRepositoryTest {
coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList.dropLast(1) coEvery { sdk.getAttendance(startDate, endDate) } returns remoteList.dropLast(1)
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
flowOf(remoteList.mapToEntities(semester, emptyList())), flowOf(remoteList.mapToEntities(semester, emptyList())),
flowOf(remoteList.mapToEntities(semester, emptyList())), // after fetch end before save result flowOf(
remoteList.mapToEntities(
semester,
emptyList()
)
), // after fetch end before save result
flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())) flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList()))
) )
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { attendanceDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { attendanceRepository.getAttendance(student, semester, startDate, endDate, true).toFirstResult() } val res = runBlocking {
attendanceRepository.getAttendance(
student,
semester,
startDate,
endDate,
true
).toFirstResult()
}
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
assertEquals(1, res.dataOrNull?.size) assertEquals(1, res.dataOrNull?.size)
coVerify { sdk.getAttendance(startDate, endDate) } coVerify { sdk.getAttendance(startDate, endDate) }
coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) }
coVerify { attendanceDb.insertAll(match { it.isEmpty() }) }
coVerify { coVerify {
attendanceDb.deleteAll(match { attendanceDb.removeOldAndSaveNew(
it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1] oldItems = match {
}) it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1]
},
newItems = emptyList(),
)
} }
} }

View File

@ -9,11 +9,16 @@ import io.github.wulkanowy.getSemesterEntity
import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.getStudentEntity
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.mockk.* import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK import io.mockk.impl.annotations.SpyK
import io.mockk.just
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -52,46 +57,28 @@ class CompletedLessonsRepositoryTest {
MockKAnnotations.init(this) MockKAnnotations.init(this)
every { refreshHelper.shouldBeRefreshed(any()) } returns false every { refreshHelper.shouldBeRefreshed(any()) } returns false
completedLessonRepository = CompletedLessonsRepository(completedLessonDb, sdk, refreshHelper) completedLessonRepository =
CompletedLessonsRepository(completedLessonDb, sdk, refreshHelper)
} }
@Test @Test
fun `force refresh without difference`() { fun `force refresh without difference`() = runTest {
// prepare // prepare
coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList
coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester)),
flowOf(remoteList.mapToEntities(semester)) flowOf(remoteList.mapToEntities(semester))
) )
coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { completedLessonDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { completedLessonDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } val res = completedLessonRepository.getCompletedLessons(
student = student,
// verify semester = semester,
assertEquals(null, res.errorOrNull) start = startDate,
assertEquals(2, res.dataOrNull?.size) end = endDate,
coVerify { sdk.getCompletedLessons(startDate, endDate) } forceRefresh = true,
coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } ).toFirstResult()
coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) }
coVerify { completedLessonDb.deleteAll(match { it.isEmpty() }) }
}
@Test
fun `force refresh with more items in remote`() {
// prepare
coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList
coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
flowOf(remoteList.dropLast(1).mapToEntities(semester)),
flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result
flowOf(remoteList.mapToEntities(semester))
)
coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { completedLessonDb.deleteAll(any()) } just Runs
// execute
val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() }
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
@ -99,15 +86,52 @@ class CompletedLessonsRepositoryTest {
coVerify { sdk.getCompletedLessons(startDate, endDate) } coVerify { sdk.getCompletedLessons(startDate, endDate) }
coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) }
coVerify { coVerify {
completedLessonDb.insertAll(match { completedLessonDb.removeOldAndSaveNew(
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] oldItems = match { it.isEmpty() },
}) newItems = match { it.isEmpty() },
)
} }
coVerify { completedLessonDb.deleteAll(match { it.isEmpty() }) }
} }
@Test @Test
fun `force refresh with more items in local`() { fun `force refresh with more items in remote`() = runTest {
// prepare
coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList
coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
flowOf(remoteList.dropLast(1).mapToEntities(semester)),
flowOf(
remoteList.dropLast(1).mapToEntities(semester)
), // after fetch end before save result
flowOf(remoteList.mapToEntities(semester))
)
coEvery { completedLessonDb.removeOldAndSaveNew(any(), any()) } just Runs
// execute
val res = completedLessonRepository.getCompletedLessons(
student = student,
semester = semester,
start = startDate,
end = endDate,
forceRefresh = true
).toFirstResult()
// verify
assertEquals(null, res.errorOrNull)
assertEquals(2, res.dataOrNull?.size)
coVerify { sdk.getCompletedLessons(startDate, endDate) }
coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) }
coVerify {
completedLessonDb.removeOldAndSaveNew(
oldItems = match { it.isEmpty() },
newItems = match {
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1]
}
)
}
}
@Test
fun `force refresh with more items in local`() = runTest {
// prepare // prepare
coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList.dropLast(1) coEvery { sdk.getCompletedLessons(startDate, endDate) } returns remoteList.dropLast(1)
coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( coEvery { completedLessonDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
@ -115,22 +139,29 @@ class CompletedLessonsRepositoryTest {
flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result
flowOf(remoteList.dropLast(1).mapToEntities(semester)) flowOf(remoteList.dropLast(1).mapToEntities(semester))
) )
coEvery { completedLessonDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { completedLessonDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { completedLessonDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { completedLessonRepository.getCompletedLessons(student, semester, startDate, endDate, true).toFirstResult() } val res = completedLessonRepository.getCompletedLessons(
student = student,
semester = semester,
start = startDate,
end = endDate,
forceRefresh = true,
).toFirstResult()
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
assertEquals(1, res.dataOrNull?.size) assertEquals(1, res.dataOrNull?.size)
coVerify { sdk.getCompletedLessons(startDate, endDate) } coVerify { sdk.getCompletedLessons(startDate, endDate) }
coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) } coVerify { completedLessonDb.loadAll(1, 1, startDate, endDate) }
coVerify { completedLessonDb.insertAll(match { it.isEmpty() }) }
coVerify { coVerify {
completedLessonDb.deleteAll(match { completedLessonDb.removeOldAndSaveNew(
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] oldItems = match {
}) it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1]
},
newItems = match { it.isEmpty() },
)
} }
} }

View File

@ -9,11 +9,17 @@ import io.github.wulkanowy.getSemesterEntity
import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.getStudentEntity
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.mockk.* import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK import io.mockk.impl.annotations.SpyK
import io.mockk.just
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -64,35 +70,42 @@ class ExamRemoteTest {
flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester)),
flowOf(remoteList.mapToEntities(semester)) flowOf(remoteList.mapToEntities(semester))
) )
coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { examDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { examDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } val res = runBlocking {
examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult()
}
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
assertEquals(2, res.dataOrNull?.size) assertEquals(2, res.dataOrNull?.size)
coVerify { sdk.getExams(startDate, realEndDate) } coVerify { sdk.getExams(startDate, realEndDate) }
coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) }
coVerify { examDb.insertAll(match { it.isEmpty() }) } coVerify { examDb.removeOldAndSaveNew(emptyList(), emptyList()) }
coVerify { examDb.deleteAll(match { it.isEmpty() }) }
} }
@Test @Test
fun `force refresh with more items in remote`() { fun `force refresh with more items in remote`() = runTest {
// prepare // prepare
coEvery { sdk.getExams(startDate, realEndDate) } returns remoteList coEvery { sdk.getExams(startDate, realEndDate) } returns remoteList
coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf( coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf(
flowOf(remoteList.dropLast(1).mapToEntities(semester)), flowOf(remoteList.dropLast(1).mapToEntities(semester)),
flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result flowOf(
remoteList.dropLast(1).mapToEntities(semester)
), // after fetch end before save result
flowOf(remoteList.mapToEntities(semester)) flowOf(remoteList.mapToEntities(semester))
) )
coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { examDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { examDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } val res = examRepository.getExams(
student = student,
semester = semester,
start = startDate,
end = endDate,
forceRefresh = true,
).toFirstResult()
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
@ -100,15 +113,17 @@ class ExamRemoteTest {
coVerify { sdk.getExams(startDate, realEndDate) } coVerify { sdk.getExams(startDate, realEndDate) }
coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) }
coVerify { coVerify {
examDb.insertAll(match { examDb.removeOldAndSaveNew(
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] oldItems = emptyList(),
}) newItems = match {
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1]
},
)
} }
coVerify { examDb.deleteAll(match { it.isEmpty() }) }
} }
@Test @Test
fun `force refresh with more items in local`() { fun `force refresh with more items in local`() = runTest {
// prepare // prepare
coEvery { sdk.getExams(startDate, realEndDate) } returns remoteList.dropLast(1) coEvery { sdk.getExams(startDate, realEndDate) } returns remoteList.dropLast(1)
coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf( coEvery { examDb.loadAll(1, 1, startDate, realEndDate) } returnsMany listOf(
@ -116,22 +131,27 @@ class ExamRemoteTest {
flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result
flowOf(remoteList.dropLast(1).mapToEntities(semester)) flowOf(remoteList.dropLast(1).mapToEntities(semester))
) )
coEvery { examDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { examDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { examDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { examRepository.getExams(student, semester, startDate, endDate, true).toFirstResult() } val res = examRepository.getExams(
student = student,
semester = semester,
start = startDate,
end = endDate,
forceRefresh = true,
).toFirstResult()
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
assertEquals(1, res.dataOrNull?.size) assertEquals(1, res.dataOrNull?.size)
coVerify { sdk.getExams(startDate, realEndDate) } coVerify { sdk.getExams(startDate, realEndDate) }
coVerify { examDb.loadAll(1, 1, startDate, realEndDate) } coVerify { examDb.loadAll(1, 1, startDate, realEndDate) }
coVerify { examDb.insertAll(match { it.isEmpty() }) }
coVerify { coVerify {
examDb.deleteAll(match { examDb.removeOldAndSaveNew(
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] oldItems = match { it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] },
}) newItems = emptyList()
)
} }
} }

View File

@ -22,6 +22,7 @@ import io.mockk.impl.annotations.SpyK
import io.mockk.just import io.mockk.just
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
@ -60,26 +61,27 @@ class GradeRepositoryTest {
MockKAnnotations.init(this) MockKAnnotations.init(this)
every { refreshHelper.shouldBeRefreshed(any()) } returns false every { refreshHelper.shouldBeRefreshed(any()) } returns false
gradeRepository = gradeRepository = GradeRepository(
GradeRepository(gradeDb, gradeSummaryDb, gradeDescriptiveDb, sdk, refreshHelper) gradeDb = gradeDb,
gradeSummaryDb = gradeSummaryDb,
gradeDescriptiveDb = gradeDescriptiveDb,
sdk = sdk,
refreshHelper = refreshHelper,
)
coEvery { gradeDb.deleteAll(any()) } just Runs coEvery { gradeDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { gradeDb.insertAll(any()) } returns listOf()
coEvery { gradeSummaryDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { gradeSummaryDb.loadAll(1, 1) } returnsMany listOf( coEvery { gradeSummaryDb.loadAll(1, 1) } returnsMany listOf(
flowOf(listOf()), flowOf(listOf()),
flowOf(listOf()), flowOf(listOf()),
flowOf(listOf()) flowOf(listOf())
) )
coEvery { gradeSummaryDb.deleteAll(any()) } just Runs
coEvery { gradeSummaryDb.insertAll(any()) } returns listOf()
coEvery { gradeDescriptiveDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { gradeDescriptiveDb.loadAll(any(), any()) } returnsMany listOf( coEvery { gradeDescriptiveDb.loadAll(any(), any()) } returnsMany listOf(
flowOf(listOf()), flowOf(listOf()),
) )
coEvery { gradeDescriptiveDb.deleteAll(any()) } just Runs
coEvery { gradeDescriptiveDb.insertAll(any()) } returns listOf()
} }
@Test @Test
@ -113,13 +115,16 @@ class GradeRepositoryTest {
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
assertEquals(4, res.dataOrNull?.first?.size) assertEquals(4, res.dataOrNull?.first?.size)
coVerify { coVerify {
gradeDb.insertAll(withArg { gradeDb.removeOldAndSaveNew(
assertEquals(4, it.size) oldItems = emptyList(),
assertTrue(it[0].isRead) newItems = withArg {
assertTrue(it[1].isRead) assertEquals(4, it.size)
assertFalse(it[2].isRead) assertTrue(it[0].isRead)
assertFalse(it[3].isRead) assertTrue(it[1].isRead)
}) assertFalse(it[2].isRead)
assertFalse(it[3].isRead)
},
)
} }
} }
@ -167,23 +172,23 @@ class GradeRepositoryTest {
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
assertEquals(4, res.dataOrNull?.first?.size) assertEquals(4, res.dataOrNull?.first?.size)
coVerify { coVerify {
gradeDb.insertAll(withArg { gradeDb.removeOldAndSaveNew(
assertEquals(3, it.size) oldItems = withArg {
assertTrue(it[0].isRead) assertEquals(2, it.size)
assertTrue(it[1].isRead) },
assertFalse(it[2].isRead) newItems = withArg {
assertEquals(remoteList.mapToEntities(semester).last(), it[2]) assertEquals(3, it.size)
}) assertTrue(it[0].isRead)
} assertTrue(it[1].isRead)
coVerify { assertFalse(it[2].isRead)
gradeDb.deleteAll(withArg { assertEquals(remoteList.mapToEntities(semester).last(), it[2])
assertEquals(2, it.size) }
}) )
} }
} }
@Test @Test
fun `force refresh when local contains duplicated grades`() { fun `force refresh when local contains duplicated grades`() = runTest {
// prepare // prepare
val remoteList = listOf( val remoteList = listOf(
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"), createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
@ -203,13 +208,17 @@ class GradeRepositoryTest {
) )
// execute // execute
val res = runBlocking { gradeRepository.getGrades(student, semester, true).toFirstResult() } val res = gradeRepository.getGrades(student, semester, true).toFirstResult()
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
assertEquals(2, res.dataOrNull?.first?.size) assertEquals(2, res.dataOrNull?.first?.size)
coVerify { gradeDb.insertAll(match { it.isEmpty() }) } coVerify {
coVerify { gradeDb.deleteAll(match { it.size == 1 }) } // ... here gradeDb.removeOldAndSaveNew(
oldItems = match { it.size == 1 }, // ... here
newItems = emptyList()
)
}
} }
@Test @Test
@ -238,8 +247,12 @@ class GradeRepositoryTest {
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
assertEquals(3, res.dataOrNull?.first?.size) assertEquals(3, res.dataOrNull?.first?.size)
coVerify { gradeDb.insertAll(match { it.size == 1 }) } // ... here coVerify {
coVerify { gradeDb.deleteAll(match { it.isEmpty() }) } gradeDb.removeOldAndSaveNew(
oldItems = emptyList(),
newItems = match { it.size == 1 }, // ... here
)
}
} }
@Test @Test

View File

@ -71,8 +71,7 @@ class GradeStatisticsRepositoryTest {
flowOf(remotePartialList.mapToEntities(semester)), flowOf(remotePartialList.mapToEntities(semester)),
flowOf(remotePartialList.mapToEntities(semester)) flowOf(remotePartialList.mapToEntities(semester))
) )
coEvery { gradePartialStatisticsDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { gradePartialStatisticsDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { gradePartialStatisticsDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { val res = runBlocking {
@ -93,8 +92,7 @@ class GradeStatisticsRepositoryTest {
assertEquals("", items[2].partial?.studentAverage) assertEquals("", items[2].partial?.studentAverage)
coVerify { sdk.getGradesPartialStatistics(1) } coVerify { sdk.getGradesPartialStatistics(1) }
coVerify { gradePartialStatisticsDb.loadAll(1, 1) } coVerify { gradePartialStatisticsDb.loadAll(1, 1) }
coVerify { gradePartialStatisticsDb.insertAll(match { it.isEmpty() }) } coVerify { gradePartialStatisticsDb.removeOldAndSaveNew(emptyList(), emptyList()) }
coVerify { gradePartialStatisticsDb.deleteAll(match { it.isEmpty() }) }
} }
@Test @Test
@ -109,8 +107,7 @@ class GradeStatisticsRepositoryTest {
flowOf(remotePartialList.mapToEntities(semester)), flowOf(remotePartialList.mapToEntities(semester)),
flowOf(remotePartialList.mapToEntities(semester)) flowOf(remotePartialList.mapToEntities(semester))
) )
coEvery { gradePartialStatisticsDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { gradePartialStatisticsDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { gradePartialStatisticsDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { val res = runBlocking {
@ -131,8 +128,7 @@ class GradeStatisticsRepositoryTest {
assertEquals("5.0", items[2].partial?.studentAverage) assertEquals("5.0", items[2].partial?.studentAverage)
coVerify { sdk.getGradesPartialStatistics(1) } coVerify { sdk.getGradesPartialStatistics(1) }
coVerify { gradePartialStatisticsDb.loadAll(1, 1) } coVerify { gradePartialStatisticsDb.loadAll(1, 1) }
coVerify { gradePartialStatisticsDb.insertAll(match { it.isEmpty() }) } coVerify { gradePartialStatisticsDb.removeOldAndSaveNew(emptyList(), emptyList()) }
coVerify { gradePartialStatisticsDb.deleteAll(match { it.isEmpty() }) }
} }
private fun getGradeStatisticsPartialSubject( private fun getGradeStatisticsPartialSubject(

View File

@ -7,11 +7,16 @@ import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.getStudentEntity import io.github.wulkanowy.getStudentEntity
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.mockk.* import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK import io.mockk.impl.annotations.SpyK
import io.mockk.just
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -53,7 +58,8 @@ class LuckyNumberRemoteTest {
coEvery { luckyNumberDb.deleteAll(any()) } just Runs coEvery { luckyNumberDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } val res =
runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() }
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
@ -65,19 +71,19 @@ class LuckyNumberRemoteTest {
} }
@Test @Test
fun `force refresh with different item on remote`() { fun `force refresh with different item on remote`() = runTest {
// prepare // prepare
coEvery { sdk.getLuckyNumber(student.schoolShortName) } returns luckyNumber coEvery { sdk.getLuckyNumber(student.schoolShortName) } returns luckyNumber
coEvery { luckyNumberDb.load(1, date) } returnsMany listOf( coEvery { luckyNumberDb.load(1, date) } returnsMany listOf(
flowOf(luckyNumber.mapToEntity(student).copy(luckyNumber = 6666)), flowOf(luckyNumber.mapToEntity(student).copy(luckyNumber = 6666)),
flowOf(luckyNumber.mapToEntity(student).copy(luckyNumber = 6666)), // after fetch end before save result // after fetch end before save result
flowOf(luckyNumber.mapToEntity(student).copy(luckyNumber = 6666)),
flowOf(luckyNumber.mapToEntity(student)) flowOf(luckyNumber.mapToEntity(student))
) )
coEvery { luckyNumberDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { luckyNumberDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { luckyNumberDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } val res = luckyNumberRepository.getLuckyNumber(student, true).toFirstResult()
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
@ -85,13 +91,16 @@ class LuckyNumberRemoteTest {
coVerify { sdk.getLuckyNumber(student.schoolShortName) } coVerify { sdk.getLuckyNumber(student.schoolShortName) }
coVerify { luckyNumberDb.load(1, date) } coVerify { luckyNumberDb.load(1, date) }
coVerify { coVerify {
luckyNumberDb.insertAll(match { luckyNumberDb.removeOldAndSaveNew(
it.size == 1 && it[0] == luckyNumber.mapToEntity(student) oldItems = match {
}) it.size == 1 && it[0] == luckyNumber.mapToEntity(student)
.copy(luckyNumber = 6666)
},
newItems = match {
it.size == 1 && it[0] == luckyNumber.mapToEntity(student)
}
)
} }
coVerify { luckyNumberDb.deleteAll(match {
it.size == 1 && it[0] == luckyNumber.mapToEntity(student).copy(luckyNumber = 6666)
}) }
} }
@Test @Test
@ -103,11 +112,11 @@ class LuckyNumberRemoteTest {
flowOf(null), // after fetch end before save result flowOf(null), // after fetch end before save result
flowOf(luckyNumber.mapToEntity(student)) flowOf(luckyNumber.mapToEntity(student))
) )
coEvery { luckyNumberDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { luckyNumberDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { luckyNumberDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() } val res =
runBlocking { luckyNumberRepository.getLuckyNumber(student, true).toFirstResult() }
// verify // verify
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
@ -115,10 +124,12 @@ class LuckyNumberRemoteTest {
coVerify { sdk.getLuckyNumber(student.schoolShortName) } coVerify { sdk.getLuckyNumber(student.schoolShortName) }
coVerify { luckyNumberDb.load(1, date) } coVerify { luckyNumberDb.load(1, date) }
coVerify { coVerify {
luckyNumberDb.insertAll(match { luckyNumberDb.removeOldAndSaveNew(
it.size == 1 && it[0] == luckyNumber.mapToEntity(student) oldItems = emptyList(),
}) newItems = match {
it.size == 1 && it[0] == luckyNumber.mapToEntity(student)
}
)
} }
coVerify(exactly = 0) { luckyNumberDb.deleteAll(any()) }
} }
} }

View File

@ -113,7 +113,7 @@ class MessageRepositoryTest {
} }
@Test @Test
fun `get messages when fetched completely new message without notify`() = runBlocking { fun `get messages when fetched completely new message without notify`() = runTest {
coEvery { mailboxDao.loadAll(any()) } returns listOf(mailbox) coEvery { mailboxDao.loadAll(any()) } returns listOf(mailbox)
every { messageDb.loadAll(mailbox.globalKey, any()) } returns flowOf(emptyList()) every { messageDb.loadAll(mailbox.globalKey, any()) } returns flowOf(emptyList())
coEvery { sdk.getMessages(Folder.RECEIVED, any()) } returns listOf( coEvery { sdk.getMessages(Folder.RECEIVED, any()) } returns listOf(
@ -122,8 +122,7 @@ class MessageRepositoryTest {
readBy = 10, readBy = 10,
) )
) )
coEvery { messageDb.deleteAll(any()) } just Runs coEvery { messageDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { messageDb.insertAll(any()) } returns listOf()
val res = repository.getMessages( val res = repository.getMessages(
student = student, student = student,
@ -134,12 +133,14 @@ class MessageRepositoryTest {
).toFirstResult() ).toFirstResult()
assertEquals(null, res.errorOrNull) assertEquals(null, res.errorOrNull)
coVerify(exactly = 1) { messageDb.deleteAll(withArg { checkEquals(emptyList<Message>()) }) }
coVerify { coVerify {
messageDb.insertAll(withArg { messageDb.removeOldAndSaveNew(
assertEquals(4, it.single().messageId) oldItems = withArg { checkEquals(emptyList<Message>()) },
assertTrue(it.single().isNotified) newItems = withArg {
}) assertEquals(4, it.single().messageId)
assertTrue(it.single().isNotified)
},
)
} }
} }

View File

@ -19,7 +19,7 @@ import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK import io.mockk.impl.annotations.SpyK
import io.mockk.just import io.mockk.just
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -57,42 +57,21 @@ class MobileDeviceRepositoryTest {
} }
@Test @Test
fun `force refresh without difference`() { fun `force refresh without difference`() = runTest {
// prepare // prepare
coEvery { sdk.getRegisteredDevices() } returns remoteList coEvery { sdk.getRegisteredDevices() } returns remoteList
coEvery { mobileDeviceDb.loadAll(student.studentId) } returnsMany listOf( coEvery { mobileDeviceDb.loadAll(student.studentId) } returnsMany listOf(
flowOf(remoteList.mapToEntities(student)), flowOf(remoteList.mapToEntities(student)),
flowOf(remoteList.mapToEntities(student)) flowOf(remoteList.mapToEntities(student))
) )
coEvery { mobileDeviceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { mobileDeviceDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { mobileDeviceDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } val res = mobileDeviceRepository.getDevices(
student = student,
// verify semester = semester,
Assert.assertEquals(null, res.errorOrNull) forceRefresh = true,
Assert.assertEquals(2, res.dataOrNull?.size) ).toFirstResult()
coVerify { sdk.getRegisteredDevices() }
coVerify { mobileDeviceDb.loadAll(1) }
coVerify { mobileDeviceDb.insertAll(match { it.isEmpty() }) }
coVerify { mobileDeviceDb.deleteAll(match { it.isEmpty() }) }
}
@Test
fun `force refresh with more items in remote`() {
// prepare
coEvery { sdk.getRegisteredDevices() } returns remoteList
coEvery { mobileDeviceDb.loadAll(1) } returnsMany listOf(
flowOf(remoteList.dropLast(1).mapToEntities(student)),
flowOf(remoteList.dropLast(1).mapToEntities(student)), // after fetch end before save result
flowOf(remoteList.mapToEntities(student))
)
coEvery { mobileDeviceDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { mobileDeviceDb.deleteAll(any()) } just Runs
// execute
val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() }
// verify // verify
Assert.assertEquals(null, res.errorOrNull) Assert.assertEquals(null, res.errorOrNull)
@ -100,15 +79,50 @@ class MobileDeviceRepositoryTest {
coVerify { sdk.getRegisteredDevices() } coVerify { sdk.getRegisteredDevices() }
coVerify { mobileDeviceDb.loadAll(1) } coVerify { mobileDeviceDb.loadAll(1) }
coVerify { coVerify {
mobileDeviceDb.insertAll(match { mobileDeviceDb.removeOldAndSaveNew(
it.size == 1 && it[0] == remoteList.mapToEntities(student)[1] oldItems = match { it.isEmpty() },
}) newItems = match { it.isEmpty() },
)
} }
coVerify { mobileDeviceDb.deleteAll(match { it.isEmpty() }) }
} }
@Test @Test
fun `force refresh with more items in local`() { fun `force refresh with more items in remote`() = runTest {
// prepare
coEvery { sdk.getRegisteredDevices() } returns remoteList
coEvery { mobileDeviceDb.loadAll(1) } returnsMany listOf(
flowOf(remoteList.dropLast(1).mapToEntities(student)),
flowOf(
remoteList.dropLast(1).mapToEntities(student)
), // after fetch end before save result
flowOf(remoteList.mapToEntities(student))
)
coEvery { mobileDeviceDb.removeOldAndSaveNew(any(), any()) } just Runs
// execute
val res = mobileDeviceRepository.getDevices(
student = student,
semester = semester,
forceRefresh = true,
).toFirstResult()
// verify
Assert.assertEquals(null, res.errorOrNull)
Assert.assertEquals(2, res.dataOrNull?.size)
coVerify { sdk.getRegisteredDevices() }
coVerify { mobileDeviceDb.loadAll(1) }
coVerify {
mobileDeviceDb.removeOldAndSaveNew(
oldItems = match { it.isEmpty() },
newItems = match {
it.size == 1 && it[0] == remoteList.mapToEntities(student)[1]
},
)
}
}
@Test
fun `force refresh with more items in local`() = runTest {
// prepare // prepare
coEvery { sdk.getRegisteredDevices() } returns remoteList.dropLast(1) coEvery { sdk.getRegisteredDevices() } returns remoteList.dropLast(1)
coEvery { mobileDeviceDb.loadAll(1) } returnsMany listOf( coEvery { mobileDeviceDb.loadAll(1) } returnsMany listOf(
@ -116,22 +130,27 @@ class MobileDeviceRepositoryTest {
flowOf(remoteList.mapToEntities(student)), // after fetch end before save result flowOf(remoteList.mapToEntities(student)), // after fetch end before save result
flowOf(remoteList.dropLast(1).mapToEntities(student)) flowOf(remoteList.dropLast(1).mapToEntities(student))
) )
coEvery { mobileDeviceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { mobileDeviceDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { mobileDeviceDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { mobileDeviceRepository.getDevices(student, semester, true).toFirstResult() } val res = mobileDeviceRepository.getDevices(
student = student,
semester = semester,
forceRefresh = true,
).toFirstResult()
// verify // verify
Assert.assertEquals(null, res.errorOrNull) Assert.assertEquals(null, res.errorOrNull)
Assert.assertEquals(1, res.dataOrNull?.size) Assert.assertEquals(1, res.dataOrNull?.size)
coVerify { sdk.getRegisteredDevices() } coVerify { sdk.getRegisteredDevices() }
coVerify { mobileDeviceDb.loadAll(1) } coVerify { mobileDeviceDb.loadAll(1) }
coVerify { mobileDeviceDb.insertAll(match { it.isEmpty() }) }
coVerify { coVerify {
mobileDeviceDb.deleteAll(match { mobileDeviceDb.removeOldAndSaveNew(
it.size == 1 && it[0] == remoteList.mapToEntities(student)[1] oldItems = match {
}) it.size == 1 && it[0] == remoteList.mapToEntities(student)[1]
},
newItems = match { it.isEmpty() },
)
} }
} }

View File

@ -69,7 +69,12 @@ class RecipientLocalTest {
@Test @Test
fun `load recipients when items already in database`() { fun `load recipients when items already in database`() {
// prepare // prepare
coEvery { recipientDb.loadAll(io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN, "v4") } returnsMany listOf( coEvery {
recipientDb.loadAll(
io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN,
"v4"
)
} returnsMany listOf(
remoteList.mapToEntities("v4"), remoteList.mapToEntities("v4"),
remoteList.mapToEntities("v4") remoteList.mapToEntities("v4")
) )
@ -108,8 +113,7 @@ class RecipientLocalTest {
emptyList(), emptyList(),
remoteList.mapToEntities("v4") remoteList.mapToEntities("v4")
) )
coEvery { recipientDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { recipientDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { recipientDb.deleteAll(any()) } just Runs
// execute // execute
val res = runBlocking { val res = runBlocking {
@ -123,8 +127,12 @@ class RecipientLocalTest {
// verify // verify
assertEquals(3, res.size) assertEquals(3, res.size)
coVerify { sdk.getRecipients("v4") } coVerify { sdk.getRecipients("v4") }
coVerify { recipientDb.loadAll(io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN, "v4") } coVerify {
coVerify { recipientDb.insertAll(match { it.isEmpty() }) } recipientDb.loadAll(
coVerify { recipientDb.deleteAll(match { it.isEmpty() }) } io.github.wulkanowy.data.db.entities.MailboxType.UNKNOWN,
"v4"
)
}
coVerify { recipientDb.removeOldAndSaveNew(match { it.isEmpty() }, match { it.isEmpty() }) }
} }
} }

View File

@ -50,13 +50,16 @@ class SemesterRepositoryTest {
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList()
coEvery { sdk.getSemesters() } returns semesters coEvery { sdk.getSemesters() } returns semesters
coEvery { semesterDb.deleteAll(any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { semesterDb.insertSemesters(any()) } returns emptyList()
runBlocking { semesterRepository.getSemesters(student) } runBlocking { semesterRepository.getSemesters(student) }
coVerify { semesterDb.insertSemesters(semesters.mapToEntities(student.studentId)) } coVerify {
coVerify { semesterDb.deleteAll(emptyList()) } semesterDb.removeOldAndSaveNew(
oldItems = emptyList(),
newItems = semesters.mapToEntities(student.studentId),
)
}
} }
@Test @Test
@ -71,12 +74,17 @@ class SemesterRepositoryTest {
getSemesterPojo(123, 2, now().minusMonths(3), now()) getSemesterPojo(123, 2, now().minusMonths(3), now())
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns badSemesters.mapToEntities(student.studentId) coEvery {
semesterDb.loadAll(
student.studentId,
student.classId
)
} returns badSemesters.mapToEntities(student.studentId)
coEvery { sdk.getSemesters() } returns goodSemesters coEvery { sdk.getSemesters() } returns goodSemesters
coEvery { semesterDb.deleteAll(any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { semesterDb.insertSemesters(any()) } returns listOf()
val items = runBlocking { semesterRepository.getSemesters(student.copy(loginMode = Sdk.Mode.HEBE.name)) } val items =
runBlocking { semesterRepository.getSemesters(student.copy(loginMode = Sdk.Mode.HEBE.name)) }
assertEquals(2, items.size) assertEquals(2, items.size)
assertEquals(0, items[0].diaryId) assertEquals(0, items[0].diaryId)
} }
@ -99,8 +107,7 @@ class SemesterRepositoryTest {
goodSemesters.mapToEntities(student.studentId) goodSemesters.mapToEntities(student.studentId)
) )
coEvery { sdk.getSemesters() } returns goodSemesters coEvery { sdk.getSemesters() } returns goodSemesters
coEvery { semesterDb.deleteAll(any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { semesterDb.insertSemesters(any()) } returns listOf()
val items = semesterRepository.getSemesters( val items = semesterRepository.getSemesters(
student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name) student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name)
@ -157,13 +164,16 @@ class SemesterRepositoryTest {
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList() coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns emptyList()
coEvery { sdk.getSemesters() } returns semesters coEvery { sdk.getSemesters() } returns semesters
coEvery { semesterDb.deleteAll(any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { semesterDb.insertSemesters(any()) } returns listOf()
runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) }
coVerify { semesterDb.deleteAll(emptyList()) } coVerify {
coVerify { semesterDb.insertSemesters(semesters.mapToEntities(student.studentId)) } semesterDb.removeOldAndSaveNew(
oldItems = emptyList(),
newItems = semesters.mapToEntities(student.studentId),
)
}
} }
@Test @Test
@ -181,12 +191,17 @@ class SemesterRepositoryTest {
getSemesterPojo(2, 2, now().plusMonths(5), now().plusMonths(11)), getSemesterPojo(2, 2, now().plusMonths(5), now().plusMonths(11)),
) )
coEvery { semesterDb.loadAll(student.studentId, student.classId) } returns semestersWithNoCurrent coEvery {
semesterDb.loadAll(
student.studentId,
student.classId
)
} returns semestersWithNoCurrent
coEvery { sdk.getSemesters() } returns newSemesters coEvery { sdk.getSemesters() } returns newSemesters
coEvery { semesterDb.deleteAll(any()) } just Runs coEvery { semesterDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { semesterDb.insertSemesters(any()) } returns listOf()
val items = runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) } val items =
runBlocking { semesterRepository.getSemesters(student, refreshOnNoCurrent = true) }
assertEquals(2, items.size) assertEquals(2, items.size)
} }

View File

@ -108,8 +108,7 @@ class TimetableRepositoryTest {
flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester)),
flowOf(remoteList.mapToEntities(semester)) flowOf(remoteList.mapToEntities(semester))
) )
coEvery { timetableDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { timetableDb.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { timetableDb.deleteAll(any()) } just Runs
coEvery { coEvery {
timetableAdditionalDao.loadAll( timetableAdditionalDao.loadAll(
@ -119,12 +118,10 @@ class TimetableRepositoryTest {
end = endDate end = endDate
) )
} returns flowOf(listOf()) } returns flowOf(listOf())
coEvery { timetableAdditionalDao.deleteAll(emptyList()) } just Runs coEvery { timetableAdditionalDao.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { timetableAdditionalDao.insertAll(emptyList()) } returns listOf(1, 2, 3)
coEvery { timetableHeaderDao.loadAll(1, 1, startDate, endDate) } returns flowOf(listOf()) coEvery { timetableHeaderDao.loadAll(1, 1, startDate, endDate) } returns flowOf(listOf())
coEvery { timetableHeaderDao.insertAll(emptyList()) } returns listOf(1, 2, 3) coEvery { timetableHeaderDao.removeOldAndSaveNew(any(), any()) } just Runs
coEvery { timetableHeaderDao.deleteAll(emptyList()) } just Runs
// execute // execute
val res = runBlocking { val res = runBlocking {
@ -142,8 +139,12 @@ class TimetableRepositoryTest {
assertEquals(2, res.dataOrNull!!.lessons.size) assertEquals(2, res.dataOrNull!!.lessons.size)
coVerify { sdk.getTimetable(startDate, endDate) } coVerify { sdk.getTimetable(startDate, endDate) }
coVerify { timetableDb.loadAll(1, 1, startDate, endDate) } coVerify { timetableDb.loadAll(1, 1, startDate, endDate) }
coVerify { timetableDb.insertAll(match { it.isEmpty() }) } coVerify {
coVerify { timetableDb.deleteAll(match { it.isEmpty() }) } timetableDb.removeOldAndSaveNew(
oldItems = match { it.isEmpty() },
newItems = match { it.isEmpty() },
)
}
} }
private fun createTimetableRemote( private fun createTimetableRemote(