1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-19 22:49:08 -05:00

Hide grade statistics when is eduOne, add isEduOne flag migration (#2496)

Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
This commit is contained in:
Rafał Borcz 2024-03-24 19:42:36 +01:00 committed by GitHub
parent 3a55c3c760
commit 76d038eefa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 2935 additions and 88 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,15 @@
package io.github.wulkanowy.data package io.github.wulkanowy.data
import com.chuckerteam.chucker.api.ChuckerInterceptor import com.chuckerteam.chucker.api.ChuckerInterceptor
import io.github.wulkanowy.data.db.dao.StudentDao
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
import io.github.wulkanowy.data.db.entities.StudentIsEduOne
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.RemoteConfigHelper import io.github.wulkanowy.utils.RemoteConfigHelper
import io.github.wulkanowy.utils.WebkitCookieManagerProxy import io.github.wulkanowy.utils.WebkitCookieManagerProxy
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -14,9 +18,12 @@ import javax.inject.Singleton
class WulkanowySdkFactory @Inject constructor( class WulkanowySdkFactory @Inject constructor(
private val chuckerInterceptor: ChuckerInterceptor, private val chuckerInterceptor: ChuckerInterceptor,
private val remoteConfig: RemoteConfigHelper, private val remoteConfig: RemoteConfigHelper,
private val webkitCookieManagerProxy: WebkitCookieManagerProxy private val webkitCookieManagerProxy: WebkitCookieManagerProxy,
private val studentDb: StudentDao,
) { ) {
private val eduOneMutex = Mutex()
private val sdk = Sdk().apply { private val sdk = Sdk().apply {
androidVersion = android.os.Build.VERSION.RELEASE androidVersion = android.os.Build.VERSION.RELEASE
buildTag = android.os.Build.MODEL buildTag = android.os.Build.MODEL
@ -30,7 +37,12 @@ class WulkanowySdkFactory @Inject constructor(
fun create() = sdk fun create() = sdk
fun create(student: Student, semester: Semester? = null): Sdk { suspend fun create(student: Student, semester: Semester? = null): Sdk {
val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student)
return buildSdk(student, semester, overrideIsEduOne)
}
private fun buildSdk(student: Student, semester: Semester?, isStudentEduOne: Boolean): Sdk {
return create().apply { return create().apply {
email = student.email email = student.email
password = student.password password = student.password
@ -39,7 +51,7 @@ class WulkanowySdkFactory @Inject constructor(
studentId = student.studentId studentId = student.studentId
classId = student.classId classId = student.classId
emptyCookieJarInterceptor = true emptyCookieJarInterceptor = true
isEduOne = student.isEduOne isEduOne = isStudentEduOne
if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) { if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
mobileBaseUrl = student.mobileBaseUrl mobileBaseUrl = student.mobileBaseUrl
@ -62,4 +74,40 @@ class WulkanowySdkFactory @Inject constructor(
} }
} }
} }
private suspend fun checkEduOneAndMigrateIfNecessary(student: Student): Boolean {
if (student.isEduOne != null) return student.isEduOne
eduOneMutex.withLock {
val studentFromDatabase = studentDb.loadById(student.id)
if (studentFromDatabase?.isEduOne != null) {
Timber.d("Migration eduOne: already done")
return studentFromDatabase.isEduOne
}
Timber.d("Migration eduOne: flag missing. Running migration...")
val initializedSdk = buildSdk(
student = student,
semester = null,
isStudentEduOne = false, // doesn't matter
)
val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() }
.onFailure { Timber.e(it, "Migration eduOne: can't get current student") }
.getOrNull()
if (newCurrentStudent == null) {
Timber.d("Migration eduOne: failed, so skipping")
return false
}
Timber.d("Migration eduOne: success. New isEduOne flag: ${newCurrentStudent.isEduOne}")
val studentIsEduOne = StudentIsEduOne(
id = student.id,
isEduOne = newCurrentStudent.isEduOne
)
studentDb.update(studentIsEduOne)
return newCurrentStudent.isEduOne
}
}
} }

View File

@ -120,6 +120,7 @@ import io.github.wulkanowy.data.db.migrations.Migration55
import io.github.wulkanowy.data.db.migrations.Migration57 import io.github.wulkanowy.data.db.migrations.Migration57
import io.github.wulkanowy.data.db.migrations.Migration58 import io.github.wulkanowy.data.db.migrations.Migration58
import io.github.wulkanowy.data.db.migrations.Migration6 import io.github.wulkanowy.data.db.migrations.Migration6
import io.github.wulkanowy.data.db.migrations.Migration63
import io.github.wulkanowy.data.db.migrations.Migration7 import io.github.wulkanowy.data.db.migrations.Migration7
import io.github.wulkanowy.data.db.migrations.Migration8 import io.github.wulkanowy.data.db.migrations.Migration8
import io.github.wulkanowy.data.db.migrations.Migration9 import io.github.wulkanowy.data.db.migrations.Migration9
@ -175,6 +176,7 @@ import javax.inject.Singleton
AutoMigration(from = 59, to = 60), AutoMigration(from = 59, to = 60),
AutoMigration(from = 60, to = 61), AutoMigration(from = 60, to = 61),
AutoMigration(from = 61, to = 62), AutoMigration(from = 61, to = 62),
AutoMigration(from = 62, to = 63, spec = Migration63::class),
], ],
version = AppDatabase.VERSION_SCHEMA, version = AppDatabase.VERSION_SCHEMA,
exportSchema = true exportSchema = true
@ -183,7 +185,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 62 const val VERSION_SCHEMA = 63
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),

View File

@ -10,6 +10,7 @@ import androidx.room.Update
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
import io.github.wulkanowy.data.db.entities.StudentIsAuthorized import io.github.wulkanowy.data.db.entities.StudentIsAuthorized
import io.github.wulkanowy.data.db.entities.StudentIsEduOne
import io.github.wulkanowy.data.db.entities.StudentName import io.github.wulkanowy.data.db.entities.StudentName
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import javax.inject.Singleton import javax.inject.Singleton
@ -27,6 +28,9 @@ abstract class StudentDao {
@Update(entity = Student::class) @Update(entity = Student::class)
abstract suspend fun update(studentIsAuthorized: StudentIsAuthorized) abstract suspend fun update(studentIsAuthorized: StudentIsAuthorized)
@Update(entity = Student::class)
abstract suspend fun update(studentIsEduOne: StudentIsEduOne)
@Update(entity = Student::class) @Update(entity = Student::class)
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar) abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)

View File

@ -82,8 +82,8 @@ data class Student(
@ColumnInfo(name = "is_authorized", defaultValue = "0") @ColumnInfo(name = "is_authorized", defaultValue = "0")
val isAuthorized: Boolean, val isAuthorized: Boolean,
@ColumnInfo(name = "is_edu_one", defaultValue = "0") @ColumnInfo(name = "is_edu_one", defaultValue = "NULL")
val isEduOne: Boolean, val isEduOne: Boolean?,
) : Serializable { ) : Serializable {
@ -95,3 +95,22 @@ data class Student(
@ColumnInfo(name = "avatar_color") @ColumnInfo(name = "avatar_color")
var avatarColor = 0L var avatarColor = 0L
} }
@Entity
data class StudentIsAuthorized(
@PrimaryKey
var id: Long,
@ColumnInfo(name = "is_authorized", defaultValue = "NULL")
val isAuthorized: Boolean?,
) : Serializable
@Entity
data class StudentIsEduOne(
@PrimaryKey
var id: Long,
@ColumnInfo(name = "is_edu_one", defaultValue = "NULL")
val isEduOne: Boolean?,
) : Serializable

View File

@ -1,16 +0,0 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity
data class StudentIsAuthorized(
@ColumnInfo(name = "is_authorized", defaultValue = "0")
val isAuthorized: Boolean,
) : Serializable {
@PrimaryKey
var id: Long = 0
}

View File

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.AutoMigrationSpec
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration63 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
db.execSQL("UPDATE Students SET is_edu_one = NULL WHERE is_edu_one = 0")
}
}

View File

@ -16,9 +16,9 @@ import io.github.wulkanowy.data.mappers.mapToPojo
import io.github.wulkanowy.data.pojos.RegisterUser import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.security.Scrambler import io.github.wulkanowy.utils.security.Scrambler
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -101,23 +101,29 @@ class StudentRepository @Inject constructor(
return student return student
} }
suspend fun checkCurrentStudentAuthorizationStatus() { suspend fun updateCurrentStudentAuthStatus() {
Timber.i("Check isAuthorized: started")
val student = getCurrentStudent() val student = getCurrentStudent()
if (student.isAuthorized) {
if (!student.isAuthorized) { Timber.i("Check isAuthorized: already authorized")
val currentSemester = semesterDb.loadAll( return
studentId = student.studentId,
classId = student.classId,
).getCurrentOrLast()
val initializedSdk = wulkanowySdkFactory.create(student, currentSemester)
val isAuthorized = initializedSdk.getCurrentStudent()?.isAuthorized ?: false
if (isAuthorized) {
studentDb.update(StudentIsAuthorized(isAuthorized = true).apply {
id = student.id
})
} else throw NoAuthorizationException()
} }
val initializedSdk = wulkanowySdkFactory.create(student)
val newCurrentStudent = initializedSdk.getCurrentStudent() ?: return
if (!newCurrentStudent.isAuthorized) {
Timber.i("Check isAuthorized: authorization required")
throw NoAuthorizationException()
}
val studentIsAuthorized = StudentIsAuthorized(
id = student.id,
isAuthorized = true
)
Timber.i("Check isAuthorized: already authorized, update local status")
studentDb.update(studentIsAuthorized)
} }
suspend fun getCurrentStudent(decryptPass: Boolean = true): Student { suspend fun getCurrentStudent(decryptPass: Boolean = true): Student {

View File

@ -7,7 +7,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.View.INVISIBLE import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -31,14 +30,6 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
@Inject @Inject
lateinit var presenter: GradePresenter lateinit var presenter: GradePresenter
private val pagerAdapter by lazy {
BaseFragmentPagerAdapter(
fragmentManager = childFragmentManager,
pagesCount = 3,
lifecycle = lifecycle,
)
}
private var semesterSwitchMenu: MenuItem? = null private var semesterSwitchMenu: MenuItem? = null
companion object { companion object {
@ -52,6 +43,8 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
override val currentPageIndex get() = binding.gradeViewPager.currentItem override val currentPageIndex get() = binding.gradeViewPager.currentItem
private var pagerAdapter: BaseFragmentPagerAdapter? = null
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -71,13 +64,26 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
} }
override fun initView() { override fun initView() {
with(binding) {
gradeErrorRetry.setOnClickListener { presenter.onRetry() }
gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
}
override fun initTabs(pageCount: Int) {
pagerAdapter = BaseFragmentPagerAdapter(
lifecycle = lifecycle,
pagesCount = pageCount,
fragmentManager = childFragmentManager
)
with(binding.gradeViewPager) { with(binding.gradeViewPager) {
adapter = pagerAdapter adapter = pagerAdapter
offscreenPageLimit = 3 offscreenPageLimit = 3
setOnSelectPageListener(presenter::onPageSelected) setOnSelectPageListener(presenter::onPageSelected)
} }
with(pagerAdapter) { with(pagerAdapter!!) {
containerId = binding.gradeViewPager.id containerId = binding.gradeViewPager.id
titleFactory = { titleFactory = {
when (it) { when (it) {
@ -99,11 +105,6 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
} }
binding.gradeTabLayout.elevation = requireContext().dpToPx(4f) binding.gradeTabLayout.elevation = requireContext().dpToPx(4f)
with(binding) {
gradeErrorRetry.setOnClickListener { presenter.onRetry() }
gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -169,19 +170,20 @@ class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade
} }
override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) { override fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) {
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView) (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)
?.onParentLoadData(semesterId, forceRefresh) ?.onParentLoadData(semesterId, forceRefresh)
} }
override fun notifyChildParentReselected(index: Int) { override fun notifyChildParentReselected(index: Int) {
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected() (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentReselected()
} }
override fun notifyChildSemesterChange(index: Int) { override fun notifyChildSemesterChange(index: Int) {
(pagerAdapter.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester() (pagerAdapter?.getFragmentInstance(index) as? GradeView.GradeChildView)?.onParentChangeSemester()
} }
override fun onDestroyView() { override fun onDestroyView() {
pagerAdapter = null
presenter.onDetachView() presenter.onDetachView()
super.onDestroyView() super.onDestroyView()
} }

View File

@ -22,11 +22,8 @@ class GradePresenter @Inject constructor(
) : BasePresenter<GradeView>(errorHandler, studentRepository) { ) : BasePresenter<GradeView>(errorHandler, studentRepository) {
private var selectedIndex = 0 private var selectedIndex = 0
private var schoolYear = 0 private var schoolYear = 0
private var availableSemesters = emptyList<Semester>()
private var semesters = emptyList<Semester>()
private val loadedSemesterId = mutableMapOf<Int, Int>() private val loadedSemesterId = mutableMapOf<Int, Int>()
private lateinit var lastError: Throwable private lateinit var lastError: Throwable
@ -40,7 +37,7 @@ class GradePresenter @Inject constructor(
} }
fun onCreateMenu() { fun onCreateMenu() {
if (semesters.isEmpty()) view?.showSemesterSwitch(false) if (availableSemesters.isEmpty()) view?.showSemesterSwitch(false)
} }
fun onViewReselected() { fun onViewReselected() {
@ -49,8 +46,8 @@ class GradePresenter @Inject constructor(
} }
fun onSemesterSwitch(): Boolean { fun onSemesterSwitch(): Boolean {
if (semesters.isNotEmpty()) { if (availableSemesters.isNotEmpty()) {
view?.showSemesterDialog(selectedIndex - 1, semesters.take(2)) view?.showSemesterDialog(selectedIndex - 1, availableSemesters.take(2))
} }
return true return true
} }
@ -83,7 +80,7 @@ class GradePresenter @Inject constructor(
} }
fun onPageSelected(index: Int) { fun onPageSelected(index: Int) {
if (semesters.isNotEmpty()) loadChild(index) if (availableSemesters.isNotEmpty()) loadChild(index)
} }
fun onRetry() { fun onRetry() {
@ -101,16 +98,24 @@ class GradePresenter @Inject constructor(
private fun loadData() { private fun loadData() {
resourceFlow { resourceFlow {
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
semesterRepository.getSemesters(student, refreshOnNoCurrent = true) val semesters = semesterRepository.getSemesters(student, refreshOnNoCurrent = true)
student to semesters
} }
.logResourceStatus("load grade data") .logResourceStatus("load grade data")
.onResourceData { .onResourceData { (student, semesters) ->
val current = it.getCurrentOrLast() val currentSemester = semesters.getCurrentOrLast()
selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex selectedIndex =
schoolYear = current.schoolYear if (selectedIndex == 0) currentSemester.semesterName else selectedIndex
semesters = it.filter { semester -> semester.diaryId == current.diaryId } schoolYear = currentSemester.schoolYear
view?.setCurrentSemesterName(current.semesterName, schoolYear) availableSemesters = semesters.filter { semester ->
semester.diaryId == currentSemester.diaryId
}
view?.run { view?.run {
initTabs(if (student.isEduOne == true) 2 else 3)
setCurrentSemesterName(currentSemester.semesterName, schoolYear)
Timber.i("Loading grade data: Attempt load index $currentPageIndex") Timber.i("Loading grade data: Attempt load index $currentPageIndex")
loadChild(currentPageIndex) loadChild(currentPageIndex)
showErrorView(false) showErrorView(false)
@ -131,10 +136,10 @@ class GradePresenter @Inject constructor(
} }
private fun loadChild(index: Int, forceRefresh: Boolean = false) { private fun loadChild(index: Int, forceRefresh: Boolean = false) {
Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${semesters.joinToString { it.semesterName.toString() }}") Timber.d("Load grade tab child. Selected semester: $selectedIndex, semesters: ${availableSemesters.joinToString { it.semesterName.toString() }}")
val newSelectedSemesterId = try { val newSelectedSemesterId = try {
semesters.first { it.semesterName == selectedIndex }.semesterId availableSemesters.first { it.semesterName == selectedIndex }.semesterId
} catch (e: NoSuchElementException) { } catch (e: NoSuchElementException) {
Timber.e(e, "Selected semester no exists") Timber.e(e, "Selected semester no exists")
return return

View File

@ -9,6 +9,8 @@ interface GradeView : BaseView {
fun initView() fun initView()
fun initTabs(pageCount: Int)
fun showContent(show: Boolean) fun showContent(show: Boolean)
fun showProgress(show: Boolean) fun showProgress(show: Boolean)

View File

@ -73,7 +73,7 @@ class MainPresenter @Inject constructor(
syncManager.startPeriodicSyncWorker() syncManager.startPeriodicSyncWorker()
checkAppSupport() checkAppSupport()
checkCurrentStudentAuthorizationStatus() updateCurrentStudentAuthStatus()
analytics.logEvent("app_open", "destination" to initDestination.toString()) analytics.logEvent("app_open", "destination" to initDestination.toString())
Timber.i("Main view was initialized with $initDestination") Timber.i("Main view was initialized with $initDestination")
@ -193,12 +193,10 @@ class MainPresenter @Inject constructor(
view?.showStudentAvatar(currentStudent) view?.showStudentAvatar(currentStudent)
} }
private fun checkCurrentStudentAuthorizationStatus() { private fun updateCurrentStudentAuthStatus() {
presenterScope.launch { presenterScope.launch {
runCatching { studentRepository.checkCurrentStudentAuthorizationStatus() } runCatching { studentRepository.updateCurrentStudentAuthStatus() }
.onFailure { errorHandler.dispatch(it) } .onFailure { errorHandler.dispatch(it) }
Timber.i("Current student authorization status checked")
} }
} }
} }

View File

@ -150,7 +150,7 @@ class TimetablePresenter @Inject constructor(
val student = studentRepository.getCurrentStudent() val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
isEduOne = student.isEduOne isEduOne = student.isEduOne == true
checkInitialAndCurrentDate(semester) checkInitialAndCurrentDate(semester)
timetableRepository.getTimetable( timetableRepository.getTimetable(
student = student, student = student,

View File

@ -78,7 +78,7 @@ class TimetableWidgetFactory(
val lessons = getLessons(student, semester, date) val lessons = getLessons(student, semester, date)
val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date) val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
createItems(lessons, lastSync, !student.isEduOne) createItems(lessons, lastSync, !(student.isEduOne ?: false))
} }
.onFailure { .onFailure {
items = listOf(TimetableWidgetItem.Error(it)) items = listOf(TimetableWidgetItem.Error(it))

View File

@ -2,11 +2,12 @@ package io.github.wulkanowy
import io.github.wulkanowy.data.WulkanowySdkFactory import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.mockk.coEvery
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
fun createWulkanowySdkFactoryMock(sdk: Sdk) = mockk<WulkanowySdkFactory>() fun createWulkanowySdkFactoryMock(sdk: Sdk) = mockk<WulkanowySdkFactory>()
.apply { .apply {
every { create() } returns sdk every { create() } returns sdk
every { create(any(), any()) } answers { callOriginal() } coEvery { create(any(), any()) } returns sdk
} }

View File

@ -0,0 +1,129 @@
package io.github.wulkanowy.data
import android.os.Build
import dagger.hilt.android.testing.HiltTestApplication
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentIsEduOne
import io.github.wulkanowy.getStudentEntity
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.RegisterStudent
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
class WulkanowySdkFactoryTest {
private lateinit var wulkanowySdkFactory: WulkanowySdkFactory
private lateinit var studentDao: StudentDao
private lateinit var sdk: Sdk
@Before
fun setUp() {
sdk = mockk(relaxed = true)
studentDao = mockk()
wulkanowySdkFactory = spyk(
WulkanowySdkFactory(
chuckerInterceptor = mockk(),
remoteConfig = mockk(relaxed = true),
webkitCookieManagerProxy = mockk(),
studentDb = studentDao
)
)
every { wulkanowySdkFactory.create() } returns sdk
}
@Test
fun `check sdk flag isEduOne when local student is eduone`() = runTest {
val student = getStudentEntity().copy(isEduOne = true)
wulkanowySdkFactory.create(student)
verify { sdk.isEduOne = true }
coVerify(exactly = 0) { sdk.getCurrentStudent() }
}
@Test
fun `check sdk flag isEduOne when local student is not eduone`() = runTest {
val student = getStudentEntity().copy(isEduOne = false)
wulkanowySdkFactory.create(student)
verify { sdk.isEduOne = false }
coVerify(exactly = 0) { sdk.getCurrentStudent() }
}
@Test
fun `check sdk flag isEduOne when local student is eduone null and remote student is eduone true`() =
runTest {
val studentToProcess = getStudentEntity().copy(isEduOne = null)
val registerStudent = studentToProcess.toRegisterStudent(isEduOne = true)
coEvery { studentDao.loadById(any()) } returns studentToProcess
coEvery { studentDao.update(any(StudentIsEduOne::class)) } just Runs
coEvery { sdk.getCurrentStudent() } returns registerStudent
wulkanowySdkFactory.create(studentToProcess)
verify { sdk.isEduOne = true }
coVerify { sdk.getCurrentStudent() }
}
@Test
fun `check sdk flag isEduOne when local student is eduone null and remote student is eduone false`() =
runTest {
val studentToProcess = getStudentEntity().copy(isEduOne = null)
val registerStudent = studentToProcess.toRegisterStudent(isEduOne = false)
coEvery { studentDao.loadById(any()) } returns studentToProcess
coEvery { studentDao.update(any(StudentIsEduOne::class)) } just Runs
coEvery { sdk.getCurrentStudent() } returns registerStudent
wulkanowySdkFactory.create(studentToProcess)
verify { sdk.isEduOne = false }
coVerify { sdk.getCurrentStudent() }
}
@Test
fun `check sdk flag isEduOne when sdk getCurrentStudent throws error`() =
runTest {
val studentToProcess = getStudentEntity().copy(isEduOne = null)
coEvery { studentDao.loadById(any()) } returns studentToProcess
coEvery { studentDao.update(any(StudentIsEduOne::class)) } just Runs
coEvery { sdk.getCurrentStudent() } throws Exception()
wulkanowySdkFactory.create(studentToProcess)
verify { sdk.isEduOne = false }
coVerify { sdk.getCurrentStudent() }
}
private fun Student.toRegisterStudent(isEduOne: Boolean) = RegisterStudent(
studentId = studentId,
studentName = studentName,
studentSecondName = studentName,
studentSurname = studentName,
className = className,
classId = classId,
isParent = isParent,
isAuthorized = isAuthorized,
semesters = emptyList(),
isEduOne = isEduOne,
)
}

View File

@ -21,10 +21,10 @@ abstract class AbstractMigrationTest {
@get:Rule @get:Rule
val helper: MigrationTestHelper = MigrationTestHelper( val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(), instrumentation = InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java, databaseClass = AppDatabase::class.java,
listOf(Migration55()), specs = listOf(Migration63()),
FrameworkSQLiteOpenHelperFactory() openFactory = FrameworkSQLiteOpenHelperFactory()
) )
fun runMigrationsAndValidate(migration: Migration) { fun runMigrationsAndValidate(migration: Migration) {

View File

@ -7,8 +7,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication import dagger.hilt.android.testing.HiltTestApplication
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.* import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.ADFSLight
import kotlinx.coroutines.ExperimentalCoroutinesApi import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.ADFSLightScoped
import io.github.wulkanowy.sdk.Sdk.ScrapperLoginType.STANDARD
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -19,7 +20,6 @@ import kotlin.test.assertEquals
@HiltAndroidTest @HiltAndroidTest
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@OptIn(ExperimentalCoroutinesApi::class)
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class) @Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
class Migration54Test : AbstractMigrationTest() { class Migration54Test : AbstractMigrationTest() {

View File

@ -0,0 +1,89 @@
package io.github.wulkanowy.data.db.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import android.os.Build
import androidx.sqlite.db.SupportSQLiteDatabase
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
@HiltAndroidTest
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.O_MR1], application = HiltTestApplication::class)
class Migration63Test : AbstractMigrationTest() {
@Test
fun `update is_edu_one to null if 0`() = runTest {
with(helper.createDatabase(dbName, 62)) {
createStudent(1, 0)
close()
}
helper.runMigrationsAndValidate(dbName, 63, true)
val database = getMigratedRoomDatabase()
val studentDb = database.studentDao
val student = studentDb.loadById(1)
assertNull(student!!.isEduOne)
database.close()
}
@Test
fun `check is_edu_one is stay same`() = runTest {
with(helper.createDatabase(dbName, 62)) {
createStudent(1, 1)
close()
}
helper.runMigrationsAndValidate(dbName, 63, true)
val database = getMigratedRoomDatabase()
val studentDb = database.studentDao
val student = studentDb.loadById(1)
val isEduOne = assertNotNull(student!!.isEduOne)
assertTrue(isEduOne)
database.close()
}
private fun SupportSQLiteDatabase.createStudent(id: Long, isEduOneValue: Int) {
insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
put("scrapper_base_url", "https://fakelog.cf")
put("mobile_base_url", "")
put("login_type", "SCRAPPER")
put("login_mode", "SCRAPPER")
put("certificate_key", "")
put("private_key", "")
put("is_parent", false)
put("email", "jan@fakelog.cf")
put("password", "******")
put("symbol", "symbol")
put("student_id", Random.nextInt())
put("user_login_id", 123)
put("user_name", "studentName")
put("student_name", "studentName")
put("school_id", "123")
put("school_short", "")
put("school_name", "")
put("class_name", "")
put("class_id", Random.nextInt())
put("is_current", false)
put("registration_date", "0")
put("id", id)
put("nick", "")
put("avatar_color", "")
put("is_edu_one", isEduOneValue)
})
}
}