Replace "Mealie" with "Mealient" everywhere

This commit is contained in:
Kirill Kamakin
2021-11-20 13:41:47 +03:00
parent d789bfcf97
commit 5866584d14
81 changed files with 283 additions and 284 deletions

View File

@@ -0,0 +1,55 @@
package gq.kirmanak.mealient.data.auth.impl
import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest
import gq.kirmanak.mealient.data.test.AuthImplTestData.TEST_PASSWORD
import gq.kirmanak.mealient.data.test.AuthImplTestData.TEST_TOKEN
import gq.kirmanak.mealient.data.test.AuthImplTestData.TEST_USERNAME
import gq.kirmanak.mealient.data.test.AuthImplTestData.body
import gq.kirmanak.mealient.data.test.AuthImplTestData.enqueueSuccessfulAuthResponse
import gq.kirmanak.mealient.data.test.AuthImplTestData.enqueueUnsuccessfulAuthResponse
import gq.kirmanak.mealient.data.test.MockServerTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.ExperimentalSerializationApi
import org.junit.Test
import retrofit2.HttpException
import javax.inject.Inject
@ExperimentalSerializationApi
@ExperimentalCoroutinesApi
@HiltAndroidTest
class AuthDataSourceImplTest : MockServerTest() {
@Inject
lateinit var subject: AuthDataSourceImpl
@Test
fun `when authentication is successful then token is correct`() = runBlocking {
mockServer.enqueueSuccessfulAuthResponse()
val token = subject.authenticate(TEST_USERNAME, TEST_PASSWORD, serverUrl)
assertThat(token).isEqualTo(TEST_TOKEN)
}
@Test(expected = HttpException::class)
fun `when authentication isn't successful then throws`(): Unit = runBlocking {
mockServer.enqueueUnsuccessfulAuthResponse()
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, serverUrl)
}
@Test
fun `when authentication is requested then body is correct`() = runBlocking {
mockServer.enqueueSuccessfulAuthResponse()
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, serverUrl)
val body = mockServer.takeRequest().body()
assertThat(body).isEqualTo("username=$TEST_USERNAME&password=$TEST_PASSWORD")
}
@Test
fun `when authentication is requested then path is correct`() = runBlocking {
mockServer.enqueueSuccessfulAuthResponse()
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, serverUrl)
val path = mockServer.takeRequest().path
assertThat(path).isEqualTo("/api/auth/token")
}
}

View File

@@ -0,0 +1,53 @@
package gq.kirmanak.mealient.data.auth.impl
import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest
import gq.kirmanak.mealient.data.test.AuthImplTestData.TEST_PASSWORD
import gq.kirmanak.mealient.data.test.AuthImplTestData.TEST_TOKEN
import gq.kirmanak.mealient.data.test.AuthImplTestData.TEST_USERNAME
import gq.kirmanak.mealient.data.test.AuthImplTestData.enqueueSuccessfulAuthResponse
import gq.kirmanak.mealient.data.test.AuthImplTestData.enqueueUnsuccessfulAuthResponse
import gq.kirmanak.mealient.data.test.MockServerTest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Test
import retrofit2.HttpException
import javax.inject.Inject
@HiltAndroidTest
class AuthRepoImplTest : MockServerTest() {
@Inject
lateinit var subject: AuthRepoImpl
@Test
fun `when not authenticated then first auth status is false`() = runBlocking {
assertThat(subject.authenticationStatuses().first()).isFalse()
}
@Test
fun `when authenticated then first auth status is true`() = runBlocking {
mockServer.enqueueSuccessfulAuthResponse()
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, serverUrl)
assertThat(subject.authenticationStatuses().first()).isTrue()
}
@Test(expected = HttpException::class)
fun `when authentication fails then authenticate throws`() = runBlocking {
mockServer.enqueueUnsuccessfulAuthResponse()
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, serverUrl)
}
@Test
fun `when authenticated then getToken returns token`() = runBlocking {
mockServer.enqueueSuccessfulAuthResponse()
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, serverUrl)
assertThat(subject.getToken()).isEqualTo(TEST_TOKEN)
}
@Test
fun `when authenticated then getBaseUrl returns url`() = runBlocking {
mockServer.enqueueSuccessfulAuthResponse()
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, serverUrl)
assertThat(subject.getBaseUrl()).isEqualTo(serverUrl)
}
}

View File

@@ -0,0 +1,78 @@
package gq.kirmanak.mealient.data.auth.impl
import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest
import gq.kirmanak.mealient.data.test.AuthImplTestData.TEST_TOKEN
import gq.kirmanak.mealient.data.test.AuthImplTestData.TEST_URL
import gq.kirmanak.mealient.data.test.HiltRobolectricTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Test
import javax.inject.Inject
@ExperimentalCoroutinesApi
@HiltAndroidTest
class AuthStorageImplTest : HiltRobolectricTest() {
@Inject
lateinit var subject: AuthStorageImpl
@Test
fun `when storing auth data then doesn't throw`() = runBlocking {
subject.storeAuthData(TEST_TOKEN, TEST_URL)
}
@Test
fun `when reading url after storing data then returns url`() = runBlocking {
subject.storeAuthData(TEST_TOKEN, TEST_URL)
assertThat(subject.getBaseUrl()).isEqualTo(TEST_URL)
}
@Test
fun `when reading token after storing data then returns token`() = runBlocking {
subject.storeAuthData(TEST_TOKEN, TEST_URL)
assertThat(subject.getToken()).isEqualTo(TEST_TOKEN)
}
@Test
fun `when reading token without storing data then returns null`() = runBlocking {
assertThat(subject.getToken()).isNull()
}
@Test
fun `when reading url without storing data then returns null`() = runBlocking {
assertThat(subject.getBaseUrl()).isNull()
}
@Test
fun `when didn't store auth data then first token is null`() = runBlocking {
assertThat(subject.tokenObservable().first()).isNull()
}
@Test
fun `when stored auth data then first token is correct`() = runBlocking {
subject.storeAuthData(TEST_TOKEN, TEST_URL)
assertThat(subject.tokenObservable().first()).isEqualTo(TEST_TOKEN)
}
@Test
fun `when clearAuthData then first token is null`() = runBlocking {
subject.storeAuthData(TEST_TOKEN, TEST_URL)
subject.clearAuthData()
assertThat(subject.tokenObservable().first()).isNull()
}
@Test
fun `when clearAuthData then getToken returns null`() = runBlocking {
subject.storeAuthData(TEST_TOKEN, TEST_URL)
subject.clearAuthData()
assertThat(subject.getToken()).isNull()
}
@Test
fun `when clearAuthData then getBaseUrl returns null`() = runBlocking {
subject.storeAuthData(TEST_TOKEN, TEST_URL)
subject.clearAuthData()
assertThat(subject.getBaseUrl()).isNull()
}
}

View File

@@ -0,0 +1,40 @@
package gq.kirmanak.mealient.data.impl
import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest
import gq.kirmanak.mealient.data.auth.impl.AUTHORIZATION_HEADER
import gq.kirmanak.mealient.data.test.AuthImplTestData.TEST_TOKEN
import gq.kirmanak.mealient.data.test.MockServerTest
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.mockwebserver.MockResponse
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class OkHttpBuilderTest : MockServerTest() {
@Inject
lateinit var subject: OkHttpBuilder
@Test
fun `when token null then no auth header`() {
val client = subject.buildOkHttp(null)
val header = sendRequestAndExtractAuthHeader(client)
assertThat(header).isNull()
}
@Test
fun `when token isn't null then auth header contains token`() {
val client = subject.buildOkHttp(TEST_TOKEN)
val header = sendRequestAndExtractAuthHeader(client)
assertThat(header).isEqualTo("Bearer $TEST_TOKEN")
}
private fun sendRequestAndExtractAuthHeader(client: OkHttpClient): String? {
mockServer.enqueue(MockResponse())
val request = Request.Builder().url(serverUrl).get().build()
client.newCall(request).execute()
return mockServer.takeRequest().getHeader(AUTHORIZATION_HEADER)
}
}

View File

@@ -0,0 +1,36 @@
package gq.kirmanak.mealient.data.impl
import com.google.common.truth.Truth.assertThat
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import org.junit.Test
class RoomTypeConvertersTest {
@Test
fun `when localDateTimeToTimestamp then correctly converts`() {
val input = LocalDateTime.parse("2021-11-13T15:56:33")
val actual = RoomTypeConverters.localDateTimeToTimestamp(input)
assertThat(actual).isEqualTo(1636818993000)
}
@Test
fun `when timestampToLocalDateTime then correctly converts`() {
val expected = LocalDateTime.parse("2021-11-13T15:58:38")
val actual = RoomTypeConverters.timestampToLocalDateTime(1636819118000)
assertThat(actual).isEqualTo(expected)
}
@Test
fun `when localDateToTimeStamp then correctly converts`() {
val input = LocalDate.parse("2021-11-13")
val actual = RoomTypeConverters.localDateToTimeStamp(input)
assertThat(actual).isEqualTo(1636761600000)
}
@Test
fun `when timestampToLocalDate then correctly converts`() {
val expected = LocalDate.parse("2021-11-13")
val actual = RoomTypeConverters.timestampToLocalDate(1636761600000)
assertThat(actual).isEqualTo(expected)
}
}

View File

@@ -0,0 +1,185 @@
package gq.kirmanak.mealient.data.recipes.db
import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest
import gq.kirmanak.mealient.data.AppDb
import gq.kirmanak.mealient.data.recipes.db.entity.CategoryEntity
import gq.kirmanak.mealient.data.recipes.db.entity.CategoryRecipeEntity
import gq.kirmanak.mealient.data.recipes.db.entity.TagEntity
import gq.kirmanak.mealient.data.recipes.db.entity.TagRecipeEntity
import gq.kirmanak.mealient.data.test.HiltRobolectricTest
import gq.kirmanak.mealient.data.test.RecipeImplTestData.BREAD_INGREDIENT
import gq.kirmanak.mealient.data.test.RecipeImplTestData.CAKE_BREAD_RECIPE_INGREDIENT_ENTITY
import gq.kirmanak.mealient.data.test.RecipeImplTestData.CAKE_RECIPE_SUMMARY_ENTITY
import gq.kirmanak.mealient.data.test.RecipeImplTestData.FULL_CAKE_INFO_ENTITY
import gq.kirmanak.mealient.data.test.RecipeImplTestData.FULL_PORRIDGE_INFO_ENTITY
import gq.kirmanak.mealient.data.test.RecipeImplTestData.GET_CAKE_RESPONSE
import gq.kirmanak.mealient.data.test.RecipeImplTestData.GET_PORRIDGE_RESPONSE
import gq.kirmanak.mealient.data.test.RecipeImplTestData.MIX_CAKE_RECIPE_INSTRUCTION_ENTITY
import gq.kirmanak.mealient.data.test.RecipeImplTestData.MIX_INSTRUCTION
import gq.kirmanak.mealient.data.test.RecipeImplTestData.PORRIDGE_RECIPE_SUMMARY_ENTITY
import gq.kirmanak.mealient.data.test.RecipeImplTestData.RECIPE_SUMMARY_CAKE
import gq.kirmanak.mealient.data.test.RecipeImplTestData.RECIPE_SUMMARY_PORRIDGE
import gq.kirmanak.mealient.data.test.RecipeImplTestData.TEST_RECIPE_SUMMARIES
import kotlinx.coroutines.runBlocking
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class RecipeStorageImplTest : HiltRobolectricTest() {
@Inject
lateinit var subject: RecipeStorageImpl
@Inject
lateinit var appDb: AppDb
@Test
fun `when saveRecipes then saves tags`(): Unit = runBlocking {
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
val actualTags = appDb.recipeDao().queryAllTags()
assertThat(actualTags).containsExactly(
TagEntity(localId = 1, name = "gluten"),
TagEntity(localId = 2, name = "allergic"),
TagEntity(localId = 3, name = "milk")
)
}
@Test
fun `when saveRecipes then saves categories`(): Unit = runBlocking {
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
val actual = appDb.recipeDao().queryAllCategories()
assertThat(actual).containsExactly(
CategoryEntity(localId = 1, name = "dessert"),
CategoryEntity(localId = 2, name = "tasty"),
CategoryEntity(localId = 3, name = "porridge")
)
}
@Test
fun `when saveRecipes then saves recipes`(): Unit = runBlocking {
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
val actualTags = appDb.recipeDao().queryAllRecipes()
assertThat(actualTags).containsExactly(
CAKE_RECIPE_SUMMARY_ENTITY,
PORRIDGE_RECIPE_SUMMARY_ENTITY
)
}
@Test
fun `when saveRecipes then saves category recipes`(): Unit = runBlocking {
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
val actual = appDb.recipeDao().queryAllCategoryRecipes()
assertThat(actual).containsExactly(
CategoryRecipeEntity(categoryId = 1, recipeId = 1),
CategoryRecipeEntity(categoryId = 2, recipeId = 1),
CategoryRecipeEntity(categoryId = 3, recipeId = 2),
CategoryRecipeEntity(categoryId = 2, recipeId = 2)
)
}
@Test
fun `when saveRecipes then saves tag recipes`(): Unit = runBlocking {
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
val actual = appDb.recipeDao().queryAllTagRecipes()
assertThat(actual).containsExactly(
TagRecipeEntity(tagId = 1, recipeId = 1),
TagRecipeEntity(tagId = 2, recipeId = 1),
TagRecipeEntity(tagId = 3, recipeId = 2),
TagRecipeEntity(tagId = 1, recipeId = 2),
)
}
@Test
fun `when refreshAll then old recipes aren't preserved`(): Unit = runBlocking {
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE))
val actual = appDb.recipeDao().queryAllRecipes()
assertThat(actual).containsExactly(CAKE_RECIPE_SUMMARY_ENTITY)
}
@Test
fun `when refreshAll then old category recipes aren't preserved`(): Unit = runBlocking {
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE))
val actual = appDb.recipeDao().queryAllCategoryRecipes()
assertThat(actual).containsExactly(
CategoryRecipeEntity(categoryId = 1, recipeId = 1),
CategoryRecipeEntity(categoryId = 2, recipeId = 1),
)
}
@Test
fun `when refreshAll then old tag recipes aren't preserved`(): Unit = runBlocking {
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE))
val actual = appDb.recipeDao().queryAllTagRecipes()
assertThat(actual).containsExactly(
TagRecipeEntity(tagId = 1, recipeId = 1),
TagRecipeEntity(tagId = 2, recipeId = 1),
)
}
@Test
fun `when clearAllLocalData then recipes aren't preserved`(): Unit = runBlocking {
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
subject.clearAllLocalData()
val actual = appDb.recipeDao().queryAllRecipes()
assertThat(actual).isEmpty()
}
@Test
fun `when clearAllLocalData then categories aren't preserved`(): Unit = runBlocking {
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
subject.clearAllLocalData()
val actual = appDb.recipeDao().queryAllCategories()
assertThat(actual).isEmpty()
}
@Test
fun `when clearAllLocalData then tags aren't preserved`(): Unit = runBlocking {
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
subject.clearAllLocalData()
val actual = appDb.recipeDao().queryAllTags()
assertThat(actual).isEmpty()
}
@Test
fun `when saveRecipeInfo then saves recipe info`(): Unit = runBlocking {
subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE))
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
val actual = appDb.recipeDao().queryFullRecipeInfo(1)
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
}
@Test
fun `when saveRecipeInfo with two then saves second`(): Unit = runBlocking {
subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE))
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
subject.saveRecipeInfo(GET_PORRIDGE_RESPONSE)
val actual = appDb.recipeDao().queryFullRecipeInfo(2)
assertThat(actual).isEqualTo(FULL_PORRIDGE_INFO_ENTITY)
}
@Test
fun `when saveRecipeInfo secondly then overwrites ingredients`(): Unit = runBlocking {
subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE))
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
val newRecipe = GET_CAKE_RESPONSE.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))
assertThat(actual).isEqualTo(expected)
}
@Test
fun `when saveRecipeInfo secondly then overwrites instructions`(): Unit = runBlocking {
subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE))
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
val newRecipe = GET_CAKE_RESPONSE.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))
assertThat(actual).isEqualTo(expected)
}
}

View File

@@ -0,0 +1,54 @@
package gq.kirmanak.mealient.data.recipes.impl
import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest
import gq.kirmanak.mealient.data.AppDb
import gq.kirmanak.mealient.data.recipes.RecipeRepo
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
import gq.kirmanak.mealient.data.test.MockServerWithAuthTest
import gq.kirmanak.mealient.data.test.RecipeImplTestData.FULL_CAKE_INFO_ENTITY
import gq.kirmanak.mealient.data.test.RecipeImplTestData.RECIPE_SUMMARY_CAKE
import gq.kirmanak.mealient.data.test.RecipeImplTestData.enqueueSuccessfulGetRecipe
import gq.kirmanak.mealient.data.test.RecipeImplTestData.enqueueUnsuccessfulRecipeResponse
import kotlinx.coroutines.runBlocking
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class RecipeRepoImplTest : MockServerWithAuthTest() {
@Inject
lateinit var subject: RecipeRepo
@Inject
lateinit var storage: RecipeStorage
@Inject
lateinit var appDb: AppDb
@Test
fun `when loadRecipeInfo then loads recipe`(): Unit = runBlocking {
storage.saveRecipes(listOf(RECIPE_SUMMARY_CAKE))
mockServer.enqueueSuccessfulGetRecipe()
val actual = subject.loadRecipeInfo(1, "cake")
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
}
@Test
fun `when loadRecipeInfo then saves to DB`(): Unit = runBlocking {
storage.saveRecipes(listOf(RECIPE_SUMMARY_CAKE))
mockServer.enqueueSuccessfulGetRecipe()
subject.loadRecipeInfo(1, "cake")
val actual = appDb.recipeDao().queryFullRecipeInfo(1)
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
}
@Test
fun `when loadRecipeInfo with error then loads from DB`(): Unit = runBlocking {
storage.saveRecipes(listOf(RECIPE_SUMMARY_CAKE))
mockServer.enqueueSuccessfulGetRecipe()
subject.loadRecipeInfo(1, "cake")
mockServer.enqueueUnsuccessfulRecipeResponse()
val actual = subject.loadRecipeInfo(1, "cake")
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
}
}

View File

@@ -0,0 +1,126 @@
package gq.kirmanak.mealient.data.recipes.impl
import androidx.paging.*
import androidx.paging.LoadType.*
import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest
import gq.kirmanak.mealient.data.AppDb
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
import gq.kirmanak.mealient.data.test.MockServerWithAuthTest
import gq.kirmanak.mealient.data.test.RecipeImplTestData.CAKE_RECIPE_SUMMARY_ENTITY
import gq.kirmanak.mealient.data.test.RecipeImplTestData.PORRIDGE_RECIPE_SUMMARY_ENTITY
import gq.kirmanak.mealient.data.test.RecipeImplTestData.TEST_RECIPE_ENTITIES
import gq.kirmanak.mealient.data.test.RecipeImplTestData.enqueueSuccessfulRecipeSummaryResponse
import gq.kirmanak.mealient.data.test.RecipeImplTestData.enqueueUnsuccessfulRecipeResponse
import kotlinx.coroutines.runBlocking
import org.junit.Test
import javax.inject.Inject
@ExperimentalPagingApi
@HiltAndroidTest
class RecipesRemoteMediatorTest : MockServerWithAuthTest() {
private val pagingConfig = PagingConfig(
pageSize = 2,
prefetchDistance = 5,
enablePlaceholders = false
)
@Inject
lateinit var subject: RecipesRemoteMediator
@Inject
lateinit var appDb: AppDb
@Test
fun `when first load with refresh successful then result success`(): Unit = runBlocking {
mockServer.enqueueSuccessfulRecipeSummaryResponse()
val result = subject.load(REFRESH, pagingState())
assertThat(result).isInstanceOf(RemoteMediator.MediatorResult.Success::class.java)
}
@Test
fun `when first load with refresh successful then recipes stored`(): Unit = runBlocking {
mockServer.enqueueSuccessfulRecipeSummaryResponse()
subject.load(REFRESH, pagingState())
val actual = appDb.recipeDao().queryAllRecipes()
assertThat(actual).containsExactly(
CAKE_RECIPE_SUMMARY_ENTITY,
PORRIDGE_RECIPE_SUMMARY_ENTITY
)
}
@Test
fun `when load state prepend then success`(): Unit = runBlocking {
val result = subject.load(PREPEND, pagingState())
assertThat(result).isInstanceOf(RemoteMediator.MediatorResult.Success::class.java)
}
@Test
fun `when load state prepend then end is reached`(): Unit = runBlocking {
val result = subject.load(PREPEND, pagingState())
assertThat((result as RemoteMediator.MediatorResult.Success).endOfPaginationReached).isTrue()
}
@Test
fun `when load successful then lastRequestEnd updated`(): Unit = runBlocking {
mockServer.enqueueSuccessfulRecipeSummaryResponse()
subject.load(REFRESH, pagingState())
val actual = subject.lastRequestEnd
assertThat(actual).isEqualTo(2)
}
@Test
fun `when load fails then lastRequestEnd still 0`(): Unit = runBlocking {
mockServer.enqueueUnsuccessfulRecipeResponse()
subject.load(REFRESH, pagingState())
val actual = subject.lastRequestEnd
assertThat(actual).isEqualTo(0)
}
@Test
fun `when load fails then result is error`(): Unit = runBlocking {
mockServer.enqueueUnsuccessfulRecipeResponse()
val actual = subject.load(REFRESH, pagingState())
assertThat(actual).isInstanceOf(RemoteMediator.MediatorResult.Error::class.java)
}
@Test
fun `when refresh then request params correct`(): Unit = runBlocking {
mockServer.enqueueUnsuccessfulRecipeResponse()
subject.load(REFRESH, pagingState())
val actual = mockServer.takeRequest().path
assertThat(actual).isEqualTo("/api/recipes/summary?start=0&limit=6")
}
@Test
fun `when append then request params correct`(): Unit = runBlocking {
mockServer.enqueueSuccessfulRecipeSummaryResponse()
subject.load(REFRESH, pagingState())
mockServer.takeRequest()
mockServer.enqueueSuccessfulRecipeSummaryResponse()
subject.load(APPEND, pagingState())
val actual = mockServer.takeRequest().path
assertThat(actual).isEqualTo("/api/recipes/summary?start=2&limit=2")
}
@Test
fun `when append fails then recipes aren't removed`(): Unit = runBlocking {
mockServer.enqueueSuccessfulRecipeSummaryResponse()
subject.load(REFRESH, pagingState())
mockServer.takeRequest()
mockServer.enqueueUnsuccessfulRecipeResponse()
subject.load(APPEND, pagingState())
val actual = appDb.recipeDao().queryAllRecipes()
assertThat(actual).isEqualTo(TEST_RECIPE_ENTITIES)
}
private fun pagingState(
pages: List<PagingSource.LoadResult.Page<Int, RecipeSummaryEntity>> = emptyList(),
anchorPosition: Int? = null
): PagingState<Int, RecipeSummaryEntity> = PagingState(
pages = pages,
anchorPosition = anchorPosition,
config = pagingConfig,
leadingPlaceholderCount = 0
)
}

View File

@@ -0,0 +1,35 @@
package gq.kirmanak.mealient.data.test
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import java.nio.charset.Charset
object AuthImplTestData {
const val TEST_USERNAME = "TEST_USERNAME"
const val TEST_PASSWORD = "TEST_PASSWORD"
const val TEST_TOKEN = "TEST_TOKEN"
const val SUCCESSFUL_AUTH_RESPONSE =
"{\"access_token\":\"$TEST_TOKEN\",\"token_type\":\"TEST_TOKEN_TYPE\"}"
const val UNSUCCESSFUL_AUTH_RESPONSE =
"{\"detail\":\"Unauthorized\"}"
const val TEST_URL = "TEST_URL"
fun RecordedRequest.body() = body.readString(Charset.defaultCharset())
fun MockWebServer.enqueueUnsuccessfulAuthResponse() {
val response = MockResponse()
.setBody(UNSUCCESSFUL_AUTH_RESPONSE)
.setHeader("Content-Type", "application/json")
.setResponseCode(401)
enqueue(response)
}
fun MockWebServer.enqueueSuccessfulAuthResponse() {
val response = MockResponse()
.setBody(SUCCESSFUL_AUTH_RESPONSE)
.setHeader("Content-Type", "application/json")
.setResponseCode(200)
enqueue(response)
}
}

View File

@@ -0,0 +1,36 @@
package gq.kirmanak.mealient.data.test
import androidx.test.ext.junit.runners.AndroidJUnit4
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltTestApplication
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import timber.log.Timber
@RunWith(AndroidJUnit4::class)
@Config(application = HiltTestApplication::class, manifest = Config.NONE)
abstract class HiltRobolectricTest {
companion object {
@BeforeClass
@JvmStatic
fun setupTimber() {
Timber.plant(object : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
println(message)
t?.printStackTrace()
}
})
}
}
@get:Rule
var hiltRule = HiltAndroidRule(this)
@Before
fun inject() {
hiltRule.inject()
}
}

View File

@@ -0,0 +1,23 @@
package gq.kirmanak.mealient.data.test
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
abstract class MockServerTest : HiltRobolectricTest() {
lateinit var mockServer: MockWebServer
lateinit var serverUrl: String
@Before
fun startMockServer() {
mockServer = MockWebServer().apply {
start()
}
serverUrl = mockServer.url("/").toString()
}
@After
fun stopMockServer() {
mockServer.shutdown()
}
}

View File

@@ -0,0 +1,21 @@
package gq.kirmanak.mealient.data.test
import gq.kirmanak.mealient.data.auth.AuthRepo
import gq.kirmanak.mealient.data.test.AuthImplTestData.TEST_PASSWORD
import gq.kirmanak.mealient.data.test.AuthImplTestData.TEST_USERNAME
import gq.kirmanak.mealient.data.test.AuthImplTestData.enqueueSuccessfulAuthResponse
import kotlinx.coroutines.runBlocking
import org.junit.Before
import javax.inject.Inject
abstract class MockServerWithAuthTest : MockServerTest() {
@Inject
lateinit var authRepo: AuthRepo
@Before
fun authenticate(): Unit = runBlocking {
mockServer.enqueueSuccessfulAuthResponse()
authRepo.authenticate(TEST_USERNAME, TEST_PASSWORD, serverUrl)
mockServer.takeRequest()
}
}

View File

@@ -0,0 +1,410 @@
package gq.kirmanak.mealient.data.test
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeEntity
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeIngredientEntity
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeInstructionEntity
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
import gq.kirmanak.mealient.data.recipes.impl.FullRecipeInfo
import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeIngredientResponse
import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeInstructionResponse
import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeResponse
import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeSummaryResponse
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
object RecipeImplTestData {
val RECIPE_SUMMARY_CAKE = GetRecipeSummaryResponse(
remoteId = 1,
name = "Cake",
slug = "cake",
image = "86",
description = "A tasty cake",
recipeCategories = listOf("dessert", "tasty"),
tags = listOf("gluten", "allergic"),
rating = 4,
dateAdded = LocalDate.parse("2021-11-13"),
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"),
)
val RECIPE_SUMMARY_PORRIDGE = GetRecipeSummaryResponse(
remoteId = 2,
name = "Porridge",
slug = "porridge",
image = "89",
description = "A tasty porridge",
recipeCategories = listOf("porridge", "tasty"),
tags = listOf("gluten", "milk"),
rating = 5,
dateAdded = LocalDate.parse("2021-11-12"),
dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"),
)
val TEST_RECIPE_SUMMARIES = listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE)
const val RECIPE_SUMMARY_SUCCESSFUL = """[
{
"id": 1,
"name": "Cake",
"slug": "cake",
"image": "86",
"description": "A tasty cake",
"recipeCategory": ["dessert", "tasty"],
"tags": ["gluten", "allergic"],
"rating": 4,
"dateAdded": "2021-11-13",
"dateUpdated": "2021-11-13T15:30:13"
},
{
"id": 2,
"name": "Porridge",
"slug": "porridge",
"image": "89",
"description": "A tasty porridge",
"recipeCategory": ["porridge", "tasty"],
"tags": ["gluten", "milk"],
"rating": 5,
"dateAdded": "2021-11-12",
"dateUpdated": "2021-10-13T17:35:23"
}
]"""
const val RECIPE_SUMMARY_UNSUCCESSFUL = """
{"detail":"Unauthorized"}
"""
val CAKE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity(
remoteId = 1,
name = "Cake",
slug = "cake",
image = "86",
description = "A tasty cake",
rating = 4,
dateAdded = LocalDate.parse("2021-11-13"),
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13")
)
val PORRIDGE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity(
remoteId = 2,
name = "Porridge",
slug = "porridge",
image = "89",
description = "A tasty porridge",
rating = 5,
dateAdded = LocalDate.parse("2021-11-12"),
dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"),
)
val TEST_RECIPE_ENTITIES = listOf(CAKE_RECIPE_SUMMARY_ENTITY, PORRIDGE_RECIPE_SUMMARY_ENTITY)
fun MockWebServer.enqueueSuccessfulRecipeSummaryResponse() {
val response = MockResponse()
.setBody(RECIPE_SUMMARY_SUCCESSFUL)
.setHeader("Content-Type", "application/json")
.setResponseCode(200)
enqueue(response)
}
fun MockWebServer.enqueueUnsuccessfulRecipeResponse() {
val response = MockResponse()
.setBody(RECIPE_SUMMARY_UNSUCCESSFUL)
.setHeader("Content-Type", "application/json")
.setResponseCode(401)
enqueue(response)
}
val SUGAR_INGREDIENT = GetRecipeIngredientResponse(
title = "Sugar",
note = "2 oz of white sugar",
unit = "",
food = "",
disableAmount = true,
quantity = 1
)
val BREAD_INGREDIENT = GetRecipeIngredientResponse(
title = "Bread",
note = "2 oz of white bread",
unit = "",
food = "",
disableAmount = false,
quantity = 2
)
val MILK_INGREDIENT = GetRecipeIngredientResponse(
title = "Milk",
note = "2 oz of white milk",
unit = "",
food = "",
disableAmount = true,
quantity = 3
)
val MIX_INSTRUCTION = GetRecipeInstructionResponse(
title = "Mix",
text = "Mix the ingredients"
)
val BAKE_INSTRUCTION = GetRecipeInstructionResponse(
title = "Bake",
text = "Bake the ingredients"
)
val BOIL_INSTRUCTION = GetRecipeInstructionResponse(
title = "Boil",
text = "Boil the ingredients"
)
val GET_CAKE_RESPONSE = GetRecipeResponse(
remoteId = 1,
name = "Cake",
slug = "cake",
image = "86",
description = "A tasty cake",
recipeCategories = listOf("dessert", "tasty"),
tags = listOf("gluten", "allergic"),
rating = 4,
dateAdded = LocalDate.parse("2021-11-13"),
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"),
recipeYield = "4 servings",
recipeIngredients = listOf(SUGAR_INGREDIENT, BREAD_INGREDIENT),
recipeInstructions = listOf(MIX_INSTRUCTION, BAKE_INSTRUCTION)
)
val GET_CAKE_RESPONSE_BODY = """
{
"id": 1,
"name": "Cake",
"slug": "cake",
"image": "86",
"description": "A tasty cake",
"recipeCategory": ["dessert", "tasty"],
"tags": ["gluten", "allergic"],
"rating": 4,
"dateAdded": "2021-11-13",
"dateUpdated": "2021-11-13T15:30:13",
"recipeYield": "4 servings",
"recipeIngredient": [
{
"title": "Sugar",
"note": "2 oz of white sugar",
"unit": null,
"food": null,
"disableAmount": true,
"quantity": 1
},
{
"title": "Bread",
"note": "2 oz of white bread",
"unit": null,
"food": null,
"disableAmount": false,
"quantity": 2
}
],
"recipeInstructions": [
{
"title": "Mix",
"text": "Mix the ingredients"
},
{
"title": "Bake",
"text": "Bake the ingredients"
}
],
"nutrition": {
"calories": "100",
"fatContent": "20",
"proteinContent": "30",
"carbohydrateContent": "40",
"fiberContent": "50",
"sodiumContent": "23",
"sugarContent": "53"
},
"tools": [],
"totalTime": "12 hours",
"prepTime": "1 hour",
"performTime": "4 hours",
"settings": {
"public": true,
"showNutrition": true,
"showAssets": true,
"landscapeView": true,
"disableComments": false,
"disableAmount": false
},
"assets": [],
"notes": [
{
"title": "Note title",
"text": "Note text"
},
{
"title": "Second note",
"text": "Second note text"
}
],
"orgURL": null,
"extras": {},
"comments": [
{
"text": "A new comment",
"id": 1,
"uuid": "476ebc15-f794-4eda-8380-d77bba47f839",
"recipeSlug": "test-recipe",
"dateAdded": "2021-11-19T22:13:23.862459",
"user": {
"id": 1,
"username": "kirmanak",
"admin": true
}
},
{
"text": "A second comment",
"id": 2,
"uuid": "20498eba-9639-4acd-ba0a-4829ee06915a",
"recipeSlug": "test-recipe",
"dateAdded": "2021-11-19T22:13:29.912314",
"user": {
"id": 1,
"username": "kirmanak",
"admin": true
}
}
]
}
""".trimIndent()
val GET_PORRIDGE_RESPONSE = GetRecipeResponse(
remoteId = 2,
name = "Porridge",
slug = "porridge",
image = "89",
description = "A tasty porridge",
recipeCategories = listOf("porridge", "tasty"),
tags = listOf("gluten", "milk"),
rating = 5,
dateAdded = LocalDate.parse("2021-11-12"),
dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"),
recipeYield = "3 servings",
recipeIngredients = listOf(SUGAR_INGREDIENT, MILK_INGREDIENT),
recipeInstructions = listOf(MIX_INSTRUCTION, BOIL_INSTRUCTION)
)
val MIX_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
localId = 1,
recipeId = 1,
title = "Mix",
text = "Mix the ingredients",
)
val BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
localId = 2,
recipeId = 1,
title = "Bake",
text = "Bake the ingredients",
)
val CAKE_RECIPE_ENTITY = RecipeEntity(
remoteId = 1,
recipeYield = "4 servings"
)
val CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
localId = 1,
recipeId = 1,
title = "Sugar",
note = "2 oz of white sugar",
unit = "",
food = "",
disableAmount = true,
quantity = 1
)
val CAKE_BREAD_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
localId = 2,
recipeId = 1,
title = "Bread",
note = "2 oz of white bread",
unit = "",
food = "",
disableAmount = false,
quantity = 2
)
val FULL_CAKE_INFO_ENTITY = FullRecipeInfo(
recipeEntity = CAKE_RECIPE_ENTITY,
recipeSummaryEntity = CAKE_RECIPE_SUMMARY_ENTITY,
recipeIngredients = listOf(
CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY,
CAKE_BREAD_RECIPE_INGREDIENT_ENTITY,
),
recipeInstructions = listOf(
MIX_CAKE_RECIPE_INSTRUCTION_ENTITY,
BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY,
),
)
val PORRIDGE_RECIPE_ENTITY_FULL = RecipeEntity(
remoteId = 2,
recipeYield = "3 servings"
)
val PORRIDGE_MILK_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
localId = 4,
recipeId = 2,
title = "Milk",
note = "2 oz of white milk",
unit = "",
food = "",
disableAmount = true,
quantity = 3
)
val PORRIDGE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
localId = 3,
recipeId = 2,
title = "Sugar",
note = "2 oz of white sugar",
unit = "",
food = "",
disableAmount = true,
quantity = 1
)
val PORRIDGE_MIX_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
localId = 3,
recipeId = 2,
title = "Mix",
text = "Mix the ingredients"
)
val PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
localId = 4,
recipeId = 2,
title = "Boil",
text = "Boil the ingredients"
)
val FULL_PORRIDGE_INFO_ENTITY = FullRecipeInfo(
recipeEntity = PORRIDGE_RECIPE_ENTITY_FULL,
recipeSummaryEntity = PORRIDGE_RECIPE_SUMMARY_ENTITY,
recipeIngredients = listOf(
PORRIDGE_SUGAR_RECIPE_INGREDIENT_ENTITY,
PORRIDGE_MILK_RECIPE_INGREDIENT_ENTITY,
),
recipeInstructions = listOf(
PORRIDGE_MIX_RECIPE_INSTRUCTION_ENTITY,
PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY,
)
)
fun MockWebServer.enqueueSuccessfulGetRecipe() {
val response = MockResponse()
.setResponseCode(200)
.setHeader("Content-Type", "application/json")
.setBody(GET_CAKE_RESPONSE_BODY)
enqueue(response)
}
}