From 191b1ad022e19c02bb53231e88e38aba21b63a4d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miko=C5=82aj=20Pich?= <m.pich@outlook.com>
Date: Tue, 2 Jun 2020 15:51:15 +0200
Subject: [PATCH] Emulate summaries from grade list when summaries are empty
 (#855)

---
 app/build.gradle                              |   2 +-
 .../ui/modules/grade/GradeAverageProvider.kt  |  33 ++-
 .../grade/summary/GradeSummaryPresenter.kt    |   2 +-
 .../modules/grade/GradeAverageProviderTest.kt | 269 ++++++++++++------
 4 files changed, 215 insertions(+), 91 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 2521a10ed..f29af84fa 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -124,7 +124,7 @@ configurations.all {
 }
 
 dependencies {
-    implementation "io.github.wulkanowy:sdk:0.18.1"
+    implementation "io.github.wulkanowy:sdk:d0081b4"
 
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
     implementation "androidx.core:core-ktx:1.2.0"
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt
index 954b57566..9582a5db3 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProvider.kt
@@ -1,5 +1,7 @@
 package io.github.wulkanowy.ui.modules.grade
 
+import io.github.wulkanowy.data.db.entities.Grade
+import io.github.wulkanowy.data.db.entities.GradeSummary
 import io.github.wulkanowy.data.db.entities.Semester
 import io.github.wulkanowy.data.db.entities.Student
 import io.github.wulkanowy.data.repositories.grade.GradeRepository
@@ -58,15 +60,14 @@ class GradeAverageProvider @Inject constructor(
             val isAnyAverage = summaries.any { it.average != .0 }
             val allGrades = details.groupBy { it.subject }
 
-            summaries.map { summary ->
+            summaries.emulateEmptySummaries(student, semester, allGrades.toList(), isAnyAverage).map { summary ->
                 val grades = allGrades[summary.subject].orEmpty()
                 GradeDetailsWithAverage(
                     subject = summary.subject,
                     average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
-                        grades.map {
-                            if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier)
-                            else it
-                        }.calcAverage()
+                        (if (student.loginMode == Sdk.Mode.SCRAPPER.name)
+                            grades.map { it.changeModifier(plusModifier, minusModifier) }
+                        else grades).calcAverage()
                     } else summary.average,
                     points = summary.pointsSum,
                     summary = summary,
@@ -75,4 +76,26 @@ class GradeAverageProvider @Inject constructor(
             }
         }
     }
+
+    private fun List<GradeSummary>.emulateEmptySummaries(student: Student, semester: Semester, grades: List<Pair<String, List<Grade>>>, calcAverage: Boolean): List<GradeSummary> {
+        if (isNotEmpty() && size == grades.size) return this
+
+        return grades.mapIndexed { i, (subject, details) ->
+            singleOrNull { it.subject == subject }?.let { return@mapIndexed it }
+            GradeSummary(
+                studentId = student.studentId,
+                semesterId = semester.semesterId,
+                position = i,
+                subject = subject,
+                predictedGrade = "",
+                finalGrade = "",
+                proposedPoints = "",
+                finalPoints = "",
+                pointsSum = "",
+                average = if (calcAverage) (if (student.loginMode == Sdk.Mode.SCRAPPER.name) {
+                    details.map { it.changeModifier(plusModifier, minusModifier) }
+                } else details).calcAverage() else .0
+            )
+        }
+    }
 }
diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
index a61ad4af9..229a0107d 100644
--- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
+++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt
@@ -119,7 +119,7 @@ class GradeSummaryPresenter @Inject constructor(
             summary.finalGrade.isBlank()
                 && summary.predictedGrade.isBlank()
                 && average == .0
-                && points == "-"
+                && points.isBlank()
         }
     }
 }
diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt
index fcc30593f..ee8634ca8 100644
--- a/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt
+++ b/app/src/test/java/io/github/wulkanowy/ui/modules/grade/GradeAverageProviderTest.kt
@@ -1,11 +1,11 @@
 package io.github.wulkanowy.ui.modules.grade
 
+import io.github.wulkanowy.createSemesterEntity
 import io.github.wulkanowy.data.db.entities.Grade
 import io.github.wulkanowy.data.db.entities.GradeSummary
 import io.github.wulkanowy.data.db.entities.Student
 import io.github.wulkanowy.data.repositories.grade.GradeRepository
 import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
-import io.github.wulkanowy.createSemesterEntity
 import io.github.wulkanowy.data.repositories.semester.SemesterRepository
 import io.github.wulkanowy.sdk.Sdk
 import io.reactivex.Single
@@ -84,8 +84,6 @@ class GradeAverageProviderTest {
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33)
-        `when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
         `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
         `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
 
@@ -93,7 +91,83 @@ class GradeAverageProviderTest {
     }
 
     @Test
-    fun onlyOneSemesterTest() {
+    fun `force calc current semester average with default modifiers in scraper mode`() {
+        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
+        `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
+        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
+
+        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
+
+        assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus
+    }
+
+    @Test
+    fun `force calc current semester average with custom modifiers in scraper mode`() {
+        val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name)
+
+        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
+        `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33)
+        `when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
+
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
+        `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
+        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
+
+        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
+
+        assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0) // from details and after set custom plus/minus
+    }
+
+    @Test
+    fun `force calc current semester average with custom modifiers in api mode`() {
+        val student = student.copy(loginMode = Sdk.Mode.API.name)
+
+        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
+        `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) // useless in this mode
+        `when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
+
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
+        `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
+        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
+
+        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
+
+        assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375
+    }
+
+    @Test
+    fun `force calc current semester average with custom modifiers in hybrid mode`() {
+        val student = student.copy(loginMode = Sdk.Mode.HYBRID.name)
+
+        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
+        `when`(preferencesRepository.gradeMinusModifier).thenReturn(.33) // useless in this mode
+        `when`(preferencesRepository.gradePlusModifier).thenReturn(.33)
+
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
+        `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
+        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
+
+        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
+
+        assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0) // (from details): 3.375
+    }
+
+    @Test
+    fun `calc current semester average`() {
+        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
+        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
+
+        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
+
+        assertEquals(2, items.size)
+        assertEquals(2.9, items.single { it.subject == "Matematyka" }.average, .0) // from summary: 2,9
+        assertEquals(3.4, items.single { it.subject == "Fizyka" }.average, .0) // from details: 3,4
+    }
+
+    @Test
+    fun `force calc current semester average`() {
         `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
         `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
         `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
@@ -101,65 +175,12 @@ class GradeAverageProviderTest {
         val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
 
         assertEquals(2, items.size)
-        assertEquals(2.5, items.single { it.subject == "Matematyka" }.average, .0)
-        assertEquals(3.0, items.single { it.subject == "Fizyka" }.average, .0)
+        assertEquals(2.5, items.single { it.subject == "Matematyka" }.average, .0) // from details: 2,5
+        assertEquals(3.0, items.single { it.subject == "Fizyka" }.average, .0) // from details: 3,0
     }
 
     @Test
-    fun onlyOneSemester_gradesWithModifiers_default() {
-        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
-        `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
-        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
-
-        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
-
-        assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0)
-    }
-
-    @Test
-    fun onlyOneSemester_gradesWithModifiers_api() {
-        val student = student.copy(loginMode = Sdk.Mode.API.name)
-
-        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
-        `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
-        `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
-        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
-
-        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
-
-        assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0)
-    }
-
-    @Test
-    fun onlyOneSemester_gradesWithModifiers_scrapper() {
-        val student = student.copy(loginMode = Sdk.Mode.SCRAPPER.name)
-
-        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
-        `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
-        `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
-        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
-
-        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
-
-        assertEquals(3.5, items.single { it.subject == "Język polski" }.average, .0)
-    }
-
-    @Test
-    fun onlyOneSemester_gradesWithModifiers_hybrid() {
-        val student = student.copy(loginMode = Sdk.Mode.HYBRID.name)
-
-        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
-        `when`(preferencesRepository.gradeAverageMode).thenReturn("only_one_semester")
-        `when`(semesterRepository.getSemesters(student)).thenReturn(Single.just(semesters))
-        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGradeWithModifier to secondSummariesWithModifier))
-
-        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
-
-        assertEquals(3.375, items.single { it.subject == "Język polski" }.average, .0)
-    }
-
-    @Test
-    fun allYearFirstSemesterTest() {
+    fun `force calc full year average when current is first`() {
         `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
         `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
         `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
@@ -167,33 +188,19 @@ class GradeAverageProviderTest {
         val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[1].semesterId).blockingGet()
 
         assertEquals(2, items.size)
-        assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0)
-        assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0)
-    }
-
-    @Test
-    fun allYearSecondSemesterTest() {
-        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
-        `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
-        `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
-        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
-
-        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
-
-        assertEquals(2, items.size)
-        assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0)
-        assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0)
+        assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summary): 3,5
+        assertEquals(3.5, items.single { it.subject == "Fizyka" }.average, .0) // (from summary): 3,5
     }
 
     @Test(expected = IllegalArgumentException::class)
-    fun incorrectAverageModeTest() {
+    fun `calc average on invalid mode`() {
         `when`(preferencesRepository.gradeAverageMode).thenReturn("test_mode")
 
         gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId, true).blockingGet()
     }
 
     @Test
-    fun allYearSemester_averageFromSummary() {
+    fun `calc full year average`() {
         `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
         `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
         `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf(
@@ -208,14 +215,14 @@ class GradeAverageProviderTest {
         val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
 
         assertEquals(2, items.size)
-        assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0)
-        assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0)
+        assertEquals(3.25, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 3,0 + 3,5 → 3,25
+        assertEquals(3.75, items.single { it.subject == "Fizyka" }.average, .0) // (from summaries ↑): 3,5 + 4,0 → 3,75
     }
 
     @Test
-    fun onlyOneSemester_averageFromSummary_forceCalc() {
-        `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
+    fun `force calc full year average`() {
         `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
         `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
         `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf(
             getSummary(22, "Matematyka", 1.1),
@@ -225,8 +232,102 @@ class GradeAverageProviderTest {
         val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
 
         assertEquals(2, items.size)
-        assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0)
-        assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0)
+        assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
+        assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5  + 3,0 → 3,25
+    }
+
+    @Test
+    fun `calc full year average when no summaries`() {
+        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
+
+        `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to emptyList()))
+        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to emptyList()))
+
+        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
+
+        assertEquals(2, items.size)
+        assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 2,5 + 3,5 → 3,0
+        assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5  + 3,0 → 3,25
+    }
+
+    @Test
+    fun `force calc full year average when no summaries`() {
+        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
+
+        `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to emptyList()))
+        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to emptyList()))
+
+        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
+
+        assertEquals(2, items.size)
+        assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
+        assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5  + 3,0 → 3,25
+    }
+
+    @Test
+    fun `calc full year average when missing summaries in both semesters`() {
+        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
+
+        `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to listOf(
+            getSummary(22, "Matematyka", 4.0)
+        )))
+        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to listOf(
+            getSummary(23, "Matematyka", 3.0)
+        )))
+
+        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
+
+        assertEquals(2, items.size)
+        assertEquals(3.5, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries ↑): 4,0 + 3,0 → 3,5
+        assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5  + 3,0 → 3,25
+    }
+
+    @Test
+    fun `calc full year average when missing summary in second semester`() {
+        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
+
+        `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries))
+        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries.dropLast(1)))
+
+        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
+
+        assertEquals(2, items.size)
+        assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4
+        assertEquals(3.05, items.single { it.subject == "Fizyka" }.average, .0) // 3,1 (from summary) + 3,0 (from details) → 3,05
+    }
+
+    @Test
+    fun `calc full year average when missing summary in first semester`() {
+        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(false)
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
+
+        `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries.dropLast(1)))
+        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
+
+        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
+
+        assertEquals(2, items.size)
+        assertEquals(3.4, items.single { it.subject == "Matematyka" }.average, .0) // (from summaries): 3,9 + 2,9 → 3,4
+        assertEquals(3.45, items.single { it.subject == "Fizyka" }.average, .0) // 3,5 (from details) + 3,4 (from summary) → 3,45
+    }
+
+    @Test
+    fun `force calc full year average when missing summary in first semester`() {
+        `when`(preferencesRepository.gradeAverageForceCalc).thenReturn(true)
+        `when`(preferencesRepository.gradeAverageMode).thenReturn("all_year")
+
+        `when`(gradeRepository.getGrades(student, semesters[1])).thenReturn(Single.just(firstGrades to firstSummaries.dropLast(1)))
+        `when`(gradeRepository.getGrades(student, semesters[2])).thenReturn(Single.just(secondGrades to secondSummaries))
+
+        val items = gradeAverageProvider.getGradesDetailsWithAverage(student, semesters[2].semesterId).blockingGet()
+
+        assertEquals(2, items.size)
+        assertEquals(3.0, items.single { it.subject == "Matematyka" }.average, .0) // (from details): 3,5 + 2,5 → 3,0
+        assertEquals(3.25, items.single { it.subject == "Fizyka" }.average, .0) // (from details): 3,5  + 3,0 → 3,25
     }
 
     private fun getGrade(semesterId: Int, subject: String, value: Double, modifier: Double = 0.0): Grade {