Add unit tests

This commit is contained in:
Kirill Kamakin
2022-10-31 19:41:49 +01:00
parent 98e082b95e
commit 7c02df4d30
27 changed files with 903 additions and 180 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -42,7 +42,8 @@ android {
buildTypes {
getByName("debug") {
ext["enableCrashlytics"] = false
isTestCoverageEnabled = true
enableUnitTestCoverage = true
enableAndroidTestCoverage = true
}
getByName("release") {
isMinifyEnabled = true

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -53,7 +53,7 @@ fun GetRecipeSummaryResponseV1.toRecipeSummaryInfo() = RecipeSummaryInfo(
imageId = remoteId,
)
fun RecipeSummaryInfo.recipeEntity() = RecipeSummaryEntity(
fun RecipeSummaryInfo.toRecipeSummaryEntity() = RecipeSummaryEntity(
remoteId = remoteId,
name = name,
slug = slug,

View File

@@ -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)) }
}
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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))) }
}
}

View File

@@ -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<GetRecipeResponseV0>(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)
}
}

View File

@@ -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))

View File

@@ -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() {

View File

@@ -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<Int, RecipeSummaryEntity>
@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() }
}
}

View File

@@ -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<Int, RecipeSummaryEntity>
@MockK(relaxUnitFun = true)
lateinit var logger: Logger
private val logger: Logger = FakeLogger()
@Before
fun setUp() {

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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()}")
}
}

View File

@@ -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
)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,
)
) {
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
}
}

View File

@@ -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
}
}

View File

@@ -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<String>,
)
) {
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(