From c12eacae1ac5d71f0002d2507aa4e80af259584e Mon Sep 17 00:00:00 2001 From: Bliss Pisit Wetcha Date: Tue, 25 Oct 2022 11:01:24 +0700 Subject: [PATCH] [#18] Add Survey List Repo --- .../blisskmmic/data/model/SurveyApiModel.kt | 29 ++++ .../blisskmmic/data/model/TokenApiModel.kt | 15 +- .../repository/SurveyRepositoryImpl.kt | 23 +++ .../data/network/target/SurveyTargetType.kt | 28 ++++ .../blisskmmic/domain/model/Survey.kt | 11 ++ .../domain/repository/SurveyRepository.kt | 11 ++ .../domain/usecase/SurveyListUseCase.kt | 21 +++ .../data/repository/SurveyRepositoryTest.kt | 61 +++++++ .../helpers/json/SurveyDetailJsonResult.kt | 4 + .../helpers/json/SurveyListJsonResult.kt | 154 ++++++++++++++++++ 10 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/SurveyApiModel.kt create mode 100644 shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/repository/SurveyRepositoryImpl.kt create mode 100644 shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/target/SurveyTargetType.kt create mode 100644 shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/model/Survey.kt create mode 100644 shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/repository/SurveyRepository.kt create mode 100644 shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/usecase/SurveyListUseCase.kt create mode 100644 shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/repository/SurveyRepositoryTest.kt create mode 100644 shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/helpers/json/SurveyDetailJsonResult.kt create mode 100644 shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/helpers/json/SurveyListJsonResult.kt diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/SurveyApiModel.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/SurveyApiModel.kt new file mode 100644 index 00000000..55382982 --- /dev/null +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/SurveyApiModel.kt @@ -0,0 +1,29 @@ +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 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( + title, + description, + isActive, + coverImageUrl, + surveyType + ) +} + diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/TokenApiModel.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/TokenApiModel.kt index ebe3e80b..b8ecb9cf 100644 --- a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/TokenApiModel.kt +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/TokenApiModel.kt @@ -15,7 +15,16 @@ data class TokenApiModel( val refreshToken: String, @SerialName("created_at") val createdAt: Int -) +) { + constructor(token: Token): + this( + token.accessToken, + token.tokenType, + token.expiresIn, + token.refreshToken, + token.createdAt + ) +} fun TokenApiModel.toToken() = Token( accessToken, @@ -24,3 +33,7 @@ fun TokenApiModel.toToken() = Token( refreshToken, createdAt ) + +fun TokenApiModel.toHeader(): Map { + return mapOf("Authorization" to "$tokenType $accessToken") +} diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/repository/SurveyRepositoryImpl.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/repository/SurveyRepositoryImpl.kt new file mode 100644 index 00000000..1dd11cf9 --- /dev/null +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/repository/SurveyRepositoryImpl.kt @@ -0,0 +1,23 @@ +package co.nimblehq.blisskmmic.data.network.repository + +import co.nimblehq.blisskmmic.data.model.SurveyApiModel +import co.nimblehq.blisskmmic.data.model.SurveyDetailApiModel +import co.nimblehq.blisskmmic.data.model.toSurvey +import co.nimblehq.blisskmmic.data.network.core.NetworkClient +import co.nimblehq.blisskmmic.data.network.target.SurveySelectionTargetType +import co.nimblehq.blisskmmic.domain.model.Survey +import co.nimblehq.blisskmmic.domain.model.Token +import co.nimblehq.blisskmmic.domain.model.TokenApiModel +import co.nimblehq.blisskmmic.domain.repository.SurveyRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + + +class SurveyRepositoryImpl(private val networkClient: NetworkClient): SurveyRepository { + + override fun survey(page: Int, token: Token): Flow> { + return networkClient.fetch>( + SurveySelectionTargetType(page, token = TokenApiModel(token)).requestBuilder + ).map { list -> list.map { it.toSurvey() } } + } +} diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/target/SurveyTargetType.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/target/SurveyTargetType.kt new file mode 100644 index 00000000..7ae2cf82 --- /dev/null +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/target/SurveyTargetType.kt @@ -0,0 +1,28 @@ +package co.nimblehq.blisskmmic.data.network.target + +import co.nimblehq.blisskmmic.BuildKonfig +import co.nimblehq.blisskmmic.data.network.helpers.TargetType +import co.nimblehq.blisskmmic.domain.model.TokenApiModel +import co.nimblehq.blisskmmic.domain.model.toHeader +import io.ktor.http.* +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +sealed class SurveyTargetType: TargetType + +class SurveySelectionTargetType(page: Int = 1, size: Int = 3, token: TokenApiModel): UserTargetType() { + + @Serializable + data class SurveySelectionInput( + @SerialName("page[number]") + val pageNumber: Int, + @SerialName("page[size]") + val pageSize: Int + ) + + override var baseURL: String = BuildKonfig.BASE_URL + override var path: String = "api/v1/surveys" + override var method: HttpMethod = HttpMethod.Post + override var body: Any? = SurveySelectionInput(page, size) + override var headers: Map? = token.toHeader() +} diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/model/Survey.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/model/Survey.kt new file mode 100644 index 00000000..54bf09b2 --- /dev/null +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/model/Survey.kt @@ -0,0 +1,11 @@ +package co.nimblehq.blisskmmic.domain.model + +import kotlinx.serialization.SerialName + +data class Survey( + val title: String, + val description: String, + val isActive: Boolean, + val coverImageUrl: String, + val surveyType: String +) diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/repository/SurveyRepository.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/repository/SurveyRepository.kt new file mode 100644 index 00000000..ba0e7197 --- /dev/null +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/repository/SurveyRepository.kt @@ -0,0 +1,11 @@ +package co.nimblehq.blisskmmic.domain.repository + +import co.nimblehq.blisskmmic.data.model.SurveyDetailApiModel +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, token: Token): Flow> +} diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/usecase/SurveyListUseCase.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/usecase/SurveyListUseCase.kt new file mode 100644 index 00000000..5bc839a5 --- /dev/null +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/usecase/SurveyListUseCase.kt @@ -0,0 +1,21 @@ +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 + +interface SurveyListUseCase { + + operator fun invoke(page: Int): Flow> +} + +class SurveyListUseCaseImpl( + private val repository: SurveyRepository, +// private val userSession: + ) : SurveyListUseCase { + + override operator fun invoke(page: Int): Flow> { + return repository.survey(page, token = Token("", "", 1, "", 1)) + } +} diff --git a/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/repository/SurveyRepositoryTest.kt b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/repository/SurveyRepositoryTest.kt new file mode 100644 index 00000000..bb908c03 --- /dev/null +++ b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/repository/SurveyRepositoryTest.kt @@ -0,0 +1,61 @@ +package co.nimblehq.blisskmmic.data.repository + +import co.nimblehq.blisskmmic.data.network.core.NetworkClient +import co.nimblehq.blisskmmic.data.network.repository.SurveyRepositoryImpl +import co.nimblehq.blisskmmic.helpers.json.ERROR_JSON_RESULT +import co.nimblehq.blisskmmic.helpers.json.SURVEY_DETAIL_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 +import io.kotest.matchers.comparables.shouldBeGreaterThan +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.fail + +@ExperimentalCoroutinesApi +class SurveyRepositoryTest { + + val token = co.nimblehq.blisskmmic.domain.model.Token( + "", + "", + 0, + "", + 0 + ) + @Suppress("MaxLineLength") + @Test + fun `When calling survey with success response, it returns correct object`() = runTest { + val engine = jsonMockEngine(SURVEY_LIST_JSON_RESULT) + val networkClient = NetworkClient(engine = engine) + val surveyRepository = SurveyRepositoryImpl(networkClient) + surveyRepository + .survey(1, token) + .collect { + it.size shouldBe 2 + it.first().title shouldBe "Scarlett Bangkok" + } + } + + @Suppress("MaxLineLength") + @Test + fun `When calling survey with failure response, it returns correct error`() = runTest { + val engine = jsonMockEngine(ERROR_JSON_RESULT) + val networkClient = NetworkClient(engine = engine) + val surveyRepository = SurveyRepositoryImpl(networkClient) + surveyRepository + .survey(1, token) + .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") + } + } +} diff --git a/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/helpers/json/SurveyDetailJsonResult.kt b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/helpers/json/SurveyDetailJsonResult.kt new file mode 100644 index 00000000..493632be --- /dev/null +++ b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/helpers/json/SurveyDetailJsonResult.kt @@ -0,0 +1,4 @@ +package co.nimblehq.blisskmmic.helpers.json + +class SurveyDetailJsonResult { +} diff --git a/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/helpers/json/SurveyListJsonResult.kt b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/helpers/json/SurveyListJsonResult.kt new file mode 100644 index 00000000..040536ff --- /dev/null +++ b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/helpers/json/SurveyListJsonResult.kt @@ -0,0 +1,154 @@ +package co.nimblehq.blisskmmic.helpers.json + +const val SURVEY_LIST_JSON_RESULT = """ +{ + "data": [ + { + "id": "d5de6a8f8f5f1cfe51bc", + "type": "survey", + "attributes": { + "title": "Scarlett Bangkok", + "description": "We'd love ot hear from you!", + "thank_email_above_threshold": "Dear {name},

Thank you for visiting Scarlett Wine Bar & Restaurant at Pullman Bangkok Hotel G  and for taking the time to complete our guest feedback survey!

Your feedback is very important to us and each survey is read individually by the management and owners shortly after it is sent. We discuss comments and suggestions at our daily meetings and use them to constantly improve our services.

We would very much appreciate it if you could take a few more moments and review us on TripAdvisor regarding your recent visit. By clicking here you will be directed to our page. 

Thank you once again and we look forward to seeing you soon!

The Team at Scarlett Wine Bar & Restaurant 
Pullman Bangkok Hotel G", + "thank_email_below_threshold": "Dear {name},

Thank you for visiting 
Uno Mas at Centara Central World  and for taking the time to complete our customer feedback survey.

The Team at Scarlett Wine Bar & Restaurant Pullman Bangkok Hotel G", + "is_active": true, + "cover_image_url": "https://dhdbhh0jsld0o.cloudfront.net/m/1ea51560991bcb7d00d0_", + "created_at": "2017-01-23T07:48:12.991Z", + "active_at": "2015-10-08T07:04:00.000Z", + "inactive_at": null, + "survey_type": "Restaurant" + }, + "relationships": { + "questions": { + "data": [ + { + "id": "d3afbcf2b1d60af845dc", + "type": "question" + }, + { + "id": "940d229e4cd87cd1e202", + "type": "question" + }, + { + "id": "ea0555f328b3b0124127", + "type": "question" + }, + { + "id": "16e68f5610ef0e0fa4db", + "type": "question" + }, + { + "id": "bab38ad82eaf22afcdfe", + "type": "question" + }, + { + "id": "85275a0bf28a6f3b1e63", + "type": "question" + }, + { + "id": "642770376f7cd0c87d3c", + "type": "question" + }, + { + "id": "b093a6ad9a6a466fa787", + "type": "question" + }, + { + "id": "e593b2fa2f81891a2b1e", + "type": "question" + }, + { + "id": "c3a9b8ce5c2356010703", + "type": "question" + }, + { + "id": "fbf5d260de1ee6195473", + "type": "question" + }, + { + "id": "4372463ce56db58c0983", + "type": "question" + } + ] + } + } + }, + { + "id": "ed1d4f0ff19a56073a14", + "type": "survey", + "attributes": { + "title": "ibis Bangkok Riverside", + "description": "We'd love to hear from you!", + "thank_email_above_threshold": "Dear {name},

Thank you for visiting Beach Republic and for taking the time to complete our brief survey. We are thrilled that you enjoyed your time with us! If you have a moment, we would be greatly appreciate it if you could leave a short review on TripAdvisor. It helps to spread the word and let others know about the Beach Republic Revolution!

Thank you again and we look forward to welcoming you back soon.

Sincerely,

Beach Republic Team", + "thank_email_below_threshold": "Dear {name},

Thank you for visiting Beach Republic and for taking the time to complete our brief survey. We are constantly striving to improve and your feedback allows us to help improve the experience for you on your next visit. Each survey is read individually by senior staff and discussed with the team in daily meetings. 

Thank you again and we look forward to welcoming you back soon.

Sincerely,

Beach Republic Team", + "is_active": true, + "cover_image_url": "https://dhdbhh0jsld0o.cloudfront.net/m/287db81c5e4242412cc0_", + "created_at": "2017-01-23T03:32:24.585Z", + "active_at": "2016-01-22T04:12:00.000Z", + "inactive_at": null, + "survey_type": "Hotel" + }, + "relationships": { + "questions": { + "data": [ + { + "id": "fa385b75617d98e069a3", + "type": "question" + }, + { + "id": "1b03688d4af8a5c6b1e0", + "type": "question" + }, + { + "id": "6e2b6ee71d3011cc0ac1", + "type": "question" + }, + { + "id": "29272d3bac5725b4c2cf", + "type": "question" + }, + { + "id": "7f164dd6150e6113f8ad", + "type": "question" + }, + { + "id": "1d13ef20807de4f752c7", + "type": "question" + }, + { + "id": "d06378d7ab2925282ecd", + "type": "question" + }, + { + "id": "4c1e9486cf95ba54dac8", + "type": "question" + }, + { + "id": "b8f06895134eb1da2d13", + "type": "question" + }, + { + "id": "e9e2518333211ee2e5c8", + "type": "question" + }, + { + "id": "81c9ae82f32f93c2967d", + "type": "question" + }, + { + "id": "2ecd2926eb02e7a58024", + "type": "question" + } + ] + } + } + } + ], + "meta": { + "page": 1, + "pages": 10, + "page_size": 2, + "records": 20 + } +} +"""