From 7c02df4d30c58df47232906d8751239788814b0b Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Mon, 31 Oct 2022 19:41:49 +0100 Subject: [PATCH] Add unit tests --- .github/workflows/check.yml | 2 +- .github/workflows/sign.yml | 2 +- app/build.gradle.kts | 3 +- .../data/network/MealieDataSourceWrapper.kt | 4 + .../data/recipes/db/RecipeStorageImpl.kt | 4 +- .../mealient/extensions/ModelMappings.kt | 2 +- .../data/add/impl/AddRecipeRepoTest.kt | 73 ++++++ .../data/auth/impl/AuthRepoImplTest.kt | 10 +- .../data/auth/impl/AuthStorageImplTest.kt | 5 +- .../data/baseurl/ServerInfoRepoTest.kt | 153 +++++++++++++ ...geImplTest.kt => ServerInfoStorageTest.kt} | 39 +++- .../network/MealieDataSourceWrapperTest.kt | 170 ++++++++++++-- .../data/recipes/db/RecipeStorageImplTest.kt | 22 +- .../impl/RecipeImageUrlProviderImplTest.kt | 4 +- ...ecipeRepoImplTest.kt => RecipeRepoTest.kt} | 26 ++- .../recipes/impl/RecipesRemoteMediatorTest.kt | 4 +- .../mealient/extensions/ModelMappingsTest.kt | 145 ++++++++++++ .../extensions/RemoteToLocalMappingsTest.kt | 78 ------- .../mealient/test/AuthImplTestData.kt | 3 +- .../gq/kirmanak/mealient/test/FakeLogger.kt | 34 +++ .../mealient/test/RecipeImplTestData.kt | 211 ++++++++++++++++-- .../mealient/ui/add/AddRecipeViewModelTest.kt | 4 +- .../ui/baseurl/BaseURLViewModelTest.kt | 4 +- .../ui/disclaimer/DisclaimerViewModelTest.kt | 4 +- .../recipe/entity/RecipeIngredientEntity.kt | 20 +- .../recipe/entity/RecipeInstructionEntity.kt | 20 +- .../v1/models/UpdateRecipeRequestV1.kt | 37 ++- 27 files changed, 903 insertions(+), 180 deletions(-) create mode 100644 app/src/test/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoTest.kt create mode 100644 app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoTest.kt rename app/src/test/java/gq/kirmanak/mealient/data/baseurl/{ServerInfoStorageImplTest.kt => ServerInfoStorageTest.kt} (51%) rename app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/{RecipeRepoImplTest.kt => RecipeRepoTest.kt} (74%) create mode 100644 app/src/test/java/gq/kirmanak/mealient/extensions/ModelMappingsTest.kt delete mode 100644 app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt create mode 100644 app/src/test/java/gq/kirmanak/mealient/test/FakeLogger.kt diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 89056ea..34e452a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -14,7 +14,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew check coverageReport sonar --no-daemon --no-configuration-cache --no-build-cache + run: ./gradlew check coverageReport sonar --no-configuration-cache - name: Publish test reports uses: mikepenz/action-junit-report@v3 diff --git a/.github/workflows/sign.yml b/.github/workflows/sign.yml index 2a7df29..ff9a59f 100644 --- a/.github/workflows/sign.yml +++ b/.github/workflows/sign.yml @@ -28,7 +28,7 @@ jobs: echo "storePassword=$MEALIENT_KEY_STORE_PASSWORD" >> keystore.properties echo "keyAlias=$MEALIENT_KEY_ALIAS" >> keystore.properties echo "keyPassword=$MEALIENT_KEY_PASSWORD" >> keystore.properties - ./gradlew build coverageReport sonar uploadToAppSweepRelease --no-daemon --no-configuration-cache --no-build-cache + ./gradlew build coverageReport sonar uploadToAppSweepRelease --no-configuration-cache cp app/build/outputs/apk/release/*.apk mealient-release.apk - name: Upload signed APK diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1a02acb..ce96241 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -42,7 +42,8 @@ android { buildTypes { getByName("debug") { ext["enableCrashlytics"] = false - isTestCoverageEnabled = true + enableUnitTestCoverage = true + enableAndroidTestCoverage = true } getByName("release") { isMinifyEnabled = true diff --git a/app/src/main/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapper.kt b/app/src/main/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapper.kt index 1fbd9d3..4f451fc 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapper.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapper.kt @@ -13,6 +13,7 @@ import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 import gq.kirmanak.mealient.extensions.* +import gq.kirmanak.mealient.logging.Logger import javax.inject.Inject import javax.inject.Singleton @@ -22,6 +23,7 @@ class MealieDataSourceWrapper @Inject constructor( private val authRepo: AuthRepo, private val v0Source: MealieDataSourceV0, private val v1Source: MealieDataSourceV1, + private val logger: Logger, ) : AddRecipeDataSource, RecipeDataSource { override suspend fun addRecipe( @@ -68,9 +70,11 @@ class MealieDataSourceWrapper @Inject constructor( val version = serverInfoRepo.getVersion() return runCatchingExceptCancel { block(authHeader, url, version) }.getOrElse { if (it is NetworkError.Unauthorized) { + logger.e { "Unauthorized, trying to invalidate token" } authRepo.invalidateAuthHeader() // Trying again with new authentication header val newHeader = authRepo.getAuthHeader() + logger.e { "New token ${if (newHeader == authHeader) "matches" else "doesn't match"} old token" } if (newHeader == authHeader) throw it else block(newHeader, url, version) } else { throw it diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt index de086fc..5b7f257 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt @@ -8,10 +8,10 @@ import gq.kirmanak.mealient.database.AppDb import gq.kirmanak.mealient.database.recipe.RecipeDao import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity -import gq.kirmanak.mealient.extensions.recipeEntity import gq.kirmanak.mealient.extensions.toRecipeEntity import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity import gq.kirmanak.mealient.extensions.toRecipeInstructionEntity +import gq.kirmanak.mealient.extensions.toRecipeSummaryEntity import gq.kirmanak.mealient.logging.Logger import javax.inject.Inject import javax.inject.Singleton @@ -29,7 +29,7 @@ class RecipeStorageImpl @Inject constructor( logger.v { "saveRecipes() called with $recipes" } for (recipe in recipes) { - val recipeSummaryEntity = recipe.recipeEntity() + val recipeSummaryEntity = recipe.toRecipeSummaryEntity() recipeDao.insertRecipe(recipeSummaryEntity) } } diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/ModelMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/ModelMappings.kt index 705ceb5..1ae6168 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/ModelMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/ModelMappings.kt @@ -53,7 +53,7 @@ fun GetRecipeSummaryResponseV1.toRecipeSummaryInfo() = RecipeSummaryInfo( imageId = remoteId, ) -fun RecipeSummaryInfo.recipeEntity() = RecipeSummaryEntity( +fun RecipeSummaryInfo.toRecipeSummaryEntity() = RecipeSummaryEntity( remoteId = remoteId, name = name, slug = slug, diff --git a/app/src/test/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoTest.kt new file mode 100644 index 0000000..801a0a4 --- /dev/null +++ b/app/src/test/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoTest.kt @@ -0,0 +1,73 @@ +package gq.kirmanak.mealient.data.add.impl + +import com.google.common.truth.Truth.assertThat +import gq.kirmanak.mealient.data.add.AddRecipeDataSource +import gq.kirmanak.mealient.data.add.AddRecipeRepo +import gq.kirmanak.mealient.datastore.recipe.AddRecipeStorage +import gq.kirmanak.mealient.logging.Logger +import gq.kirmanak.mealient.test.FakeLogger +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_ADD_RECIPE_INFO +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_DRAFT +import io.mockk.* +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class AddRecipeRepoTest { + + @MockK(relaxUnitFun = true) + lateinit var dataSource: AddRecipeDataSource + + @MockK(relaxUnitFun = true) + lateinit var storage: AddRecipeStorage + + private val logger: Logger = FakeLogger() + + private lateinit var subject: AddRecipeRepo + + @Before + fun setUp() { + MockKAnnotations.init(this) + subject = AddRecipeRepoImpl(dataSource, storage, logger) + } + + @Test + fun `when clear expect storage clear`() = runTest { + subject.clear() + coVerify { storage.clear() } + } + + @Test + fun `when saveRecipe expect then reads storage`() = runTest { + every { storage.updates } returns flowOf(PORRIDGE_RECIPE_DRAFT) + coEvery { dataSource.addRecipe(any()) } returns "porridge" + subject.saveRecipe() + verify { storage.updates } + } + + @Test + fun `when saveRecipe expect addRecipe with stored value`() = runTest { + every { storage.updates } returns flowOf(PORRIDGE_RECIPE_DRAFT) + coEvery { dataSource.addRecipe(any()) } returns "porridge" + subject.saveRecipe() + coVerify { dataSource.addRecipe(eq(PORRIDGE_ADD_RECIPE_INFO)) } + } + + @Test + fun `when saveRecipe expect result from dataSource`() = runTest { + every { storage.updates } returns flowOf(PORRIDGE_RECIPE_DRAFT) + val expected = "porridge" + coEvery { dataSource.addRecipe(any()) } returns expected + assertThat(subject.saveRecipe()).isEqualTo(expected) + } + + @Test + fun `when preserve expect save to storage`() = runTest { + subject.preserve(PORRIDGE_ADD_RECIPE_INFO) + coVerify { storage.save(eq(PORRIDGE_RECIPE_DRAFT)) } + } +} \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt index e3f5b80..f77c2bc 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt @@ -10,9 +10,10 @@ import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD -import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION_V0 import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME +import gq.kirmanak.mealient.test.FakeLogger import io.mockk.* import io.mockk.impl.annotations.MockK import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -34,8 +35,7 @@ class AuthRepoImplTest { @MockK(relaxUnitFun = true) lateinit var storage: AuthStorage - @MockK(relaxUnitFun = true) - lateinit var logger: Logger + private val logger: Logger = FakeLogger() lateinit var subject: AuthRepo @@ -53,7 +53,7 @@ class AuthRepoImplTest { @Test fun `when authenticate successfully then saves to storage`() = runTest { - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns TEST_TOKEN coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL subject.authenticate(TEST_USERNAME, TEST_PASSWORD) @@ -104,7 +104,7 @@ class AuthRepoImplTest { fun `when invalidate with credentials then calls authenticate`() = runTest { coEvery { storage.getEmail() } returns TEST_USERNAME coEvery { storage.getPassword() } returns TEST_PASSWORD - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns TEST_TOKEN subject.invalidateAuthHeader() diff --git a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImplTest.kt index 5208d82..ed8f816 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImplTest.kt @@ -14,9 +14,9 @@ import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME +import gq.kirmanak.mealient.test.FakeLogger import gq.kirmanak.mealient.test.HiltRobolectricTest import io.mockk.MockKAnnotations -import io.mockk.impl.annotations.MockK import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest @@ -32,8 +32,7 @@ class AuthStorageImplTest : HiltRobolectricTest() { @ApplicationContext lateinit var context: Context - @MockK(relaxUnitFun = true) - lateinit var logger: Logger + private val logger: Logger = FakeLogger() lateinit var subject: AuthStorage diff --git a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoTest.kt new file mode 100644 index 0000000..0c58f63 --- /dev/null +++ b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoTest.kt @@ -0,0 +1,153 @@ +package gq.kirmanak.mealient.data.baseurl + +import com.google.common.truth.Truth.assertThat +import gq.kirmanak.mealient.datasource.NetworkError +import gq.kirmanak.mealient.logging.Logger +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_VERSION +import gq.kirmanak.mealient.test.FakeLogger +import gq.kirmanak.mealient.test.RecipeImplTestData.VERSION_INFO_V0 +import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class ServerInfoRepoTest { + + private val logger: Logger = FakeLogger() + + private lateinit var subject: ServerInfoRepo + + @MockK(relaxUnitFun = true) + lateinit var storage: ServerInfoStorage + + @MockK(relaxUnitFun = true) + lateinit var dataSource: VersionDataSource + + @Before + fun setUp() { + MockKAnnotations.init(this) + subject = ServerInfoRepoImpl(storage, dataSource, logger) + } + + @Test + fun `when storage returns null url expect getUrl return null`() = runTest { + coEvery { storage.getBaseURL() } returns null + assertThat(subject.getUrl()).isNull() + } + + @Test + fun `when storage returns url value expect getUrl return value`() = runTest { + val expected = TEST_BASE_URL + coEvery { storage.getBaseURL() } returns expected + assertThat(subject.getUrl()).isEqualTo(expected) + } + + @Test(expected = IllegalStateException::class) + fun `when storage returns null url expect requireUrl to throw`() = runTest { + coEvery { storage.getBaseURL() } returns null + subject.requireUrl() + } + + @Test + fun `when getUrl expect storage is accessed`() = runTest { + coEvery { storage.getBaseURL() } returns null + subject.getUrl() + coVerify { storage.getBaseURL() } + } + + @Test + fun `when requireUrl expect storage is accessed`() = runTest { + coEvery { storage.getBaseURL() } returns TEST_BASE_URL + subject.requireUrl() + coVerify { storage.getBaseURL() } + } + + @Test + fun `when storeBaseUrl expect call to storage`() = runTest { + subject.storeBaseURL(TEST_BASE_URL, TEST_VERSION) + coVerify { storage.storeBaseURL(TEST_BASE_URL, TEST_VERSION) } + } + + @Test + fun `when storage is empty expect getVersion to call data source`() = runTest { + coEvery { storage.getServerVersion() } returns null + coEvery { storage.getBaseURL() } returns TEST_BASE_URL + coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VERSION_INFO_V0 + subject.getVersion() + coVerify { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } + } + + @Test + fun `when storage is empty and data source has value expect getVersion to save it`() = runTest { + coEvery { storage.getServerVersion() } returns null + coEvery { storage.getBaseURL() } returns TEST_BASE_URL + coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VersionInfo(TEST_VERSION) + subject.getVersion() + coVerify { storage.storeServerVersion(TEST_VERSION) } + } + + @Test(expected = NetworkError.NotMealie::class) + fun `when data source has invalid value expect getVersion to throw`() = runTest { + coEvery { storage.getServerVersion() } returns null + coEvery { storage.getBaseURL() } returns TEST_BASE_URL + coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VersionInfo("v2.0.0") + subject.getVersion() + } + + @Test + fun `when data source has invalid value expect getVersion not to save`() = runTest { + coEvery { storage.getServerVersion() } returns null + coEvery { storage.getBaseURL() } returns TEST_BASE_URL + coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VersionInfo("v2.0.0") + subject.runCatching { getVersion() } + coVerify(inverse = true) { storage.storeServerVersion(any()) } + } + + @Test + fun `when storage has value expect getVersion to not get URL`() = runTest { + coEvery { storage.getServerVersion() } returns TEST_VERSION + subject.getVersion() + coVerify(inverse = true) { storage.getBaseURL() } + } + + @Test + fun `when storage has value expect getVersion to not call data source`() = runTest { + coEvery { storage.getServerVersion() } returns TEST_VERSION + subject.getVersion() + coVerify(inverse = true) { dataSource.getVersionInfo(any()) } + } + + @Test + fun `when storage has v0 value expect getVersion to return parsed`() = runTest { + coEvery { storage.getServerVersion() } returns "v0.5.6" + assertThat(subject.getVersion()).isEqualTo(ServerVersion.V0) + } + + @Test + fun `when storage has v1 value expect getVersion to return parsed`() = runTest { + coEvery { storage.getServerVersion() } returns "v1.0.0-beta05" + assertThat(subject.getVersion()).isEqualTo(ServerVersion.V1) + } + + @Test + fun `when data source has valid v0 value expect getVersion to return it`() = runTest { + coEvery { storage.getServerVersion() } returns null + coEvery { storage.getBaseURL() } returns TEST_BASE_URL + coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VersionInfo("v0.5.6") + assertThat(subject.getVersion()).isEqualTo(ServerVersion.V0) + } + + @Test + fun `when data source has valid v1 value expect getVersion to return it`() = runTest { + coEvery { storage.getServerVersion() } returns null + coEvery { storage.getBaseURL() } returns TEST_BASE_URL + coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VersionInfo("v1.0.0-beta05") + assertThat(subject.getVersion()).isEqualTo(ServerVersion.V1) + } +} \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageTest.kt similarity index 51% rename from app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageImplTest.kt rename to app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageTest.kt index 978310d..2360d98 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageTest.kt @@ -4,6 +4,8 @@ import androidx.datastore.preferences.core.stringPreferencesKey import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.baseurl.impl.ServerInfoStorageImpl import gq.kirmanak.mealient.data.storage.PreferencesStorage +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_VERSION import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify @@ -15,7 +17,7 @@ import org.junit.Before import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) -class ServerInfoStorageImplTest { +class ServerInfoStorageTest { @MockK(relaxUnitFun = true) lateinit var preferencesStorage: PreferencesStorage @@ -34,26 +36,43 @@ class ServerInfoStorageImplTest { } @Test - fun `when getBaseURL and preferences storage empty then null`() = runTest { + fun `when preferences storage empty expect getBaseURL return null`() = runTest { coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns null assertThat(subject.getBaseURL()).isNull() } @Test - fun `when getBaseUrl and preferences storage has value then value`() = runTest { - coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns "baseUrl" - assertThat(subject.getBaseURL()).isEqualTo("baseUrl") + fun `when preferences storage has value expect getBaseUrl return value`() = runTest { + coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns TEST_BASE_URL + assertThat(subject.getBaseURL()).isEqualTo(TEST_BASE_URL) } @Test - fun `when storeBaseURL then calls preferences storage`() = runTest { - subject.storeBaseURL("baseUrl", "v0.5.6") + fun `when storeBaseURL expect call to preferences storage`() = runTest { + subject.storeBaseURL(TEST_BASE_URL, TEST_VERSION) coVerify { - preferencesStorage.baseUrlKey preferencesStorage.storeValues( - eq(Pair(baseUrlKey, "baseUrl")), - eq(Pair(serverVersionKey, "v0.5.6")), + eq(Pair(baseUrlKey, TEST_BASE_URL)), + eq(Pair(serverVersionKey, TEST_VERSION)), ) } } + + @Test + fun `when preference storage is empty expect getServerVersion return null`() = runTest { + coEvery { preferencesStorage.getValue(eq(serverVersionKey)) } returns null + assertThat(subject.getServerVersion()).isNull() + } + + @Test + fun `when preference storage has value expect getServerVersion return value`() = runTest { + coEvery { preferencesStorage.getValue(eq(serverVersionKey)) } returns TEST_VERSION + assertThat(subject.getServerVersion()).isEqualTo(TEST_VERSION) + } + + @Test + fun `when storeServerVersion then calls preferences storage`() = runTest { + subject.storeServerVersion(TEST_VERSION) + coVerify { preferencesStorage.storeValues(eq(Pair(serverVersionKey, TEST_VERSION))) } + } } diff --git a/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt index 7578362..208e1d7 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt @@ -1,19 +1,30 @@ package gq.kirmanak.mealient.data.network +import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.auth.AuthRepo import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 -import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0 import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 +import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL -import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.coVerifyAll +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION_V0 +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION_V1 +import gq.kirmanak.mealient.test.FakeLogger +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_ADD_RECIPE_INFO +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_ADD_RECIPE_REQUEST_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_CREATE_RECIPE_REQUEST_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_FULL_RECIPE_INFO +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_RESPONSE_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_RESPONSE_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_UPDATE_RECIPE_REQUEST_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.RECIPE_SUMMARY_PORRIDGE_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.RECIPE_SUMMARY_PORRIDGE_V1 +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before @@ -37,30 +48,153 @@ class MealieDataSourceWrapperTest { lateinit var subject: MealieDataSourceWrapper + private val logger: Logger = FakeLogger() + @Before fun setUp() { MockKAnnotations.init(this) - subject = MealieDataSourceWrapper(serverInfoRepo, authRepo, v0Source, v1Source) + subject = MealieDataSourceWrapper(serverInfoRepo, authRepo, v0Source, v1Source, logger) } @Test - fun `when withAuthHeader fails with Unauthorized then invalidates auth`() = runTest { - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION + fun `when makeCall fails with Unauthorized expect it to invalidate token`() = runTest { + val slug = "porridge" + coEvery { + v0Source.requestRecipeInfo(any(), isNull(), any()) + } throws NetworkError.Unauthorized(IOException()) + coEvery { + v0Source.requestRecipeInfo(any(), eq(TEST_AUTH_HEADER), any()) + } returns PORRIDGE_RECIPE_RESPONSE_V0 + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL coEvery { authRepo.getAuthHeader() } returns null andThen TEST_AUTH_HEADER - coEvery { - v0Source.requestRecipeInfo(eq(TEST_BASE_URL), isNull(), eq("cake")) - } throws NetworkError.Unauthorized(IOException()) - val successResponse = mockk(relaxed = true) - coEvery { - v0Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq("cake")) - } returns successResponse - subject.requestRecipeInfo("cake") - coVerifyAll { + + subject.requestRecipeInfo(slug) + + coVerifySequence { authRepo.getAuthHeader() authRepo.invalidateAuthHeader() authRepo.getAuthHeader() } } + @Test + fun `when server version v1 expect requestRecipeInfo to call v1`() = runTest { + val slug = "porridge" + coEvery { + v1Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq(slug)) + } returns PORRIDGE_RECIPE_RESPONSE_V1 + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 + coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL + coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER + + val actual = subject.requestRecipeInfo(slug) + + coVerify { v1Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq(slug)) } + + assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO) + } + + @Test + fun `when server version v1 expect requestRecipes to call v1`() = runTest { + coEvery { + v1Source.requestRecipes(any(), any(), any(), any()) + } returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1) + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 + coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL + coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER + + val actual = subject.requestRecipes(40, 10) + + val page = 5 // 0-9 (1), 10-19 (2), 20-29 (3), 30-39 (4), 40-49 (5) + val perPage = 10 + coVerify { + v1Source.requestRecipes(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq(page), eq(perPage)) + } + + assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V1)) + } + + @Test + fun `when server version v0 expect requestRecipes to call v0`() = runTest { + coEvery { + v0Source.requestRecipes(any(), any(), any(), any()) + } returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0) + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 + coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL + coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER + + val start = 40 + val limit = 10 + val actual = subject.requestRecipes(start, limit) + + coVerify { + v0Source.requestRecipes(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq(start), eq(limit)) + } + + assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V0)) + } + + @Test(expected = IOException::class) + fun `when request fails expect addRecipe to rethrow`() = runTest { + coEvery { v0Source.addRecipe(any(), any(), any()) } throws IOException() + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 + coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL + coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER + subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO) + } + + @Test + fun `when server version v0 expect addRecipe to call v0`() = runTest { + val slug = "porridge" + + coEvery { v0Source.addRecipe(any(), any(), any()) } returns slug + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 + coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL + coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER + + val actual = subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO) + + coVerify { + v0Source.addRecipe( + eq(TEST_BASE_URL), + eq(TEST_AUTH_HEADER), + eq(PORRIDGE_ADD_RECIPE_REQUEST_V0), + ) + } + + assertThat(actual).isEqualTo(slug) + } + + @Test + fun `when server version v1 expect addRecipe to call v1`() = runTest { + val slug = "porridge" + + coEvery { v1Source.createRecipe(any(), any(), any()) } returns slug + coEvery { + v1Source.updateRecipe(any(), any(), any(), any()) + } returns PORRIDGE_RECIPE_RESPONSE_V1 + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 + coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL + coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER + + val actual = subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO) + + coVerifySequence { + v1Source.createRecipe( + eq(TEST_BASE_URL), + eq(TEST_AUTH_HEADER), + eq(PORRIDGE_CREATE_RECIPE_REQUEST_V1), + ) + + v1Source.updateRecipe( + eq(TEST_BASE_URL), + eq(TEST_AUTH_HEADER), + eq(slug), + eq(PORRIDGE_UPDATE_RECIPE_REQUEST_V1), + ) + } + + assertThat(actual).isEqualTo(slug) + } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt index 8dddb8f..0d3db50 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt @@ -6,16 +6,16 @@ import gq.kirmanak.mealient.database.AppDb import gq.kirmanak.mealient.test.HiltRobolectricTest import gq.kirmanak.mealient.test.RecipeImplTestData.BREAD_INGREDIENT import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_BREAD_RECIPE_INGREDIENT_ENTITY +import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_FULL_RECIPE_INFO import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_RECIPE_SUMMARY_ENTITY import gq.kirmanak.mealient.test.RecipeImplTestData.FULL_CAKE_INFO_ENTITY import gq.kirmanak.mealient.test.RecipeImplTestData.FULL_PORRIDGE_INFO_ENTITY -import gq.kirmanak.mealient.test.RecipeImplTestData.GET_CAKE_RESPONSE -import gq.kirmanak.mealient.test.RecipeImplTestData.GET_PORRIDGE_RESPONSE import gq.kirmanak.mealient.test.RecipeImplTestData.MIX_CAKE_RECIPE_INSTRUCTION_ENTITY import gq.kirmanak.mealient.test.RecipeImplTestData.MIX_INSTRUCTION +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_FULL_RECIPE_INFO import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_SUMMARY_ENTITY import gq.kirmanak.mealient.test.RecipeImplTestData.RECIPE_SUMMARY_CAKE -import gq.kirmanak.mealient.test.RecipeImplTestData.RECIPE_SUMMARY_PORRIDGE +import gq.kirmanak.mealient.test.RecipeImplTestData.RECIPE_SUMMARY_PORRIDGE_V0 import gq.kirmanak.mealient.test.RecipeImplTestData.TEST_RECIPE_SUMMARIES import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -61,16 +61,16 @@ class RecipeStorageImplTest : HiltRobolectricTest() { @Test fun `when saveRecipeInfo then saves recipe info`() = runTest { subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE)) - subject.saveRecipeInfo(GET_CAKE_RESPONSE) + subject.saveRecipeInfo(CAKE_FULL_RECIPE_INFO) val actual = appDb.recipeDao().queryFullRecipeInfo("1") assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY) } @Test fun `when saveRecipeInfo with two then saves second`() = runTest { - subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE)) - subject.saveRecipeInfo(GET_CAKE_RESPONSE) - subject.saveRecipeInfo(GET_PORRIDGE_RESPONSE) + subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE_V0)) + subject.saveRecipeInfo(CAKE_FULL_RECIPE_INFO) + subject.saveRecipeInfo(PORRIDGE_FULL_RECIPE_INFO) val actual = appDb.recipeDao().queryFullRecipeInfo("2") assertThat(actual).isEqualTo(FULL_PORRIDGE_INFO_ENTITY) } @@ -78,8 +78,8 @@ class RecipeStorageImplTest : HiltRobolectricTest() { @Test fun `when saveRecipeInfo secondly then overwrites ingredients`() = runTest { subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE)) - subject.saveRecipeInfo(GET_CAKE_RESPONSE) - val newRecipe = GET_CAKE_RESPONSE.copy(recipeIngredients = listOf(BREAD_INGREDIENT)) + subject.saveRecipeInfo(CAKE_FULL_RECIPE_INFO) + val newRecipe = CAKE_FULL_RECIPE_INFO.copy(recipeIngredients = listOf(BREAD_INGREDIENT)) subject.saveRecipeInfo(newRecipe) val actual = appDb.recipeDao().queryFullRecipeInfo("1")?.recipeIngredients val expected = listOf(CAKE_BREAD_RECIPE_INGREDIENT_ENTITY.copy(localId = 3)) @@ -89,8 +89,8 @@ class RecipeStorageImplTest : HiltRobolectricTest() { @Test fun `when saveRecipeInfo secondly then overwrites instructions`() = runTest { subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE)) - subject.saveRecipeInfo(GET_CAKE_RESPONSE) - val newRecipe = GET_CAKE_RESPONSE.copy(recipeInstructions = listOf(MIX_INSTRUCTION)) + subject.saveRecipeInfo(CAKE_FULL_RECIPE_INFO) + val newRecipe = CAKE_FULL_RECIPE_INFO.copy(recipeInstructions = listOf(MIX_INSTRUCTION)) subject.saveRecipeInfo(newRecipe) val actual = appDb.recipeDao().queryFullRecipeInfo("1")?.recipeInstructions val expected = listOf(MIX_CAKE_RECIPE_INSTRUCTION_ENTITY.copy(localId = 3)) diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImplTest.kt index 728bb60..7637c19 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImplTest.kt @@ -3,6 +3,7 @@ package gq.kirmanak.mealient.data.recipes.impl import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.logging.Logger +import gq.kirmanak.mealient.test.FakeLogger import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.impl.annotations.MockK @@ -19,8 +20,7 @@ class RecipeImageUrlProviderImplTest { @MockK lateinit var serverInfoRepo: ServerInfoRepo - @MockK(relaxUnitFun = true) - lateinit var logger: Logger + private val logger: Logger = FakeLogger() @Before fun setUp() { diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoTest.kt similarity index 74% rename from app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImplTest.kt rename to app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoTest.kt index b66b447..468b589 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoTest.kt @@ -7,8 +7,9 @@ import gq.kirmanak.mealient.data.recipes.db.RecipeStorage import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.logging.Logger +import gq.kirmanak.mealient.test.FakeLogger +import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_FULL_RECIPE_INFO import gq.kirmanak.mealient.test.RecipeImplTestData.FULL_CAKE_INFO_ENTITY -import gq.kirmanak.mealient.test.RecipeImplTestData.GET_CAKE_RESPONSE import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify @@ -19,7 +20,7 @@ import org.junit.Before import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) -class RecipeRepoImplTest { +class RecipeRepoTest { @MockK(relaxUnitFun = true) lateinit var storage: RecipeStorage @@ -33,8 +34,7 @@ class RecipeRepoImplTest { @MockK lateinit var pagingSourceFactory: InvalidatingPagingSourceFactory - @MockK(relaxUnitFun = true) - lateinit var logger: Logger + private val logger: Logger = FakeLogger() lateinit var subject: RecipeRepo @@ -45,26 +45,32 @@ class RecipeRepoImplTest { } @Test - fun `when loadRecipeInfo then loads recipe`() = runTest { - coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns GET_CAKE_RESPONSE + fun `when loadRecipeInfo expect return value from data source`() = runTest { + coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns CAKE_FULL_RECIPE_INFO coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY val actual = subject.loadRecipeInfo("1", "cake") assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY) } @Test - fun `when loadRecipeInfo then saves to DB`() = runTest { - coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns GET_CAKE_RESPONSE + fun `when loadRecipeInfo expect call to storage`() = runTest { + coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns CAKE_FULL_RECIPE_INFO coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY subject.loadRecipeInfo("1", "cake") - coVerify { storage.saveRecipeInfo(eq(GET_CAKE_RESPONSE)) } + coVerify { storage.saveRecipeInfo(eq(CAKE_FULL_RECIPE_INFO)) } } @Test - fun `when loadRecipeInfo with error then loads from DB`() = runTest { + fun `when data source fails expect loadRecipeInfo return value from storage`() = runTest { coEvery { dataSource.requestRecipeInfo(eq("cake")) } throws RuntimeException() coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY val actual = subject.loadRecipeInfo("1", "cake") assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY) } + + @Test + fun `when clearLocalData expect call to storage`() = runTest { + subject.clearLocalData() + coVerify { storage.clearAllLocalData() } + } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediatorTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediatorTest.kt index f819e66..0cd0cc0 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediatorTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediatorTest.kt @@ -8,6 +8,7 @@ import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.datasource.NetworkError.Unauthorized import gq.kirmanak.mealient.logging.Logger +import gq.kirmanak.mealient.test.FakeLogger import gq.kirmanak.mealient.test.RecipeImplTestData.TEST_RECIPE_SUMMARIES import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -39,8 +40,7 @@ class RecipesRemoteMediatorTest { @MockK(relaxUnitFun = true) lateinit var pagingSourceFactory: InvalidatingPagingSourceFactory - @MockK(relaxUnitFun = true) - lateinit var logger: Logger + private val logger: Logger = FakeLogger() @Before fun setUp() { diff --git a/app/src/test/java/gq/kirmanak/mealient/extensions/ModelMappingsTest.kt b/app/src/test/java/gq/kirmanak/mealient/extensions/ModelMappingsTest.kt new file mode 100644 index 0000000..9a585e5 --- /dev/null +++ b/app/src/test/java/gq/kirmanak/mealient/extensions/ModelMappingsTest.kt @@ -0,0 +1,145 @@ +package gq.kirmanak.mealient.extensions + +import com.google.common.truth.Truth.assertThat +import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_FULL_RECIPE_INFO +import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_RECIPE_ENTITY +import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY +import gq.kirmanak.mealient.test.RecipeImplTestData.MILK_RECIPE_INGREDIENT_INFO +import gq.kirmanak.mealient.test.RecipeImplTestData.MILK_RECIPE_INGREDIENT_RESPONSE_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.MILK_RECIPE_INGREDIENT_RESPONSE_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.MIX_CAKE_RECIPE_INSTRUCTION_ENTITY +import gq.kirmanak.mealient.test.RecipeImplTestData.MIX_INSTRUCTION +import gq.kirmanak.mealient.test.RecipeImplTestData.MIX_RECIPE_INSTRUCTION_INFO +import gq.kirmanak.mealient.test.RecipeImplTestData.MIX_RECIPE_INSTRUCTION_RESPONSE_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.MIX_RECIPE_INSTRUCTION_RESPONSE_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_ADD_RECIPE_INFO +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_ADD_RECIPE_REQUEST_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_CREATE_RECIPE_REQUEST_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_FULL_RECIPE_INFO +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_DRAFT +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_RESPONSE_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_RESPONSE_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_SUMMARY_ENTITY +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_UPDATE_RECIPE_REQUEST_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.RECIPE_SUMMARY_PORRIDGE_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.RECIPE_SUMMARY_PORRIDGE_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.SUGAR_INGREDIENT +import gq.kirmanak.mealient.test.RecipeImplTestData.VERSION_INFO_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.VERSION_INFO_V1 +import gq.kirmanak.mealient.test.RecipeImplTestData.VERSION_RESPONSE_V0 +import gq.kirmanak.mealient.test.RecipeImplTestData.VERSION_RESPONSE_V1 +import org.junit.Test + +class ModelMappingsTest { + + @Test + fun `when toAddRecipeRequest then fills fields correctly`() { + assertThat(PORRIDGE_RECIPE_DRAFT.toAddRecipeInfo()).isEqualTo(PORRIDGE_ADD_RECIPE_INFO) + } + + @Test + fun `when toDraft then fills fields correctly`() { + assertThat(PORRIDGE_ADD_RECIPE_INFO.toDraft()).isEqualTo(PORRIDGE_RECIPE_DRAFT) + } + + @Test + fun `when full recipe info to entity expect correct entity`() { + assertThat(CAKE_FULL_RECIPE_INFO.toRecipeEntity()).isEqualTo(CAKE_RECIPE_ENTITY) + } + + @Test + fun `when ingredient info to entity expect correct entity`() { + val actual = SUGAR_INGREDIENT.toRecipeIngredientEntity("1") + assertThat(actual).isEqualTo(CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY) + } + + @Test + fun `when instruction info to entity expect correct entity`() { + val actual = MIX_INSTRUCTION.toRecipeInstructionEntity("1") + assertThat(actual).isEqualTo(MIX_CAKE_RECIPE_INSTRUCTION_ENTITY) + } + + @Test + fun `when summary v0 to info expect correct info`() { + val actual = PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0.toRecipeSummaryInfo() + assertThat(actual).isEqualTo(RECIPE_SUMMARY_PORRIDGE_V0) + } + + @Test + fun `when summary v1 to info expect correct info`() { + val actual = PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1.toRecipeSummaryInfo() + assertThat(actual).isEqualTo(RECIPE_SUMMARY_PORRIDGE_V1) + } + + @Test + fun `when summary info to entity expect correct entity`() { + val actual = RECIPE_SUMMARY_PORRIDGE_V0.toRecipeSummaryEntity() + assertThat(actual).isEqualTo(PORRIDGE_RECIPE_SUMMARY_ENTITY) + } + + @Test + fun `when version response v0 to info expect correct info`() { + assertThat(VERSION_RESPONSE_V0.toVersionInfo()).isEqualTo(VERSION_INFO_V0) + } + + @Test + fun `when version response v1 to info expect correct info`() { + assertThat(VERSION_RESPONSE_V1.toVersionInfo()).isEqualTo(VERSION_INFO_V1) + } + + @Test + fun `when recipe ingredient response v0 to info expect correct info`() { + val actual = MILK_RECIPE_INGREDIENT_RESPONSE_V0.toRecipeIngredientInfo() + assertThat(actual).isEqualTo(MILK_RECIPE_INGREDIENT_INFO) + } + + @Test + fun `when recipe ingredient response v1 to info expect correct info`() { + val actual = MILK_RECIPE_INGREDIENT_RESPONSE_V1.toRecipeIngredientInfo() + assertThat(actual).isEqualTo(MILK_RECIPE_INGREDIENT_INFO) + } + + @Test + fun `when recipe instruction response v0 to info expect correct info`() { + val actual = MIX_RECIPE_INSTRUCTION_RESPONSE_V0.toRecipeInstructionInfo() + assertThat(actual).isEqualTo(MIX_RECIPE_INSTRUCTION_INFO) + } + + @Test + fun `when recipe instruction response v1 to info expect correct info`() { + val actual = MIX_RECIPE_INSTRUCTION_RESPONSE_V1.toRecipeInstructionInfo() + assertThat(actual).isEqualTo(MIX_RECIPE_INSTRUCTION_INFO) + } + + @Test + fun `when recipe response v0 to info expect correct info`() { + val actual = PORRIDGE_RECIPE_RESPONSE_V0.toFullRecipeInfo() + assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO) + } + + @Test + fun `when recipe response v1 to info expect correct info`() { + val actual = PORRIDGE_RECIPE_RESPONSE_V1.toFullRecipeInfo() + assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO) + } + + @Test + fun `when add recipe info to request v0 expect correct request`() { + val actual = PORRIDGE_ADD_RECIPE_INFO.toV0Request() + assertThat(actual).isEqualTo(PORRIDGE_ADD_RECIPE_REQUEST_V0) + } + + @Test + fun `when add recipe info to create request v1 expect correct request`() { + val actual = PORRIDGE_ADD_RECIPE_INFO.toV1CreateRequest() + assertThat(actual).isEqualTo(PORRIDGE_CREATE_RECIPE_REQUEST_V1) + } + + @Test + fun `when add recipe info to update request v1 expect correct request`() { + val actual = PORRIDGE_ADD_RECIPE_INFO.toV1UpdateRequest() + assertThat(actual).isEqualTo(PORRIDGE_UPDATE_RECIPE_REQUEST_V1) + } +} \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt b/app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt deleted file mode 100644 index a43e6c9..0000000 --- a/app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt +++ /dev/null @@ -1,78 +0,0 @@ -package gq.kirmanak.mealient.extensions - -import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.data.add.AddRecipeInfo -import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo -import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo -import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo -import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft -import org.junit.Test - -class RemoteToLocalMappingsTest { - - @Test - fun `when toAddRecipeRequest then fills fields correctly`() { - val input = AddRecipeDraft( - recipeName = "Recipe name", - recipeDescription = "Recipe description", - recipeYield = "Recipe yield", - recipeInstructions = listOf("Recipe instruction 1", "Recipe instruction 2"), - recipeIngredients = listOf("Recipe ingredient 1", "Recipe ingredient 2"), - isRecipePublic = false, - areCommentsDisabled = true, - ) - - val expected = AddRecipeInfo( - name = "Recipe name", - description = "Recipe description", - recipeYield = "Recipe yield", - recipeIngredient = listOf( - AddRecipeIngredientInfo(note = "Recipe ingredient 1"), - AddRecipeIngredientInfo(note = "Recipe ingredient 2") - ), - recipeInstructions = listOf( - AddRecipeInstructionInfo(text = "Recipe instruction 1"), - AddRecipeInstructionInfo(text = "Recipe instruction 2") - ), - settings = AddRecipeSettingsInfo( - public = false, - disableComments = true, - ) - ) - - assertThat(input.toAddRecipeInfo()).isEqualTo(expected) - } - - @Test - fun `when toDraft then fills fields correctly`() { - val request = AddRecipeInfo( - name = "Recipe name", - description = "Recipe description", - recipeYield = "Recipe yield", - recipeIngredient = listOf( - AddRecipeIngredientInfo(note = "Recipe ingredient 1"), - AddRecipeIngredientInfo(note = "Recipe ingredient 2") - ), - recipeInstructions = listOf( - AddRecipeInstructionInfo(text = "Recipe instruction 1"), - AddRecipeInstructionInfo(text = "Recipe instruction 2") - ), - settings = AddRecipeSettingsInfo( - public = false, - disableComments = true, - ) - ) - - val expected = AddRecipeDraft( - recipeName = "Recipe name", - recipeDescription = "Recipe description", - recipeYield = "Recipe yield", - recipeInstructions = listOf("Recipe instruction 1", "Recipe instruction 2"), - recipeIngredients = listOf("Recipe ingredient 1", "Recipe ingredient 2"), - isRecipePublic = false, - areCommentsDisabled = true, - ) - - assertThat(request.toDraft()).isEqualTo(expected) - } -} \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt b/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt index cc5c8fc..3e6da0d 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt @@ -9,5 +9,6 @@ object AuthImplTestData { const val TEST_TOKEN = "TEST_TOKEN" const val TEST_AUTH_HEADER = "Bearer TEST_TOKEN" const val TEST_VERSION = "v0.5.6" - val TEST_SERVER_VERSION = ServerVersion.V0 + val TEST_SERVER_VERSION_V0 = ServerVersion.V0 + val TEST_SERVER_VERSION_V1 = ServerVersion.V1 } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/test/FakeLogger.kt b/app/src/test/java/gq/kirmanak/mealient/test/FakeLogger.kt new file mode 100644 index 0000000..1fd425b --- /dev/null +++ b/app/src/test/java/gq/kirmanak/mealient/test/FakeLogger.kt @@ -0,0 +1,34 @@ +package gq.kirmanak.mealient.test + +import gq.kirmanak.mealient.logging.Logger +import gq.kirmanak.mealient.logging.MessageSupplier + +class FakeLogger : Logger { + override fun v(throwable: Throwable?, tag: String?, messageSupplier: MessageSupplier) { + print("V", throwable, messageSupplier) + } + + override fun d(throwable: Throwable?, tag: String?, messageSupplier: MessageSupplier) { + print("D", throwable, messageSupplier) + } + + override fun i(throwable: Throwable?, tag: String?, messageSupplier: MessageSupplier) { + print("I", throwable, messageSupplier) + } + + override fun w(throwable: Throwable?, tag: String?, messageSupplier: MessageSupplier) { + print("W", throwable, messageSupplier) + } + + override fun e(throwable: Throwable?, tag: String?, messageSupplier: MessageSupplier) { + print("E", throwable, messageSupplier) + } + + private fun print( + level: String, + throwable: Throwable?, + messageSupplier: MessageSupplier, + ) { + println("$level ${messageSupplier()}. ${throwable?.stackTraceToString().orEmpty()}") + } +} \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt index e9c5bb3..54cb86f 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt @@ -4,11 +4,15 @@ import gq.kirmanak.mealient.data.add.AddRecipeInfo import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo +import gq.kirmanak.mealient.data.baseurl.VersionInfo import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo import gq.kirmanak.mealient.data.recipes.network.RecipeInstructionInfo import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.database.recipe.entity.* +import gq.kirmanak.mealient.datasource.v0.models.* +import gq.kirmanak.mealient.datasource.v1.models.* +import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime @@ -23,7 +27,7 @@ object RecipeImplTestData { imageId = "cake", ) - val RECIPE_SUMMARY_PORRIDGE = RecipeSummaryInfo( + val RECIPE_SUMMARY_PORRIDGE_V0 = RecipeSummaryInfo( remoteId = "2", name = "Porridge", slug = "porridge", @@ -33,7 +37,17 @@ object RecipeImplTestData { imageId = "porridge", ) - val TEST_RECIPE_SUMMARIES = listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE) + val RECIPE_SUMMARY_PORRIDGE_V1 = RecipeSummaryInfo( + remoteId = "2", + name = "Porridge", + slug = "porridge", + description = "A tasty porridge", + dateAdded = LocalDate.parse("2021-11-12"), + dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), + imageId = "2", + ) + + val TEST_RECIPE_SUMMARIES = listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE_V0) val CAKE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity( remoteId = "1", @@ -55,7 +69,7 @@ object RecipeImplTestData { imageId = "porridge", ) - private val SUGAR_INGREDIENT = RecipeIngredientInfo( + val SUGAR_INGREDIENT = RecipeIngredientInfo( note = "2 oz of white sugar", ) @@ -79,7 +93,7 @@ object RecipeImplTestData { text = "Boil the ingredients" ) - val GET_CAKE_RESPONSE = FullRecipeInfo( + val CAKE_FULL_RECIPE_INFO = FullRecipeInfo( remoteId = "1", name = "Cake", recipeYield = "4 servings", @@ -87,7 +101,7 @@ object RecipeImplTestData { recipeInstructions = listOf(MIX_INSTRUCTION, BAKE_INSTRUCTION) ) - val GET_PORRIDGE_RESPONSE = FullRecipeInfo( + val PORRIDGE_FULL_RECIPE_INFO = FullRecipeInfo( remoteId = "2", name = "Porridge", recipeYield = "3 servings", @@ -96,30 +110,26 @@ object RecipeImplTestData { ) val MIX_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( - localId = 1, recipeId = "1", text = "Mix the ingredients", ) private val BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( - localId = 2, recipeId = "1", text = "Bake the ingredients", ) - private val CAKE_RECIPE_ENTITY = RecipeEntity( + val CAKE_RECIPE_ENTITY = RecipeEntity( remoteId = "1", recipeYield = "4 servings" ) - private val CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( - localId = 1, + val CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( recipeId = "1", note = "2 oz of white sugar", ) val CAKE_BREAD_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( - localId = 2, recipeId = "1", note = "2 oz of white bread", ) @@ -143,25 +153,21 @@ object RecipeImplTestData { ) private val PORRIDGE_MILK_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( - localId = 4, recipeId = "2", note = "2 oz of white milk", ) private val PORRIDGE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( - localId = 3, recipeId = "2", note = "2 oz of white sugar", ) private val PORRIDGE_MIX_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( - localId = 3, recipeId = "2", text = "Mix the ingredients" ) private val PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( - localId = 4, recipeId = "2", text = "Boil the ingredients" ) @@ -179,20 +185,177 @@ object RecipeImplTestData { ) ) + val SUGAR_ADD_RECIPE_INGREDIENT_INFO = AddRecipeIngredientInfo("2 oz of white sugar") + + val MILK_ADD_RECIPE_INGREDIENT_INFO = AddRecipeIngredientInfo("2 oz of white milk") + + val BOIL_ADD_RECIPE_INSTRUCTION_INFO = AddRecipeInstructionInfo("Boil the ingredients") + + val MIX_ADD_RECIPE_INSTRUCTION_INFO = AddRecipeInstructionInfo("Mix the ingredients") + + val ADD_RECIPE_INFO_SETTINGS = AddRecipeSettingsInfo(disableComments = false, public = true) + val PORRIDGE_ADD_RECIPE_INFO = AddRecipeInfo( name = "Porridge", - description = "Tasty breakfast", - recipeYield = "5 servings", + description = "A tasty porridge", + recipeYield = "3 servings", recipeIngredient = listOf( - AddRecipeIngredientInfo("Milk"), - AddRecipeIngredientInfo("Sugar"), - AddRecipeIngredientInfo("Salt"), - AddRecipeIngredientInfo("Porridge"), + MILK_ADD_RECIPE_INGREDIENT_INFO, + SUGAR_ADD_RECIPE_INGREDIENT_INFO, ), recipeInstructions = listOf( - AddRecipeInstructionInfo("Mix"), - AddRecipeInstructionInfo("Cook"), + MIX_ADD_RECIPE_INSTRUCTION_INFO, + BOIL_ADD_RECIPE_INSTRUCTION_INFO, ), - settings = AddRecipeSettingsInfo(disableComments = false, public = true), + settings = ADD_RECIPE_INFO_SETTINGS, + ) + + val PORRIDGE_RECIPE_DRAFT = AddRecipeDraft( + recipeName = "Porridge", + recipeDescription = "A tasty porridge", + recipeYield = "3 servings", + recipeInstructions = listOf("Mix the ingredients", "Boil the ingredients"), + recipeIngredients = listOf("2 oz of white milk", "2 oz of white sugar"), + isRecipePublic = true, + areCommentsDisabled = false, + ) + + val PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0 = GetRecipeSummaryResponseV0( + remoteId = 2, + name = "Porridge", + slug = "porridge", + description = "A tasty porridge", + dateAdded = LocalDate.parse("2021-11-12"), + dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), + ) + + val PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1 = GetRecipeSummaryResponseV1( + remoteId = "2", + name = "Porridge", + slug = "porridge", + description = "A tasty porridge", + dateAdded = LocalDate.parse("2021-11-12"), + dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), + ) + + val VERSION_RESPONSE_V0 = VersionResponseV0("v0.5.6") + + val VERSION_INFO_V0 = VersionInfo("v0.5.6") + + val VERSION_RESPONSE_V1 = VersionResponseV1("v1.0.0-beta05") + + val VERSION_INFO_V1 = VersionInfo("v1.0.0-beta05") + + val MILK_RECIPE_INGREDIENT_RESPONSE_V0 = GetRecipeIngredientResponseV0("2 oz of white milk") + + val SUGAR_RECIPE_INGREDIENT_RESPONSE_V0 = GetRecipeIngredientResponseV0("2 oz of white sugar") + + val MILK_RECIPE_INGREDIENT_RESPONSE_V1 = GetRecipeIngredientResponseV1("2 oz of white milk") + + val SUGAR_RECIPE_INGREDIENT_RESPONSE_V1 = GetRecipeIngredientResponseV1("2 oz of white sugar") + + val MILK_RECIPE_INGREDIENT_INFO = RecipeIngredientInfo("2 oz of white milk") + + val MIX_RECIPE_INSTRUCTION_RESPONSE_V0 = GetRecipeInstructionResponseV0("Mix the ingredients") + + val BOIL_RECIPE_INSTRUCTION_RESPONSE_V0 = GetRecipeInstructionResponseV0("Boil the ingredients") + + val MIX_RECIPE_INSTRUCTION_RESPONSE_V1 = GetRecipeInstructionResponseV1("Mix the ingredients") + + val BOIL_RECIPE_INSTRUCTION_RESPONSE_V1 = GetRecipeInstructionResponseV1("Boil the ingredients") + + val MIX_RECIPE_INSTRUCTION_INFO = RecipeInstructionInfo("Mix the ingredients") + + val PORRIDGE_RECIPE_RESPONSE_V0 = GetRecipeResponseV0( + remoteId = 2, + name = "Porridge", + recipeYield = "3 servings", + recipeIngredients = listOf( + SUGAR_RECIPE_INGREDIENT_RESPONSE_V0, + MILK_RECIPE_INGREDIENT_RESPONSE_V0, + ), + recipeInstructions = listOf( + MIX_RECIPE_INSTRUCTION_RESPONSE_V0, + BOIL_RECIPE_INSTRUCTION_RESPONSE_V0 + ), + ) + + val PORRIDGE_RECIPE_RESPONSE_V1 = GetRecipeResponseV1( + remoteId = "2", + name = "Porridge", + recipeYield = "3 servings", + recipeIngredients = listOf( + SUGAR_RECIPE_INGREDIENT_RESPONSE_V1, + MILK_RECIPE_INGREDIENT_RESPONSE_V1, + ), + recipeInstructions = listOf( + MIX_RECIPE_INSTRUCTION_RESPONSE_V1, + BOIL_RECIPE_INSTRUCTION_RESPONSE_V1 + ), + ) + + val MIX_ADD_RECIPE_INSTRUCTION_REQUEST_V0 = AddRecipeInstructionV0("Mix the ingredients") + + val BOIL_ADD_RECIPE_INSTRUCTION_REQUEST_V0 = AddRecipeInstructionV0("Boil the ingredients") + + val SUGAR_ADD_RECIPE_INGREDIENT_REQUEST_V0 = AddRecipeIngredientV0("2 oz of white sugar") + + val MILK_ADD_RECIPE_INGREDIENT_REQUEST_V0 = AddRecipeIngredientV0("2 oz of white milk") + + val ADD_RECIPE_REQUEST_SETTINGS_V0 = AddRecipeSettingsV0(disableComments = false, public = true) + + val PORRIDGE_ADD_RECIPE_REQUEST_V0 = AddRecipeRequestV0( + name = "Porridge", + description = "A tasty porridge", + recipeYield = "3 servings", + recipeInstructions = listOf( + MIX_ADD_RECIPE_INSTRUCTION_REQUEST_V0, + BOIL_ADD_RECIPE_INSTRUCTION_REQUEST_V0, + ), + recipeIngredient = listOf( + MILK_ADD_RECIPE_INGREDIENT_REQUEST_V0, + SUGAR_ADD_RECIPE_INGREDIENT_REQUEST_V0, + ), + settings = ADD_RECIPE_REQUEST_SETTINGS_V0 + ) + + val MIX_ADD_RECIPE_INSTRUCTION_REQUEST_V1 = AddRecipeInstructionV1( + id = "1", + text = "Mix the ingredients", + ingredientReferences = emptyList() + ) + + val BOIL_ADD_RECIPE_INSTRUCTION_REQUEST_V1 = AddRecipeInstructionV1( + id = "2", + text = "Boil the ingredients", + ingredientReferences = emptyList() + ) + + val SUGAR_ADD_RECIPE_INGREDIENT_REQUEST_V1 = AddRecipeIngredientV1( + id = "3", + note = "2 oz of white sugar" + ) + + val MILK_ADD_RECIPE_INGREDIENT_REQUEST_V1 = AddRecipeIngredientV1( + id = "4", + note = "2 oz of white milk" + ) + + val ADD_RECIPE_REQUEST_SETTINGS_V1 = AddRecipeSettingsV1(disableComments = false, public = true) + + val PORRIDGE_CREATE_RECIPE_REQUEST_V1 = CreateRecipeRequestV1(name = "Porridge") + + val PORRIDGE_UPDATE_RECIPE_REQUEST_V1 = UpdateRecipeRequestV1( + description = "A tasty porridge", + recipeYield = "3 servings", + recipeInstructions = listOf( + MIX_ADD_RECIPE_INSTRUCTION_REQUEST_V1, + BOIL_ADD_RECIPE_INSTRUCTION_REQUEST_V1, + ), + recipeIngredient = listOf( + MILK_ADD_RECIPE_INGREDIENT_REQUEST_V1, + SUGAR_ADD_RECIPE_INGREDIENT_REQUEST_V1, + ), + settings = ADD_RECIPE_REQUEST_SETTINGS_V1 ) } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt b/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt index d3efb46..130e693 100644 --- a/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt @@ -3,6 +3,7 @@ package gq.kirmanak.mealient.ui.add import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.add.AddRecipeRepo import gq.kirmanak.mealient.logging.Logger +import gq.kirmanak.mealient.test.FakeLogger import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_ADD_RECIPE_INFO import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -28,8 +29,7 @@ class AddRecipeViewModelTest { @MockK(relaxUnitFun = true) lateinit var addRecipeRepo: AddRecipeRepo - @MockK(relaxUnitFun = true) - lateinit var logger: Logger + private val logger: Logger = FakeLogger() lateinit var subject: AddRecipeViewModel diff --git a/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt b/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt index 6cee162..73048b0 100644 --- a/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt @@ -6,6 +6,7 @@ import gq.kirmanak.mealient.data.baseurl.VersionInfo import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL import gq.kirmanak.mealient.test.AuthImplTestData.TEST_VERSION +import gq.kirmanak.mealient.test.FakeLogger import gq.kirmanak.mealient.test.RobolectricTest import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -26,8 +27,7 @@ class BaseURLViewModelTest : RobolectricTest() { @MockK lateinit var versionDataSource: VersionDataSource - @MockK(relaxUnitFun = true) - lateinit var logger: Logger + private val logger: Logger = FakeLogger() lateinit var subject: BaseURLViewModel diff --git a/app/src/test/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerViewModelTest.kt b/app/src/test/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerViewModelTest.kt index e186f93..ee950df 100644 --- a/app/src/test/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerViewModelTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerViewModelTest.kt @@ -3,6 +3,7 @@ package gq.kirmanak.mealient.ui.disclaimer import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage import gq.kirmanak.mealient.logging.Logger +import gq.kirmanak.mealient.test.FakeLogger import io.mockk.MockKAnnotations import io.mockk.impl.annotations.MockK import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -18,8 +19,7 @@ class DisclaimerViewModelTest { @MockK(relaxUnitFun = true) lateinit var storage: DisclaimerStorage - @MockK(relaxUnitFun = true) - lateinit var logger: Logger + private val logger: Logger = FakeLogger() lateinit var subject: DisclaimerViewModel diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeIngredientEntity.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeIngredientEntity.kt index 31b7b6a..4fcf844 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeIngredientEntity.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeIngredientEntity.kt @@ -9,4 +9,22 @@ data class RecipeIngredientEntity( @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0, @ColumnInfo(name = "recipe_id") val recipeId: String, @ColumnInfo(name = "note") val note: String, -) \ No newline at end of file +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RecipeIngredientEntity + + if (recipeId != other.recipeId) return false + if (note != other.note) return false + + return true + } + + override fun hashCode(): Int { + var result = recipeId.hashCode() + result = 31 * result + note.hashCode() + return result + } +} \ No newline at end of file diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeInstructionEntity.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeInstructionEntity.kt index c5905fa..eb4ab61 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeInstructionEntity.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeInstructionEntity.kt @@ -9,4 +9,22 @@ data class RecipeInstructionEntity( @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0, @ColumnInfo(name = "recipe_id") val recipeId: String, @ColumnInfo(name = "text") val text: String, -) +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RecipeInstructionEntity + + if (recipeId != other.recipeId) return false + if (text != other.text) return false + + return true + } + + override fun hashCode(): Int { + var result = recipeId.hashCode() + result = 31 * result + text.hashCode() + return result + } +} diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt index 3df266b..4e04b07 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt @@ -16,14 +16,47 @@ data class UpdateRecipeRequestV1( data class AddRecipeIngredientV1( @SerialName("referenceId") val id: String, @SerialName("note") val note: String, -) +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AddRecipeIngredientV1 + + if (note != other.note) return false + + return true + } + + override fun hashCode(): Int { + return note.hashCode() + } +} @Serializable data class AddRecipeInstructionV1( @SerialName("id") val id: String, @SerialName("text") val text: String = "", @SerialName("ingredientReferences") val ingredientReferences: List, -) +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AddRecipeInstructionV1 + + if (text != other.text) return false + if (ingredientReferences != other.ingredientReferences) return false + + return true + } + + override fun hashCode(): Int { + var result = text.hashCode() + result = 31 * result + ingredientReferences.hashCode() + return result + } +} @Serializable data class AddRecipeSettingsV1(