Fallback to subject name from timetable when attendance item doesn't have it (#2052)

* Fallback to subject name from timetable when attendance item doesn't have it

* Fix tests

* Add some unit tests
This commit is contained in:
Mikołaj Pich 2022-11-18 16:32:04 +01:00 committed by GitHub
parent 7a408899df
commit 6c115fb915
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 180 additions and 15 deletions

View File

@ -49,8 +49,8 @@ fun <T, U> Resource<T>.mapData(block: (T) -> U) = when (this) {
fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach { fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach {
val description = when (it) { val description = when (it) {
is Resource.Loading -> "started"
is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else "" is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else ""
is Resource.Loading -> "started"
is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else "" is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else ""
is Resource.Error -> "exception occurred: ${it.error}" is Resource.Error -> "exception occurred: ${it.error}"
} }

View File

@ -13,4 +13,7 @@ interface TimetableDao : BaseDao<Timetable> {
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Timetable>> fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Timetable>>
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List<Timetable>
} }

View File

@ -3,17 +3,22 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance
import io.github.wulkanowy.sdk.pojo.AttendanceSummary as SdkAttendanceSummary import io.github.wulkanowy.sdk.pojo.AttendanceSummary as SdkAttendanceSummary
fun List<SdkAttendance>.mapToEntities(semester: Semester) = map { fun List<SdkAttendance>.mapToEntities(semester: Semester, lessons: List<Timetable>) = map {
Attendance( Attendance(
studentId = semester.studentId, studentId = semester.studentId,
diaryId = semester.diaryId, diaryId = semester.diaryId,
date = it.date, date = it.date,
timeId = it.timeId, timeId = it.timeId,
number = it.number, number = it.number,
subject = it.subject, subject = it.subject.ifBlank {
lessons.find { lesson ->
lesson.date == it.date && lesson.number == it.number
}?.subject.orEmpty()
},
name = it.name, name = it.name,
presence = it.presence, presence = it.presence,
absence = it.absence, absence = it.absence,

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
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
@ -9,8 +10,10 @@ import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent import io.github.wulkanowy.sdk.pojo.Absent
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.LocalTime import java.time.LocalTime
@ -20,6 +23,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class AttendanceRepository @Inject constructor( class AttendanceRepository @Inject constructor(
private val attendanceDb: AttendanceDao, private val attendanceDb: AttendanceDao,
private val timetableDb: TimetableDao,
private val sdk: Sdk, private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
@ -48,10 +52,15 @@ class AttendanceRepository @Inject constructor(
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
}, },
fetch = { fetch = {
val lessons = withContext(Dispatchers.IO) {
timetableDb.load(
semester.diaryId, semester.studentId, start.monday, end.sunday
)
}
sdk.init(student) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getAttendance(start.monday, end.sunday, semester.semesterId) .getAttendance(start.monday, end.sunday, semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester, lessons)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
attendanceDb.deleteAll(old uniqueSubtract new) attendanceDb.deleteAll(old uniqueSubtract new)

View File

@ -0,0 +1,143 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.sdk.pojo.Attendance
import io.github.wulkanowy.sdk.scrapper.attendance.SentExcuse
import org.junit.Test
import java.time.Instant
import java.time.LocalDate
import kotlin.test.assertEquals
class AttendanceMapperTest {
@Test
fun `map attendance when fallback is not necessary`() {
val attendance = listOf(
getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"),
getSdkAttendance(2, LocalDate.of(2022, 11, 17), "Oryginalna 2"),
)
val lessons = listOf(
getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"),
)
val result = attendance.mapToEntities(getEntitySemester(), lessons)
assertEquals("Oryginalna 1", result[0].subject)
assertEquals("Oryginalna 2", result[1].subject)
}
@Test
fun `map attendance when fallback is not always necessary`() {
val attendance = listOf(
getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"),
getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""),
)
val lessons = listOf(
getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"),
)
val result = attendance.mapToEntities(getEntitySemester(), lessons)
assertEquals("Oryginalna 1", result[0].subject)
assertEquals("Druga", result[1].subject)
}
@Test
fun `map attendance when fallback is sometimes empty`() {
val attendance = listOf(
getSdkAttendance(1, LocalDate.of(2022, 11, 17), "Oryginalna 1"),
getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""),
)
val lessons = listOf(
getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
)
val result = attendance.mapToEntities(getEntitySemester(), lessons)
assertEquals("Oryginalna 1", result[0].subject)
assertEquals("", result[1].subject)
}
@Test
fun `map attendance when fallback is empty`() {
val attendance = listOf(
getSdkAttendance(1, LocalDate.of(2022, 11, 17), ""),
getSdkAttendance(2, LocalDate.of(2022, 11, 17), ""),
)
val lessons = listOf(
getEntityTimetable(1, LocalDate.of(2022, 11, 18), "Pierwsza"),
getEntityTimetable(2, LocalDate.of(2022, 10, 17), "Druga"),
)
val result = attendance.mapToEntities(getEntitySemester(), lessons)
assertEquals("", result[0].subject)
assertEquals("", result[1].subject)
}
@Test
fun `map attendance with all subject fallback`() {
val attendance = listOf(
getSdkAttendance(1, LocalDate.of(2022, 11, 17)),
getSdkAttendance(2, LocalDate.of(2022, 11, 17)),
)
val lessons = listOf(
getEntityTimetable(1, LocalDate.of(2022, 11, 17), "Pierwsza"),
getEntityTimetable(2, LocalDate.of(2022, 11, 17), "Druga"),
)
val result = attendance.mapToEntities(getEntitySemester(), lessons)
assertEquals("Pierwsza", result[0].subject)
assertEquals("Druga", result[1].subject)
}
private fun getSdkAttendance(number: Int, date: LocalDate, subject: String = "") = Attendance(
number = number,
name = "ABSENCE",
subject = subject,
date = date,
timeId = 1,
categoryId = 1,
deleted = false,
excuseStatus = SentExcuse.Status.WAITING,
excusable = false,
absence = false,
excused = false,
exemption = false,
lateness = false,
presence = false,
)
private fun getEntityTimetable(number: Int, date: LocalDate, subject: String = "") = Timetable(
number = number,
start = Instant.now(),
end = Instant.now(),
date = date,
subject = subject,
subjectOld = "",
group = "",
room = "",
roomOld = "",
teacher = "",
teacherOld = "",
info = "",
changes = false,
canceled = false,
studentId = 0,
diaryId = 0,
isStudentPlan = false,
)
private fun getEntitySemester() = Semester(
studentId = 0,
diaryId = 0,
kindergartenDiaryId = 0,
diaryName = "",
schoolYear = 0,
semesterId = 0,
semesterName = 0,
start = LocalDate.now(),
end = LocalDate.now(),
classId = 0,
unitId = 0
)
}

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.errorOrNull import io.github.wulkanowy.data.errorOrNull
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.data.toFirstResult
@ -29,6 +30,9 @@ class AttendanceRepositoryTest {
@MockK @MockK
private lateinit var attendanceDb: AttendanceDao private lateinit var attendanceDb: AttendanceDao
@MockK
private lateinit var timetableDb: TimetableDao
@MockK(relaxUnitFun = true) @MockK(relaxUnitFun = true)
private lateinit var refreshHelper: AutoRefreshHelper private lateinit var refreshHelper: AutoRefreshHelper
@ -51,8 +55,9 @@ class AttendanceRepositoryTest {
fun setUp() { fun setUp() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
every { refreshHelper.shouldBeRefreshed(any()) } returns false every { refreshHelper.shouldBeRefreshed(any()) } returns false
coEvery { timetableDb.load(any(), any(), any(), any()) } returns emptyList()
attendanceRepository = AttendanceRepository(attendanceDb, sdk, refreshHelper) attendanceRepository = AttendanceRepository(attendanceDb, timetableDb, sdk, refreshHelper)
} }
@Test @Test
@ -60,8 +65,8 @@ class AttendanceRepositoryTest {
// prepare // prepare
coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList
coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf( coEvery { attendanceDb.loadAll(1, 1, startDate, endDate) } returnsMany listOf(
flowOf(remoteList.mapToEntities(semester)), flowOf(remoteList.mapToEntities(semester, emptyList())),
flowOf(remoteList.mapToEntities(semester)) flowOf(remoteList.mapToEntities(semester, emptyList()))
) )
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { attendanceDb.deleteAll(any()) } just Runs coEvery { attendanceDb.deleteAll(any()) } just Runs
@ -83,9 +88,9 @@ class AttendanceRepositoryTest {
// prepare // prepare
coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList coEvery { sdk.getAttendance(startDate, endDate, 1) } 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)), flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())),
flowOf(remoteList.dropLast(1).mapToEntities(semester)), // after fetch end before save result flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList())), // after fetch end before save result
flowOf(remoteList.mapToEntities(semester)) flowOf(remoteList.mapToEntities(semester, emptyList()))
) )
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { attendanceDb.deleteAll(any()) } just Runs coEvery { attendanceDb.deleteAll(any()) } just Runs
@ -100,7 +105,7 @@ class AttendanceRepositoryTest {
coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) } coVerify { attendanceDb.loadAll(1, 1, startDate, endDate) }
coVerify { coVerify {
attendanceDb.insertAll(match { attendanceDb.insertAll(match {
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1]
}) })
} }
coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) } coVerify { attendanceDb.deleteAll(match { it.isEmpty() }) }
@ -111,9 +116,9 @@ class AttendanceRepositoryTest {
// prepare // prepare
coEvery { sdk.getAttendance(startDate, endDate, 1) } returns remoteList.dropLast(1) coEvery { sdk.getAttendance(startDate, endDate, 1) } 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)), flowOf(remoteList.mapToEntities(semester, emptyList())),
flowOf(remoteList.mapToEntities(semester)), // after fetch end before save result flowOf(remoteList.mapToEntities(semester, emptyList())), // after fetch end before save result
flowOf(remoteList.dropLast(1).mapToEntities(semester)) flowOf(remoteList.dropLast(1).mapToEntities(semester, emptyList()))
) )
coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3) coEvery { attendanceDb.insertAll(any()) } returns listOf(1, 2, 3)
coEvery { attendanceDb.deleteAll(any()) } just Runs coEvery { attendanceDb.deleteAll(any()) } just Runs
@ -129,7 +134,7 @@ class AttendanceRepositoryTest {
coVerify { attendanceDb.insertAll(match { it.isEmpty() }) } coVerify { attendanceDb.insertAll(match { it.isEmpty() }) }
coVerify { coVerify {
attendanceDb.deleteAll(match { attendanceDb.deleteAll(match {
it.size == 1 && it[0] == remoteList.mapToEntities(semester)[1] it.size == 1 && it[0] == remoteList.mapToEntities(semester, emptyList())[1]
}) })
} }
} }