Skip to content

Commit

Permalink
[#18] Add SurveyRepository
Browse files Browse the repository at this point in the history
  • Loading branch information
blyscuit committed Dec 9, 2022
1 parent 1f3c1ad commit cdd8d74
Show file tree
Hide file tree
Showing 15 changed files with 463 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package co.nimblehq.blisskmmic.data.model

import co.nimblehq.blisskmmic.domain.model.PaginationMeta
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
class PaginationMetaApiModel(
val page: Int,
val pages: Int,
@SerialName("page_size")
val pageSize: Int,
val records: Int
)

fun PaginationMetaApiModel.toPaginationMeta(): PaginationMeta {
return PaginationMeta(page, pages, pageSize, records)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package co.nimblehq.blisskmmic.data.model

import co.nimblehq.blisskmmic.domain.model.Survey
import co.nimblehq.jsonapi.model.ApiJson
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class SurveyApiModel(
val id: String,
val title: String,
val description: String,
@SerialName("is_active")
val isActive: Boolean,
@SerialName("cover_image_url")
val coverImageUrl: String,
@SerialName("survey_type")
val surveyType: String
)

fun SurveyApiModel.toSurvey(): Survey {
return Survey(
id,
coverImageUrl,
title,
description
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@ import co.nimblehq.blisskmmic.data.model.ResetPasswordMeta
import co.nimblehq.blisskmmic.data.network.core.NetworkClient
import co.nimblehq.blisskmmic.data.network.target.LoginTargetType
import co.nimblehq.blisskmmic.data.network.target.ResetPasswordTargetType
import co.nimblehq.blisskmmic.data.model.PaginationMetaApiModel
import co.nimblehq.blisskmmic.data.model.SurveyApiModel
import co.nimblehq.blisskmmic.data.network.core.NetworkClient
import co.nimblehq.blisskmmic.data.network.target.LoginTargetType
import co.nimblehq.blisskmmic.data.network.target.SurveySelectionTargetType
import co.nimblehq.blisskmmic.data.network.target.SurveyTargetType
import co.nimblehq.blisskmmic.domain.model.PaginationMeta
import co.nimblehq.blisskmmic.domain.model.Survey
import co.nimblehq.blisskmmic.domain.model.TokenApiModel
import kotlinx.coroutines.flow.Flow

interface NetworkDataSource {

fun logIn(target: LoginTargetType): Flow<TokenApiModel>
fun resetPassword(target: ResetPasswordTargetType): Flow<ResetPasswordMeta>
fun survey(target: SurveyTargetType): Flow<Pair<List<SurveyApiModel>, PaginationMetaApiModel>>
}

class NetworkDataSourceImpl(private val networkClient: NetworkClient): NetworkDataSource {
Expand All @@ -22,4 +31,7 @@ class NetworkDataSourceImpl(private val networkClient: NetworkClient): NetworkDa
override fun resetPassword(target: ResetPasswordTargetType): Flow<ResetPasswordMeta> {
return networkClient.fetch(target.requestBuilder)
}
override fun survey(target: SurveyTargetType): Flow<Pair<List<SurveyApiModel>, PaginationMetaApiModel>> {
return networkClient.fetchWithMeta(target.requestBuilder)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package co.nimblehq.blisskmmic.data.network.target

import co.nimblehq.blisskmmic.data.network.helpers.TargetType
import io.ktor.http.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

sealed class SurveyTargetType: TargetType

class SurveySelectionTargetType(page: Int = 1, size: Int = 3): SurveyTargetType() {

@Serializable
data class SurveySelectionInput(
@SerialName("page[number]")
val pageNumber: Int,
@SerialName("page[size]")
val pageSize: Int
)

override var path = "surveys"
override var method = HttpMethod.Post
override var body = SurveySelectionInput(page, size)
override var headers: Map<String, String>? = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package co.nimblehq.blisskmmic.data.repository

import co.nimblehq.blisskmmic.data.model.*
import co.nimblehq.blisskmmic.data.network.core.NetworkClient
import co.nimblehq.blisskmmic.data.network.datasource.NetworkDataSource
import co.nimblehq.blisskmmic.data.network.target.SurveySelectionTargetType
import co.nimblehq.blisskmmic.domain.model.PaginationMeta
import co.nimblehq.blisskmmic.domain.model.Survey
import co.nimblehq.blisskmmic.domain.repository.SurveyRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class SurveyRepositoryImpl(private val networkDataSource: NetworkDataSource): SurveyRepository {

override fun survey(page: Int): Flow<Pair<List<Survey>, PaginationMeta>> {
return networkDataSource.survey(SurveySelectionTargetType(page))
.map {
Pair(
it.first.map { item -> item.toSurvey() },
it.second.toPaginationMeta()
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ import org.koin.dsl.module
val repositoryModule = module {
single<AuthenticationRepository> { AuthenticationRepositoryImpl(get(), get()) }
single<AccountRecoveryRepository> { AccountRecoveryRepositoryImpl(get()) }
factory<SurveyRepository> { SurveyRepositoryImpl(get(named(TOKENIZED_NETWORK_CLIENT_KOIN))) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package co.nimblehq.blisskmmic.domain.model

data class PaginationMeta(
val page: Int,
val pages: Int,
val pageSize: Int,
val records: Int
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package co.nimblehq.blisskmmic.domain.repository

import co.nimblehq.blisskmmic.domain.model.PaginationMeta
import co.nimblehq.blisskmmic.domain.model.Survey
import co.nimblehq.blisskmmic.domain.model.Token
import kotlinx.coroutines.flow.Flow

interface SurveyRepository {

fun survey(page: Int): Flow<Pair<List<Survey>, PaginationMeta>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package co.nimblehq.blisskmmic.domain.usecase

import co.nimblehq.blisskmmic.domain.model.Survey
import co.nimblehq.blisskmmic.domain.model.Token
import co.nimblehq.blisskmmic.domain.repository.SurveyRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

interface SurveyListUseCase {

operator fun invoke(page: Int): Flow<List<Survey>>
}

class SurveyListUseCaseImpl(
private val repository: SurveyRepository
) : SurveyListUseCase {

override operator fun invoke(page: Int): Flow<List<Survey>> {
return repository
.survey(page)
.map { it.first }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import co.nimblehq.blisskmmic.data.network.core.NetworkClient
import co.nimblehq.blisskmmic.data.network.datasource.NetworkDataSourceImpl
import co.nimblehq.blisskmmic.data.network.target.LoginTargetType
import co.nimblehq.blisskmmic.data.network.target.ResetPasswordTargetType
import co.nimblehq.blisskmmic.data.network.target.SurveySelectionTargetType
import co.nimblehq.blisskmmic.helpers.json.ERROR_JSON_RESULT
import co.nimblehq.blisskmmic.helpers.json.LOG_IN_JSON_RESULT
import co.nimblehq.blisskmmic.helpers.json.RESET_PASSWORD_JSON_RESULT
import co.nimblehq.blisskmmic.helpers.json.SURVEY_LIST_JSON_RESULT
import co.nimblehq.blisskmmic.helpers.mock.ktor.jsonMockEngine
import co.nimblehq.jsonapi.model.JsonApiException
import io.kotest.matchers.collections.shouldContain
Expand Down Expand Up @@ -67,4 +69,38 @@ class NetworkDataSourceTest {
""".trimIndent()
}
}

// Survey

@Test
fun `When calling survey with success response, it returns correct object`() = runTest {
val engine = jsonMockEngine(SURVEY_LIST_JSON_RESULT, "surveys")
val networkClient = NetworkClient(engine = engine)
val dataSource = NetworkDataSourceImpl(networkClient)
dataSource
.survey(SurveySelectionTargetType())
.collect {
it.first.size shouldBe 2
it.first.first().title shouldBe "Scarlett Bangkok"
it.second.page shouldBe 1
}
}

@Test
fun `When calling survey with failure response, it returns correct error`() = runTest {
val engine = jsonMockEngine(ERROR_JSON_RESULT, "surveys")
val networkClient = NetworkClient(engine = engine)
val dataSource = NetworkDataSourceImpl(networkClient)
dataSource
.survey(SurveySelectionTargetType())
.catch { error ->
when(error) {
is JsonApiException -> error.errors.map { it.code } shouldContain "invalid_token"
else -> fail("Should not return incorrect error type")
}
}
.collect {
fail("Should not return object")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package co.nimblehq.blisskmmic.data.network.core

import co.nimblehq.blisskmmic.BuildKonfig
import co.nimblehq.blisskmmic.data.network.helpers.API_VERSION
import co.nimblehq.blisskmmic.helpers.mock.NETWORK_META_MOCK_MODEL_RESULT
import co.nimblehq.blisskmmic.helpers.mock.NetworkMockModel
import co.nimblehq.blisskmmic.helpers.mock.NETWORK_MOCK_MODEL_RESULT
import co.nimblehq.blisskmmic.helpers.mock.NetworkMetaMockModel
import co.nimblehq.blisskmmic.helpers.mock.ktor.jsonMockEngine
import co.nimblehq.jsonapi.model.JsonApiException
import io.kotest.matchers.collections.shouldContain
Expand Down Expand Up @@ -38,6 +40,18 @@ class NetworkClientTest {
}
}

@Test
fun `when calling fetchWithMeta, it returns correct object`() = runTest {
val engine = jsonMockEngine(NETWORK_META_MOCK_MODEL_RESULT, path)
val networkClient = NetworkClient(engine = engine)
networkClient
.fetchWithMeta<NetworkMockModel, NetworkMetaMockModel>(request)
.collect {
it.first.title shouldBe "Hello"
it.second.page shouldBe 1
}
}

@Test
fun `when calling fetch with invalid path, it returns correct object`() = runTest {
val engine = jsonMockEngine(NETWORK_MOCK_MODEL_RESULT, "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import co.nimblehq.blisskmmic.data.database.datasource.LocalDataSource
import co.nimblehq.blisskmmic.data.database.datasource.MockLocalDataSource
import co.nimblehq.blisskmmic.data.database.model.TokenDatabaseModel
import co.nimblehq.blisskmmic.data.network.helpers.API_VERSION
import co.nimblehq.blisskmmic.helpers.mock.NETWORK_META_MOCK_MODEL_RESULT
import co.nimblehq.blisskmmic.helpers.mock.NETWORK_MOCK_MODEL_RESULT
import co.nimblehq.blisskmmic.helpers.mock.NetworkMetaMockModel
import co.nimblehq.blisskmmic.helpers.mock.NetworkMockModel
import co.nimblehq.blisskmmic.helpers.mock.ktor.jsonTokenizedMockEngine
import co.nimblehq.jsonapi.model.JsonApiException
Expand Down Expand Up @@ -64,6 +66,27 @@ class TokenizedNetworkClientTest {
}
}

@Test
fun `when calling fetchWithMeta, it returns correct object`() = runTest {
val mocker = Mocker()
val localDataSource = MockLocalDataSource(mocker)
mocker.every {
localDataSource.getToken()
} returns flow { emit(token) }
val engine = jsonTokenizedMockEngine(
NETWORK_META_MOCK_MODEL_RESULT,
token.accessToken,
path
)
val networkClient = TokenizedNetworkClient(engine = engine, localDataSource)
networkClient
.fetchWithMeta<NetworkMockModel, NetworkMetaMockModel>(request)
.collect {
it.first.title shouldBe "Hello"
it.second.page shouldBe 1
}
}

@Test
fun `when calling fetch with incorrect token, it returns correct error`() = runTest {
mocker.every {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package co.nimblehq.blisskmmic.data.repository

import co.nimblehq.blisskmmic.data.model.PaginationMetaApiModel
import co.nimblehq.blisskmmic.data.model.SurveyApiModel
import co.nimblehq.blisskmmic.data.network.datasource.NetworkDataSource
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.runTest
import org.kodein.mock.Fake
import org.kodein.mock.Mock
import org.kodein.mock.tests.TestsWithMocks
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.fail

@ExperimentalCoroutinesApi
class SurveyRepositoryTest: TestsWithMocks() {

@Mock
lateinit var networkDataSource: NetworkDataSource
@Fake
lateinit var survey: SurveyApiModel
@Fake
lateinit var meta: PaginationMetaApiModel

private val surveyRepository by withMocks { SurveyRepositoryImpl(networkDataSource) }

override fun setUpMocks() = injectMocks(mocker)

@BeforeTest
fun setUp() {
mocker.reset()
}

@Test
fun `When calling survey with success response, it returns correct object`() = runTest {
mocker.every {
networkDataSource.survey(isAny())
} returns flow { emit(Pair(listOf(survey), meta)) }
surveyRepository
.survey(1)
.collect {
it.first.first().title shouldBe survey.title
it.second.page shouldBe meta.page
}
}

@Test
fun `When calling survey with failure response, it returns correct error`() = runTest {
mocker.every {
networkDataSource.survey(isAny())
} returns flow { error("Fail") }
surveyRepository
.survey(1)
.catch {
it.message shouldBe "Fail"
}
.collect {
fail("Should not return object")
}
}
}
Loading

0 comments on commit cdd8d74

Please sign in to comment.