From e726a2855a095672743377f9d1c1ca23da486f73 Mon Sep 17 00:00:00 2001 From: Masoud Khoshkam Date: Sat, 28 Dec 2024 22:00:54 +0330 Subject: [PATCH 1/6] apply JsonApiX library to parse JSON:API --- app/build.gradle.kts | 11 +++- .../com/mkdev/nimblesurvey/di/DataModule.kt | 42 ++++++--------- build.gradle.kts | 1 + data/build.gradle.kts | 11 +++- .../database/room/dao/SurveyRemoteKeyDao.kt | 2 +- .../database/room/entity/SurveyEntity.kt | 4 +- .../room/entity/SurveyRemoteKeyEntity.kt | 4 +- .../datasource/local/mapper/SignInMapper.kt | 6 +-- .../local/mapper/SurveyEntityMapper.kt | 3 +- .../mediator/SurveyRemoteMediator.kt | 17 +++--- .../data/datasource/remote/api/AuthApi.kt | 10 ++-- .../data/datasource/remote/api/SurveyApi.kt | 6 +-- .../remote/interceptor/AuthInterceptor.kt | 6 +-- .../remote/mapper/SurveyDomainMapper.kt | 12 ++--- .../refreshToken/RefreshTokenRequest.kt | 12 +++-- .../resetPassword/ResetPasswordRequest.kt | 10 ++-- .../request/resetPassword/UserRequest.kt | 6 ++- .../model/request/singIn/SignInRequest.kt | 14 ++--- .../resetPassword/ResetPasswordResponse.kt | 20 +++++++ .../singIn/SignInAttributesResponse.kt | 16 ------ .../model/response/singIn/SignInResponse.kt | 27 ++++++---- .../survey/SurveyAttributesResponse.kt | 27 ---------- .../model/response/survey/SurveyResponse.kt | 53 +++++++++++++++---- .../data/repository/AuthRepositoryImpl.kt | 17 ++---- .../com/mkdev/data/utils/ApiErrorHandler.kt | 13 +++-- .../local/mapper/SignInMapperTest.kt | 12 ++--- .../local/mapper/SurveyEntityMapperTest.kt | 13 +++-- .../mediator/SurveyRemoteMediatorTest.kt | 40 ++++++++------ .../data/datasource/remote/api/AuthApiTest.kt | 22 ++++---- .../datasource/remote/api/SurveyApiTest.kt | 16 ++---- .../remote/interceptor/AuthInterceptorTest.kt | 29 +++++----- .../mkdev/data/factory/MetaResponseFactory.kt | 13 ----- .../com/mkdev/data/factory/ResourceFactory.kt | 12 ----- .../data/factory/SignInResponseFactory.kt | 14 +++-- .../mkdev/data/factory/SurveyEntityFactory.kt | 4 +- .../factory/SurveyRemoteKeyEntityFactory.kt | 4 +- .../data/factory/SurveyResponseFactory.kt | 53 +++++++++++-------- .../mkdev/domain/model/survey/SurveyModel.kt | 1 - gradle.properties | 4 +- gradle/libs.versions.toml | 11 +++- presentation/build.gradle.kts | 2 + .../presentation/viewmodel/HomeViewModel.kt | 1 - 42 files changed, 299 insertions(+), 302 deletions(-) create mode 100644 data/src/main/java/com/mkdev/data/datasource/remote/model/response/resetPassword/ResetPasswordResponse.kt delete mode 100644 data/src/main/java/com/mkdev/data/datasource/remote/model/response/singIn/SignInAttributesResponse.kt delete mode 100644 data/src/main/java/com/mkdev/data/datasource/remote/model/response/survey/SurveyAttributesResponse.kt delete mode 100644 data/src/test/java/com/mkdev/data/factory/MetaResponseFactory.kt delete mode 100644 data/src/test/java/com/mkdev/data/factory/ResourceFactory.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3970d22..5c3e89e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,8 @@ plugins { alias(libs.plugins.kotlin.compose) alias(libs.plugins.dagger.hilt) alias(libs.plugins.ksp) + alias(libs.plugins.kotlinx.serialization) + alias(libs.plugins.kotlin.kapt) } android { @@ -96,10 +98,8 @@ dependencies { // Networking implementation(libs.retrofit2) implementation(libs.retrofit2.converter.gson) - implementation(libs.retrofit2.adapter.rxjava2) implementation(libs.okhttp3) implementation(libs.okhttp3.logging.interceptor) - implementation(libs.kotlinx.serialization.json) // DataStore, ProtoBuf implementation(libs.datastore) @@ -115,6 +115,13 @@ dependencies { implementation(libs.room.ktx) implementation(libs.room.paging) + // JsonApiX + implementation(libs.kotlinx.serialization.json) + implementation(libs.jsonapix.core) + kapt(libs.jsonapix.processor) + implementation(libs.jsonapix.retrofit) + implementation(libs.retrofit2.kotlinx.serialization.converter) + // Unit Test testImplementation(libs.junit) testImplementation(libs.robolectric) diff --git a/app/src/main/java/com/mkdev/nimblesurvey/di/DataModule.kt b/app/src/main/java/com/mkdev/nimblesurvey/di/DataModule.kt index ddfca75..abec0b0 100644 --- a/app/src/main/java/com/mkdev/nimblesurvey/di/DataModule.kt +++ b/app/src/main/java/com/mkdev/nimblesurvey/di/DataModule.kt @@ -3,7 +3,9 @@ package com.mkdev.nimblesurvey.di import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.core.Serializer -import com.google.gson.Gson +import com.infinum.jsonapix.TypeAdapterFactory +import com.infinum.jsonapix.retrofit.JsonXConverterFactory +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.mkdev.data.datasource.local.UserLocal import com.mkdev.data.datasource.local.crypto.Crypto import com.mkdev.data.datasource.local.crypto.CryptoImpl @@ -15,7 +17,6 @@ import com.mkdev.data.datasource.local.datastore.UserLocalSourceImpl import com.mkdev.data.datasource.remote.api.AuthApi import com.mkdev.data.datasource.remote.api.SurveyApi import com.mkdev.data.datasource.remote.interceptor.AuthInterceptor -import com.mkdev.data.utils.ApiErrorHandler import com.mkdev.nimblesurvey.BuildConfig import com.mkdev.nimblesurvey.utils.ApiConfigs import dagger.Binds @@ -24,15 +25,15 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Converter import retrofit2.Retrofit -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory -import retrofit2.converter.gson.GsonConverterFactory import javax.inject.Singleton import kotlin.time.toJavaDuration + @Module @InstallIn(SingletonComponent::class) abstract class DataModule { @@ -54,16 +55,6 @@ abstract class DataModule { internal companion object { - @Provides - @Singleton - fun provideApiErrorHandler(gson: Gson): ApiErrorHandler { - return ApiErrorHandler(gson) - } - - @Provides - @Singleton - fun provideGson(): Gson = Gson() - @Provides @Singleton fun provideDataStore( @@ -99,24 +90,23 @@ abstract class DataModule { .readTimeout(ApiConfigs.Timeouts.read.toJavaDuration()) .build() - @Singleton - @Provides - fun provideConverterFactory(): Converter.Factory { - return GsonConverterFactory.create() - } - @Singleton @Provides fun provideRetrofitApiService( okHttpClient: OkHttpClient, - converterFactory: Converter.Factory, - ): Retrofit = - Retrofit.Builder() + ): Retrofit { + val networkJson = Json { + ignoreUnknownKeys = true + isLenient = true + } + + return Retrofit.Builder() .baseUrl(BuildConfig.API_URL) - .addConverterFactory(converterFactory) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(JsonXConverterFactory(TypeAdapterFactory())) + .addConverterFactory(networkJson.asConverterFactory("application/json".toMediaType())) .client(okHttpClient) .build() + } @Singleton @Provides diff --git a/build.gradle.kts b/build.gradle.kts index 8646d2f..88b905c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,4 +8,5 @@ plugins { alias(libs.plugins.android.library) apply false alias(libs.plugins.dagger.hilt) apply false alias(libs.plugins.ksp) apply false + alias(libs.plugins.kotlin.kapt) apply false } \ No newline at end of file diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 5abb81e..a784f19 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -8,6 +8,8 @@ plugins { alias(libs.plugins.dagger.hilt) alias(libs.plugins.ksp) alias(libs.plugins.protobuf) + alias(libs.plugins.kotlinx.serialization) + alias(libs.plugins.kotlin.kapt) } android { @@ -59,10 +61,8 @@ dependencies { // Networking implementation(libs.retrofit2) implementation(libs.retrofit2.converter.gson) - implementation(libs.retrofit2.adapter.rxjava2) implementation(libs.okhttp3) implementation(libs.okhttp3.logging.interceptor) - implementation(libs.kotlinx.serialization.json) // DataStore, ProtoBuf implementation(libs.datastore) @@ -75,6 +75,13 @@ dependencies { implementation(libs.room.ktx) implementation(libs.room.paging) + // JsonApiX + implementation(libs.kotlinx.serialization.json) + implementation(libs.jsonapix.core) + kapt(libs.jsonapix.processor) + implementation(libs.jsonapix.retrofit) + implementation(libs.retrofit2.kotlinx.serialization.converter) + // Unit Test testImplementation(libs.junit) testImplementation(libs.mockito) diff --git a/data/src/main/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDao.kt b/data/src/main/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDao.kt index 913f74a..bc7a369 100644 --- a/data/src/main/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDao.kt +++ b/data/src/main/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDao.kt @@ -18,7 +18,7 @@ interface SurveyRemoteKeyDao { suspend fun insertOrReplace(remoteKey: SurveyRemoteKeyEntity) @Query("SELECT * FROM survey_remote_key_table WHERE surveyId = :id") - suspend fun remoteKeysId(id: String): SurveyRemoteKeyEntity? + suspend fun remoteKeysId(id: Int): SurveyRemoteKeyEntity? @Query("DELETE FROM survey_remote_key_table") suspend fun clearRemoteKeys() diff --git a/data/src/main/java/com/mkdev/data/datasource/local/database/room/entity/SurveyEntity.kt b/data/src/main/java/com/mkdev/data/datasource/local/database/room/entity/SurveyEntity.kt index 8d719f8..e71c413 100644 --- a/data/src/main/java/com/mkdev/data/datasource/local/database/room/entity/SurveyEntity.kt +++ b/data/src/main/java/com/mkdev/data/datasource/local/database/room/entity/SurveyEntity.kt @@ -5,8 +5,8 @@ import androidx.room.PrimaryKey @Entity(tableName = "survey_table") data class SurveyEntity( - @PrimaryKey - val id: String, + @PrimaryKey(autoGenerate = true) + val id: Int = 0, val title: String, val description: String, val coverImageUrl: String, diff --git a/data/src/main/java/com/mkdev/data/datasource/local/database/room/entity/SurveyRemoteKeyEntity.kt b/data/src/main/java/com/mkdev/data/datasource/local/database/room/entity/SurveyRemoteKeyEntity.kt index 24eacc3..155a32e 100644 --- a/data/src/main/java/com/mkdev/data/datasource/local/database/room/entity/SurveyRemoteKeyEntity.kt +++ b/data/src/main/java/com/mkdev/data/datasource/local/database/room/entity/SurveyRemoteKeyEntity.kt @@ -5,8 +5,8 @@ import androidx.room.PrimaryKey @Entity(tableName = "survey_remote_key_table") data class SurveyRemoteKeyEntity( - @PrimaryKey - val surveyId: String, + @PrimaryKey(autoGenerate = true) + val surveyId: Int = 0, val prevPage: Int?, val nextPage: Int? ) \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/datasource/local/mapper/SignInMapper.kt b/data/src/main/java/com/mkdev/data/datasource/local/mapper/SignInMapper.kt index fd9989f..85e192f 100644 --- a/data/src/main/java/com/mkdev/data/datasource/local/mapper/SignInMapper.kt +++ b/data/src/main/java/com/mkdev/data/datasource/local/mapper/SignInMapper.kt @@ -6,12 +6,12 @@ import javax.inject.Inject class SignInMapper @Inject constructor() { fun mapToUserLocal(signInResponse: SignInResponse?): UserLocal? { - return signInResponse?.attributes?.let { attributes -> + return signInResponse?.let { attributes -> UserLocal.newBuilder() .setAccessToken(attributes.accessToken) .setRefreshToken(attributes.refreshToken) - .setCreatedAt(attributes.createdAt) - .setExpiresIn(attributes.expiresIn) + .setCreatedAt(attributes.createdAt.toString()) + .setExpiresIn(attributes.expiresIn.toString()) .setTokenType(attributes.tokenType) .build() } diff --git a/data/src/main/java/com/mkdev/data/datasource/local/mapper/SurveyEntityMapper.kt b/data/src/main/java/com/mkdev/data/datasource/local/mapper/SurveyEntityMapper.kt index 76bb5bc..abb1af8 100644 --- a/data/src/main/java/com/mkdev/data/datasource/local/mapper/SurveyEntityMapper.kt +++ b/data/src/main/java/com/mkdev/data/datasource/local/mapper/SurveyEntityMapper.kt @@ -6,9 +6,8 @@ import javax.inject.Inject class SurveyEntityMapper @Inject constructor() { fun mapToSurveyEntity(surveyResponse: SurveyResponse): SurveyEntity { - return with(surveyResponse.attributes) { + return with(surveyResponse) { SurveyEntity( - id = surveyResponse.id, title = title, description = description, coverImageUrl = coverImageUrl, diff --git a/data/src/main/java/com/mkdev/data/datasource/mediator/SurveyRemoteMediator.kt b/data/src/main/java/com/mkdev/data/datasource/mediator/SurveyRemoteMediator.kt index 411d290..2805cf1 100644 --- a/data/src/main/java/com/mkdev/data/datasource/mediator/SurveyRemoteMediator.kt +++ b/data/src/main/java/com/mkdev/data/datasource/mediator/SurveyRemoteMediator.kt @@ -13,7 +13,6 @@ import com.mkdev.data.datasource.remote.api.SurveyApi import com.mkdev.data.utils.RemoteApiPaging import retrofit2.HttpException import java.io.IOException -import java.lang.RuntimeException @OptIn(ExperimentalPagingApi::class) internal class SurveyRemoteMediator( @@ -62,8 +61,8 @@ internal class SurveyRemoteMediator( } val response = surveyApi.getSurveys(page = page, pageSize = RemoteApiPaging.PAGE_SIZE) - val surveys = response.body()?.data - val endOfPaginationReached = surveys.isNullOrEmpty() + val surveys = response.data + val endOfPaginationReached = surveys.isEmpty() // Clear local data for REFRESH if (loadType == LoadType.REFRESH) { @@ -74,15 +73,15 @@ internal class SurveyRemoteMediator( val prevKey = if (page == RemoteApiPaging.FIRST_PAGE) null else page - 1 val nextKey = if (endOfPaginationReached) null else page + 1 - val surveyEntities = surveys?.map { surveyDto -> - surveyEntityMapper.mapToSurveyEntity(surveyDto) + val surveyEntities = surveys.map { surveyDto -> + surveyEntityMapper.mapToSurveyEntity(surveyDto.data) } // Insert new surveys and remote keys - surveyEntities?.let { surveyDao.insertAll(it) } - surveys?.map { - SurveyRemoteKeyEntity(surveyId = it.id, prevPage = prevKey, nextPage = nextKey) - }?.let { surveyRemoteKeyDao.insertAll(it) } + surveyEntities.let { surveyDao.insertAll(it) } + surveys.map { + SurveyRemoteKeyEntity(prevPage = prevKey, nextPage = nextKey) + }.let { surveyRemoteKeyDao.insertAll(it) } return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) } catch (e: IOException) { diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/api/AuthApi.kt b/data/src/main/java/com/mkdev/data/datasource/remote/api/AuthApi.kt index 629c82f..93be158 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/api/AuthApi.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/api/AuthApi.kt @@ -3,9 +3,9 @@ package com.mkdev.data.datasource.remote.api import com.mkdev.data.datasource.remote.model.request.refreshToken.RefreshTokenRequest import com.mkdev.data.datasource.remote.model.request.resetPassword.ResetPasswordRequest import com.mkdev.data.datasource.remote.model.request.singIn.SignInRequest -import com.mkdev.data.datasource.remote.model.response.singIn.SignInResponse +import com.mkdev.data.datasource.remote.model.response.resetPassword.ResetPasswordResponseModel +import com.mkdev.data.datasource.remote.model.response.singIn.SignInResponseModel import com.mkdev.data.utils.ApiConfigs -import com.mkdev.data.datasource.remote.model.response.base.BaseApiResponse import retrofit2.Response import retrofit2.http.Body import retrofit2.http.Headers @@ -14,14 +14,14 @@ import retrofit2.http.POST interface AuthApi { @Headers("${ApiConfigs.CUSTOM_HEADER}: ${ApiConfigs.NO_AUTH}") @POST("oauth/token") - suspend fun signIn(@Body requestBody: SignInRequest): Response> + suspend fun signIn(@Body requestBody: SignInRequest): SignInResponseModel @Headers("${ApiConfigs.CUSTOM_HEADER}: ${ApiConfigs.NO_AUTH}") @POST("oauth/token") - suspend fun refreshToken(@Body requestBody: RefreshTokenRequest): Response> + suspend fun refreshToken(@Body requestBody: RefreshTokenRequest): Response @POST("passwords") suspend fun resetPassword( @Body requestBody: ResetPasswordRequest - ): Response> + ): ResetPasswordResponseModel } \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/api/SurveyApi.kt b/data/src/main/java/com/mkdev/data/datasource/remote/api/SurveyApi.kt index fcd6118..ffb8295 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/api/SurveyApi.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/api/SurveyApi.kt @@ -1,8 +1,6 @@ package com.mkdev.data.datasource.remote.api -import com.mkdev.data.datasource.remote.model.response.survey.SurveyResponse -import com.mkdev.data.datasource.remote.model.response.base.BaseApiResponse -import retrofit2.Response +import com.mkdev.data.datasource.remote.model.response.survey.SurveyResponseList import retrofit2.http.GET import retrofit2.http.Query @@ -11,5 +9,5 @@ interface SurveyApi { suspend fun getSurveys( @Query("page[number]") page: Int, @Query("page[size]") pageSize: Int - ): Response>> + ): SurveyResponseList } \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/interceptor/AuthInterceptor.kt b/data/src/main/java/com/mkdev/data/datasource/remote/interceptor/AuthInterceptor.kt index 4fa5e6c..cf4fec2 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/interceptor/AuthInterceptor.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/interceptor/AuthInterceptor.kt @@ -63,14 +63,14 @@ class AuthInterceptor @Inject constructor( when (refreshTokenResponse.code()) { HTTP_OK -> { - refreshTokenResponse.body()!!.data?.attributes?.also { data -> + refreshTokenResponse.body()?.data?.also { data -> userLocalSource.update { (it ?: return@update null) .toBuilder() .setAccessToken(data.accessToken) .setRefreshToken(data.refreshToken) - .setCreatedAt(data.createdAt) - .setExpiresIn(data.expiresIn) + .setCreatedAt(data.createdAt.toString()) + .setExpiresIn(data.expiresIn.toString()) .setTokenType(data.tokenType) .build() } diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/mapper/SurveyDomainMapper.kt b/data/src/main/java/com/mkdev/data/datasource/remote/mapper/SurveyDomainMapper.kt index 1d97acc..ce71392 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/mapper/SurveyDomainMapper.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/mapper/SurveyDomainMapper.kt @@ -8,18 +8,16 @@ import javax.inject.Inject class SurveyDomainMapper @Inject constructor() { fun mapToSurveyDomainModel(surveyResponse: SurveyResponse): SurveyModel { return SurveyModel( - id = surveyResponse.id, - title = surveyResponse.attributes.title, - description = surveyResponse.attributes.description, - coverImageUrl = surveyResponse.attributes.coverImageUrl, - isActive = surveyResponse.attributes.isActive, - surveyType = surveyResponse.attributes.surveyType + title = surveyResponse.title, + description = surveyResponse.description, + coverImageUrl = surveyResponse.coverImageUrl, + isActive = surveyResponse.isActive, + surveyType = surveyResponse.surveyType ) } fun mapToSurveyDomainModel(surveyEntity: SurveyEntity): SurveyModel { return SurveyModel( - id = surveyEntity.id, title = surveyEntity.title, description = surveyEntity.description, coverImageUrl = surveyEntity.coverImageUrl, diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/model/request/refreshToken/RefreshTokenRequest.kt b/data/src/main/java/com/mkdev/data/datasource/remote/model/request/refreshToken/RefreshTokenRequest.kt index 8a67307..f7d099d 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/model/request/refreshToken/RefreshTokenRequest.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/model/request/refreshToken/RefreshTokenRequest.kt @@ -1,14 +1,16 @@ package com.mkdev.data.datasource.remote.model.request.refreshToken -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +@Serializable data class RefreshTokenRequest( - @SerializedName("grant_type") + @SerialName("grant_type") val grantType: String, - @SerializedName("refresh_token") + @SerialName("refresh_token") val refreshToken: String, - @SerializedName("client_id") + @SerialName("client_id") val clientId: String, - @SerializedName("client_secret") + @SerialName("client_secret") val clientSecret: String, ) \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/model/request/resetPassword/ResetPasswordRequest.kt b/data/src/main/java/com/mkdev/data/datasource/remote/model/request/resetPassword/ResetPasswordRequest.kt index 934d410..91df669 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/model/request/resetPassword/ResetPasswordRequest.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/model/request/resetPassword/ResetPasswordRequest.kt @@ -1,12 +1,14 @@ package com.mkdev.data.datasource.remote.model.request.resetPassword -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +@Serializable data class ResetPasswordRequest( - @SerializedName("user") + @SerialName("user") val user: UserRequest, - @SerializedName("client_id") + @SerialName("client_id") val clientId: String, - @SerializedName("client_secret") + @SerialName("client_secret") val clientSecret: String, ) \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/model/request/resetPassword/UserRequest.kt b/data/src/main/java/com/mkdev/data/datasource/remote/model/request/resetPassword/UserRequest.kt index b29af86..e47ca86 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/model/request/resetPassword/UserRequest.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/model/request/resetPassword/UserRequest.kt @@ -1,8 +1,10 @@ package com.mkdev.data.datasource.remote.model.request.resetPassword -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +@Serializable data class UserRequest( - @SerializedName("email") + @SerialName("email") val email: String ) diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/model/request/singIn/SignInRequest.kt b/data/src/main/java/com/mkdev/data/datasource/remote/model/request/singIn/SignInRequest.kt index 216bcd1..a938853 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/model/request/singIn/SignInRequest.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/model/request/singIn/SignInRequest.kt @@ -1,16 +1,18 @@ package com.mkdev.data.datasource.remote.model.request.singIn -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +@Serializable data class SignInRequest( - @SerializedName("grant_type") + @SerialName("grant_type") val grantType: String, - @SerializedName("email") + @SerialName("email") val email: String, - @SerializedName("password") + @SerialName("password") val password: String, - @SerializedName("client_id") + @SerialName("client_id") val clientId: String, - @SerializedName("client_secret") + @SerialName("client_secret") val clientSecret: String, ) \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/model/response/resetPassword/ResetPasswordResponse.kt b/data/src/main/java/com/mkdev/data/datasource/remote/model/response/resetPassword/ResetPasswordResponse.kt new file mode 100644 index 0000000..826d2c6 --- /dev/null +++ b/data/src/main/java/com/mkdev/data/datasource/remote/model/response/resetPassword/ResetPasswordResponse.kt @@ -0,0 +1,20 @@ +package com.mkdev.data.datasource.remote.model.response.resetPassword + +import com.infinum.jsonapix.annotations.JsonApiX +import com.infinum.jsonapix.annotations.JsonApiXMeta +import com.infinum.jsonapix.core.JsonApiModel +import com.infinum.jsonapix.core.resources.Meta +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@SerialName("resetPassword") +@JsonApiX("resetPassword") +@Serializable +data class ResetPasswordResponse(val test: String? = null) : JsonApiModel() + +@SerialName("resetPassword") +@Serializable +@JsonApiXMeta("resetPassword") +data class ResetPasswordMetaResponse( + val message: String +) : Meta \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/model/response/singIn/SignInAttributesResponse.kt b/data/src/main/java/com/mkdev/data/datasource/remote/model/response/singIn/SignInAttributesResponse.kt deleted file mode 100644 index e952625..0000000 --- a/data/src/main/java/com/mkdev/data/datasource/remote/model/response/singIn/SignInAttributesResponse.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.mkdev.data.datasource.remote.model.response.singIn - -import com.google.gson.annotations.SerializedName - -data class SignInAttributesResponse( - @SerializedName("access_token") - val accessToken: String, - @SerializedName("created_at") - val createdAt: String, - @SerializedName("expires_in") - val expiresIn: String, - @SerializedName("refresh_token") - val refreshToken: String, - @SerializedName("token_type") - val tokenType: String -) \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/model/response/singIn/SignInResponse.kt b/data/src/main/java/com/mkdev/data/datasource/remote/model/response/singIn/SignInResponse.kt index 4c5d77c..3f6a685 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/model/response/singIn/SignInResponse.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/model/response/singIn/SignInResponse.kt @@ -1,13 +1,22 @@ package com.mkdev.data.datasource.remote.model.response.singIn +import com.infinum.jsonapix.annotations.JsonApiX +import com.infinum.jsonapix.core.JsonApiModel +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable -import com.google.gson.annotations.SerializedName - +@Serializable +@SerialName("token") +@JsonApiX("token") data class SignInResponse( - @SerializedName("attributes") - val attributes: SignInAttributesResponse, - @SerializedName("id") - val id: String, - @SerializedName("type") - val type: String -) \ No newline at end of file + @SerialName("access_token") + val accessToken: String, + @SerialName("created_at") + val createdAt: Long, + @SerialName("expires_in") + val expiresIn: Long, + @SerialName("refresh_token") + val refreshToken: String, + @SerialName("token_type") + val tokenType: String +) : JsonApiModel() \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/model/response/survey/SurveyAttributesResponse.kt b/data/src/main/java/com/mkdev/data/datasource/remote/model/response/survey/SurveyAttributesResponse.kt deleted file mode 100644 index 8610c19..0000000 --- a/data/src/main/java/com/mkdev/data/datasource/remote/model/response/survey/SurveyAttributesResponse.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.mkdev.data.datasource.remote.model.response.survey - - -import com.google.gson.annotations.SerializedName - -data class SurveyAttributesResponse( - @SerializedName("active_at") - val activeAt: String, - @SerializedName("cover_image_url") - val coverImageUrl: String, - @SerializedName("created_at") - val createdAt: String, - @SerializedName("description") - val description: String, - @SerializedName("inactive_at") - val inactiveAt: Any, - @SerializedName("is_active") - val isActive: Boolean, - @SerializedName("survey_type") - val surveyType: String, - @SerializedName("thank_email_above_threshold") - val thankEmailAboveThreshold: String, - @SerializedName("thank_email_below_threshold") - val thankEmailBelowThreshold: String, - @SerializedName("title") - val title: String -) \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/model/response/survey/SurveyResponse.kt b/data/src/main/java/com/mkdev/data/datasource/remote/model/response/survey/SurveyResponse.kt index 9975996..72bfd15 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/model/response/survey/SurveyResponse.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/model/response/survey/SurveyResponse.kt @@ -1,13 +1,48 @@ package com.mkdev.data.datasource.remote.model.response.survey +import com.infinum.jsonapix.annotations.JsonApiX +import com.infinum.jsonapix.annotations.JsonApiXMeta +import com.infinum.jsonapix.core.JsonApiModel +import com.infinum.jsonapix.core.resources.Meta +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable -import com.google.gson.annotations.SerializedName - +@Serializable +@SerialName("survey_simple") +@JsonApiX("survey_simple") data class SurveyResponse( - @SerializedName("attributes") - val attributes: SurveyAttributesResponse, - @SerializedName("id") - val id: String, - @SerializedName("type") - val type: String -) \ No newline at end of file + @SerialName("title") + val title: String, + @SerialName("description") + val description: String, + @SerialName("thank_email_above_threshold") + val thankEmailAboveThreshold: String? = null, + @SerialName("thank_email_below_threshold") + val thankEmailBelowThreshold: String? = null, + @SerialName("is_active") + val isActive: Boolean, + @SerialName("cover_image_url") + val coverImageUrl: String, + @SerialName("created_at") + val createdAt: String, + @SerialName("active_at") + val activeAt: String, + @SerialName("inactive_at") + val inactiveAt: String? = null, + @SerialName("survey_type") + val surveyType: String, +) : JsonApiModel() + +@SerialName("survey_simple") +@Serializable +@JsonApiXMeta("survey_simple") +data class SurveyMetaResponse( + @SerialName("page") + val page: Int, + @SerialName("pages") + val pages: Int, + @SerialName("page_size") + val pageSize: Int, + @SerialName("records") + val records: Int, +) : Meta \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/mkdev/data/repository/AuthRepositoryImpl.kt index e7dc659..74b0f29 100644 --- a/data/src/main/java/com/mkdev/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/mkdev/data/repository/AuthRepositoryImpl.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map -import retrofit2.HttpException class AuthRepositoryImpl( private val userLocalSource: UserLocalSource, @@ -43,13 +42,8 @@ class AuthRepositoryImpl( ) ) }.onSuccess { result -> - if (result.isSuccessful) { - userLocalSource.update { signInMapper.mapToUserLocal(result.body()?.data) } - emit(Resource.Success(Unit)) - } else { - val apiException = apiErrorHandler.handleError(HttpException(result)) - emit(Resource.Error(apiException.message)) - } + userLocalSource.update { signInMapper.mapToUserLocal(result.data) } + emit(Resource.Success(Unit)) }.onFailure { throwable -> val apiException = apiErrorHandler.handleError(throwable) emit(Resource.Error(apiException.message)) @@ -79,12 +73,7 @@ class AuthRepositoryImpl( ) ) }.onSuccess { result -> - if (result.isSuccessful) { - emit(Resource.Success(result.body()?.meta?.message)) - } else { - val apiException = apiErrorHandler.handleError(HttpException(result)) - emit(Resource.Error(apiException.message)) - } + emit(Resource.Success(result.rootMeta?.message)) }.onFailure { throwable -> val apiException = apiErrorHandler.handleError(throwable) emit(Resource.Error(apiException.message)) diff --git a/data/src/main/java/com/mkdev/data/utils/ApiErrorHandler.kt b/data/src/main/java/com/mkdev/data/utils/ApiErrorHandler.kt index 35db6ee..26f0f2e 100644 --- a/data/src/main/java/com/mkdev/data/utils/ApiErrorHandler.kt +++ b/data/src/main/java/com/mkdev/data/utils/ApiErrorHandler.kt @@ -1,23 +1,22 @@ package com.mkdev.data.utils -import com.google.gson.Gson import com.google.gson.JsonSyntaxException -import com.mkdev.data.datasource.remote.model.response.error.LoginErrorResponse +import com.infinum.jsonapix.asJsonXHttpException +import com.infinum.jsonapix.core.resources.DefaultError import retrofit2.HttpException import java.io.IOException import javax.inject.Inject -class ApiErrorHandler @Inject constructor(private val gson: Gson) { +class ApiErrorHandler @Inject constructor() { fun handleError(throwable: Throwable): ApiException { return when (throwable) { is IOException -> ApiException.NetworkError(throwable.message) is HttpException -> { - val errorBody = throwable.response()?.errorBody()?.string() val code = throwable.code() try { - val errorResponse = gson.fromJson(errorBody, LoginErrorResponse::class.java) - val errorMessage = errorResponse.errors.firstOrNull()?.detail - ApiException.HttpError(code, errorMessage) + val jsonXHttpException = throwable.asJsonXHttpException() + val defaultError = jsonXHttpException.errors?.firstOrNull() + ApiException.HttpError(code, defaultError?.detail) } catch (exception: JsonSyntaxException) { ApiException.ParseError("Error parsing error response") } catch (exception: Exception) { diff --git a/data/src/test/java/com/mkdev/data/datasource/local/mapper/SignInMapperTest.kt b/data/src/test/java/com/mkdev/data/datasource/local/mapper/SignInMapperTest.kt index 0a352c6..6f6beeb 100644 --- a/data/src/test/java/com/mkdev/data/datasource/local/mapper/SignInMapperTest.kt +++ b/data/src/test/java/com/mkdev/data/datasource/local/mapper/SignInMapperTest.kt @@ -25,7 +25,7 @@ class SignInMapperTest { @Test fun `mapToUserLocal should return UserLocal when signInResponse is not null`() { // Given - val signInResponse = SignInResponseFactory.createSignInResponse() + val signInResponse = SignInResponseFactory.createSignInResponse().data // When val result = signInMapper.mapToUserLocal(signInResponse) @@ -33,10 +33,10 @@ class SignInMapperTest { // Then // Assert that the result is not null and then access its properties val userLocal = requireNotNull(result) - assertEquals(signInResponse.attributes.accessToken, userLocal.accessToken) - assertEquals(signInResponse.attributes.refreshToken, userLocal.refreshToken) - assertEquals(signInResponse.attributes.createdAt, userLocal.createdAt) - assertEquals(signInResponse.attributes.expiresIn, userLocal.expiresIn) - assertEquals(signInResponse.attributes.tokenType, userLocal.tokenType) + assertEquals(signInResponse.accessToken, userLocal.accessToken) + assertEquals(signInResponse.refreshToken, userLocal.refreshToken) + assertEquals(signInResponse.createdAt.toString(), userLocal.createdAt) + assertEquals(signInResponse.expiresIn.toString(), userLocal.expiresIn) + assertEquals(signInResponse.tokenType, userLocal.tokenType) } } \ No newline at end of file diff --git a/data/src/test/java/com/mkdev/data/datasource/local/mapper/SurveyEntityMapperTest.kt b/data/src/test/java/com/mkdev/data/datasource/local/mapper/SurveyEntityMapperTest.kt index 40ea740..45692b5 100644 --- a/data/src/test/java/com/mkdev/data/datasource/local/mapper/SurveyEntityMapperTest.kt +++ b/data/src/test/java/com/mkdev/data/datasource/local/mapper/SurveyEntityMapperTest.kt @@ -11,17 +11,16 @@ class SurveyEntityMapperTest { @Test fun `mapToSurveyEntity should map SurveyResponse to SurveyEntity correctly`() { // Given - val surveyResponse = SurveyResponseFactory.createSurveyResponse() + val surveyResponse = SurveyResponseFactory.createSurveyResponse().data // When val result = surveyEntityMapper.mapToSurveyEntity(surveyResponse) // Then - assertEquals(surveyResponse.id, result.id) - assertEquals(surveyResponse.attributes.title, result.title) - assertEquals(surveyResponse.attributes.description, result.description) - assertEquals(surveyResponse.attributes.coverImageUrl, result.coverImageUrl) - assertEquals(surveyResponse.attributes.isActive, result.isActive) - assertEquals(surveyResponse.attributes.surveyType, result.surveyType) + assertEquals(surveyResponse.title, result.title) + assertEquals(surveyResponse.description, result.description) + assertEquals(surveyResponse.coverImageUrl, result.coverImageUrl) + assertEquals(surveyResponse.isActive, result.isActive) + assertEquals(surveyResponse.surveyType, result.surveyType) } } \ No newline at end of file diff --git a/data/src/test/java/com/mkdev/data/datasource/mediator/SurveyRemoteMediatorTest.kt b/data/src/test/java/com/mkdev/data/datasource/mediator/SurveyRemoteMediatorTest.kt index cb53580..eefdd88 100644 --- a/data/src/test/java/com/mkdev/data/datasource/mediator/SurveyRemoteMediatorTest.kt +++ b/data/src/test/java/com/mkdev/data/datasource/mediator/SurveyRemoteMediatorTest.kt @@ -12,7 +12,7 @@ import com.mkdev.data.datasource.local.database.room.dao.SurveyRemoteKeyDao import com.mkdev.data.datasource.local.database.room.entity.SurveyEntity import com.mkdev.data.datasource.local.mapper.SurveyEntityMapper import com.mkdev.data.datasource.remote.api.SurveyApi -import com.mkdev.data.datasource.remote.model.response.base.BaseApiResponse +import com.mkdev.data.datasource.remote.model.response.survey.SurveyResponseList import com.mkdev.data.factory.SurveyEntityFactory import com.mkdev.data.factory.SurveyRemoteKeyEntityFactory import com.mkdev.data.factory.SurveyResponseFactory @@ -26,7 +26,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnitRunner -import retrofit2.Response +import org.mockito.kotlin.anyOrNull @OptIn(ExperimentalPagingApi::class) @RunWith(MockitoJUnitRunner::class) @@ -61,7 +61,7 @@ class SurveyRemoteMediatorTest { fun `refresh load returns success result when more data is present`() = runTest(testDispatcher) { // Given - val surveyResponse = SurveyResponseFactory.createSurveyResponse() + val surveyResponse = SurveyResponseFactory.createSurveyResponseList() val surveyEntity = SurveyEntityFactory.createSurveyEntity() val pagingState = PagingState( listOf(), @@ -70,9 +70,11 @@ class SurveyRemoteMediatorTest { 10 ) `when`(surveyApi.getSurveys(RemoteApiPaging.FIRST_PAGE, RemoteApiPaging.PAGE_SIZE)) - .thenReturn(Response.success(BaseApiResponse(listOf(surveyResponse), null))) + .thenReturn(surveyResponse) `when`(surveyDao.getSurveysCount()).thenReturn(0) - `when`(surveyEntityMapper.mapToSurveyEntity(surveyResponse)).thenReturn(surveyEntity) + `when`(surveyEntityMapper.mapToSurveyEntity(surveyResponse.data[0].data)).thenReturn( + surveyEntity + ) // When val result = surveyRemoteMediator.load(LoadType.REFRESH, pagingState) @@ -93,7 +95,7 @@ class SurveyRemoteMediatorTest { 10 ) `when`(surveyApi.getSurveys(RemoteApiPaging.FIRST_PAGE, RemoteApiPaging.PAGE_SIZE)) - .thenReturn(Response.success(BaseApiResponse(null, null))) + .thenReturn(SurveyResponseList(data = emptyList())) `when`(surveyDao.getSurveysCount()).thenReturn(0) // When @@ -129,9 +131,9 @@ class SurveyRemoteMediatorTest { runTest(testDispatcher) { // Given val remoteKeys = SurveyRemoteKeyEntityFactory.createSurveyRemoteKeyEntity(prevPage = 1) - val surveyResponse = SurveyResponseFactory.createSurveyResponse() + val surveyResponse = SurveyResponseFactory.createSurveyResponseList() val surveyEntity = SurveyEntityFactory.createSurveyEntity() - val pagingState = PagingState( + val pagingState = PagingState( listOf(PagingSource.LoadResult.Page(listOf(surveyEntity), null, 2)), null, PagingConfig(10), @@ -139,8 +141,10 @@ class SurveyRemoteMediatorTest { ) `when`(surveyRemoteKeyDao.remoteKeysId(surveyEntity.id)).thenReturn(remoteKeys) `when`(surveyApi.getSurveys(1, RemoteApiPaging.PAGE_SIZE)) - .thenReturn(Response.success(BaseApiResponse(listOf(surveyResponse), null))) - `when`(surveyEntityMapper.mapToSurveyEntity(surveyResponse)).thenReturn(surveyEntity) + .thenReturn(surveyResponse) + `when`(surveyEntityMapper.mapToSurveyEntity(surveyResponse.data[0].data)).thenReturn( + surveyEntity + ) // When val result = surveyRemoteMediator.load(LoadType.PREPEND, pagingState) @@ -198,19 +202,21 @@ class SurveyRemoteMediatorTest { @Test fun `append load returns success result when more data is present`() = runTest(testDispatcher) { // Given - val remoteKeys = SurveyRemoteKeyEntityFactory.createSurveyRemoteKeyEntity(nextPage = 2) - val surveyResponse = SurveyResponseFactory.createSurveyResponse() + val remoteKeys = SurveyRemoteKeyEntityFactory.createSurveyRemoteKeyEntity() + val surveyResponse = SurveyResponseFactory.createSurveyResponseList() val surveyEntity = SurveyEntityFactory.createSurveyEntity() - val pagingState = PagingState( + val pagingState = PagingState( listOf(PagingSource.LoadResult.Page(listOf(surveyEntity), null, 2)), null, PagingConfig(10), 10 ) - `when`(surveyRemoteKeyDao.remoteKeysId(surveyEntity.id)).thenReturn(remoteKeys) - `when`(surveyApi.getSurveys(2, RemoteApiPaging.PAGE_SIZE)) - .thenReturn(Response.success(BaseApiResponse(listOf(surveyResponse), null))) - `when`(surveyEntityMapper.mapToSurveyEntity(surveyResponse)).thenReturn(surveyEntity) + `when`(surveyRemoteKeyDao.remoteKeysId(anyOrNull())).thenReturn(remoteKeys) + `when`(surveyApi.getSurveys(anyOrNull(), anyOrNull())) + .thenReturn(surveyResponse) + `when`(surveyEntityMapper.mapToSurveyEntity(anyOrNull())).thenReturn( + surveyEntity + ) // When val result = surveyRemoteMediator.load(LoadType.APPEND, pagingState) diff --git a/data/src/test/java/com/mkdev/data/datasource/remote/api/AuthApiTest.kt b/data/src/test/java/com/mkdev/data/datasource/remote/api/AuthApiTest.kt index 657d468..cc3f85c 100644 --- a/data/src/test/java/com/mkdev/data/datasource/remote/api/AuthApiTest.kt +++ b/data/src/test/java/com/mkdev/data/datasource/remote/api/AuthApiTest.kt @@ -1,7 +1,8 @@ package com.mkdev.data.datasource.remote.api -import com.mkdev.data.datasource.remote.model.response.base.BaseApiResponse -import com.mkdev.data.factory.MetaResponseFactory +import com.mkdev.data.datasource.remote.model.response.resetPassword.ResetPasswordMetaResponse +import com.mkdev.data.datasource.remote.model.response.resetPassword.ResetPasswordResponse +import com.mkdev.data.datasource.remote.model.response.resetPassword.ResetPasswordResponseModel import com.mkdev.data.factory.RefreshTokenRequestFactory import com.mkdev.data.factory.ResetPasswordRequestFactory import com.mkdev.data.factory.SignInRequestFactory @@ -32,10 +33,7 @@ class AuthApiTest { // Given val signInRequest = SignInRequestFactory.createSignInRequest() val signInResponse = SignInResponseFactory.createSignInResponse() - val metaResponse = MetaResponseFactory.createMetaResponse() - val response = - Response.success(BaseApiResponse(data = signInResponse, meta = metaResponse)) - coEvery { authApi.signIn(signInRequest) } returns response + coEvery { authApi.signIn(signInRequest) } returns signInResponse // When val result = authApi.signIn(signInRequest) @@ -52,7 +50,7 @@ class AuthApiTest { } ) } - Assert.assertEquals(response, result) + Assert.assertEquals(signInResponse, result) } @Test @@ -61,9 +59,7 @@ class AuthApiTest { // Given val refreshTokenRequest = RefreshTokenRequestFactory.createRefreshTokenRequest() val signInResponse = SignInResponseFactory.createSignInResponse() - val metaResponse = MetaResponseFactory.createMetaResponse() - val response = - Response.success(BaseApiResponse(data = signInResponse, meta = metaResponse)) + val response = Response.success(signInResponse) coEvery { authApi.refreshToken(refreshTokenRequest) } returns response // When @@ -88,8 +84,10 @@ class AuthApiTest { runBlocking { // Given val resetPasswordRequest = ResetPasswordRequestFactory.createResetPasswordRequest() - val metaResponse = MetaResponseFactory.createMetaResponse() - val response = Response.success(BaseApiResponse(data = Unit, meta = metaResponse)) + val response = ResetPasswordResponseModel( + data = ResetPasswordResponse(), + rootMeta = ResetPasswordMetaResponse(message = "") + ) coEvery { authApi.resetPassword(resetPasswordRequest) } returns response // When diff --git a/data/src/test/java/com/mkdev/data/datasource/remote/api/SurveyApiTest.kt b/data/src/test/java/com/mkdev/data/datasource/remote/api/SurveyApiTest.kt index 43df098..3185972 100644 --- a/data/src/test/java/com/mkdev/data/datasource/remote/api/SurveyApiTest.kt +++ b/data/src/test/java/com/mkdev/data/datasource/remote/api/SurveyApiTest.kt @@ -1,19 +1,14 @@ package com.mkdev.data.datasource.remote.api -import com.mkdev.data.datasource.remote.model.response.base.BaseApiResponse -import com.mkdev.data.datasource.remote.model.response.survey.SurveyResponse -import com.mkdev.data.factory.MetaResponseFactory import com.mkdev.data.factory.SurveyResponseFactory import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK import kotlinx.coroutines.runBlocking -import okhttp3.ResponseBody.Companion.toResponseBody import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -import retrofit2.Response class SurveyApiTest { @@ -32,17 +27,14 @@ class SurveyApiTest { val page = 1 val pageSize = 20 val surveyResponses = SurveyResponseFactory.createSurveyResponseList() - val metaResponse = MetaResponseFactory.createMetaResponse() - val expectedResponse = - Response.success(BaseApiResponse(data = surveyResponses, meta = metaResponse)) - coEvery { surveyApi.getSurveys(page, pageSize) } returns expectedResponse + coEvery { surveyApi.getSurveys(page, pageSize) } returns surveyResponses // When val actualResponse = surveyApi.getSurveys(page, pageSize) // Then coVerify(exactly = 1) { surveyApi.getSurveys(page, pageSize) } - assertEquals(expectedResponse, actualResponse) + assertEquals(surveyResponses, actualResponse) } @Test @@ -50,8 +42,8 @@ class SurveyApiTest { // Given val page = 1 val pageSize = 20 - val errorResponse = - Response.error>>(400, "".toResponseBody()) + val errorResponse = SurveyResponseFactory.createSurveyErrorResponse() + coEvery { surveyApi.getSurveys(page, pageSize) } returns errorResponse // When diff --git a/data/src/test/java/com/mkdev/data/datasource/remote/interceptor/AuthInterceptorTest.kt b/data/src/test/java/com/mkdev/data/datasource/remote/interceptor/AuthInterceptorTest.kt index 805c199..d2373bb 100644 --- a/data/src/test/java/com/mkdev/data/datasource/remote/interceptor/AuthInterceptorTest.kt +++ b/data/src/test/java/com/mkdev/data/datasource/remote/interceptor/AuthInterceptorTest.kt @@ -3,9 +3,8 @@ package com.mkdev.data.datasource.remote.interceptor import com.mkdev.data.datasource.local.UserLocal import com.mkdev.data.datasource.local.datastore.UserLocalSource import com.mkdev.data.datasource.remote.api.AuthApi -import com.mkdev.data.datasource.remote.model.response.base.BaseApiResponse -import com.mkdev.data.datasource.remote.model.response.singIn.SignInAttributesResponse import com.mkdev.data.datasource.remote.model.response.singIn.SignInResponse +import com.mkdev.data.datasource.remote.model.response.singIn.SignInResponseModel import com.mkdev.data.utils.ApiConfigs import com.mkdev.data.utils.ClientKeysNdkWrapper import kotlinx.coroutines.flow.flowOf @@ -21,7 +20,10 @@ import org.junit.Before import org.junit.Test import org.mockito.Mock import org.mockito.Mockito -import org.mockito.Mockito.* +import org.mockito.Mockito.anyString +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.kotlin.anyOrNull import java.net.HttpURLConnection @@ -124,19 +126,14 @@ class AuthInterceptorTest { val newAccessToken = "newAccessToken" val refreshToken = "refreshToken" val refreshTokenResponse = RetrofitResponse.success( - BaseApiResponse( + SignInResponseModel( data = SignInResponse( - attributes = SignInAttributesResponse( - accessToken = newAccessToken, - createdAt = "", - expiresIn = "", - refreshToken = refreshToken, - tokenType = "" - ), - id = "", - type = "" - ), - meta = null + accessToken = newAccessToken, + createdAt = 1001001, + expiresIn = 1100110, + refreshToken = refreshToken, + tokenType = "" + ) ) ) @@ -169,7 +166,7 @@ class AuthInterceptorTest { // Given val accessToken = "accessToken" val refreshToken = "refreshToken" - val refreshTokenResponse = RetrofitResponse.error>( + val refreshTokenResponse = RetrofitResponse.error( HttpURLConnection.HTTP_UNAUTHORIZED, RealResponseBody("", 0L, Buffer()) ) diff --git a/data/src/test/java/com/mkdev/data/factory/MetaResponseFactory.kt b/data/src/test/java/com/mkdev/data/factory/MetaResponseFactory.kt deleted file mode 100644 index 42925b3..0000000 --- a/data/src/test/java/com/mkdev/data/factory/MetaResponseFactory.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.mkdev.data.factory - -import com.mkdev.data.datasource.remote.model.response.base.MetaResponse - -object MetaResponseFactory { - fun createMetaResponse( - page: Int? = 1, - pages: Int? = 10, - pageSize: Int? = 20, - records: Int? = 200, - message: String? = "Success" - ) = MetaResponse(page, pages, pageSize, records, message) -} \ No newline at end of file diff --git a/data/src/test/java/com/mkdev/data/factory/ResourceFactory.kt b/data/src/test/java/com/mkdev/data/factory/ResourceFactory.kt deleted file mode 100644 index 10d912b..0000000 --- a/data/src/test/java/com/mkdev/data/factory/ResourceFactory.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.mkdev.data.factory - -import com.mkdev.domain.utils.Resource - -class ResourceFactory { - companion object { - fun loading(): Resource = Resource.Loading() - fun success(data: T?): Resource = Resource.Success(data) - fun error(message: String?, data: T? = null): Resource = - Resource.Error(message, data) - } -} \ No newline at end of file diff --git a/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt b/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt index a850e59..ebbba3e 100644 --- a/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt +++ b/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt @@ -1,26 +1,24 @@ package com.mkdev.data.factory -import com.mkdev.data.datasource.remote.model.response.singIn.SignInAttributesResponse import com.mkdev.data.datasource.remote.model.response.singIn.SignInResponse +import com.mkdev.data.datasource.remote.model.response.singIn.SignInResponseModel object SignInResponseFactory { fun createSignInResponse( accessToken: String = "access_token", refreshToken: String = "refresh_token", - createdAt: String = "created_at", - expiresIn: String = "expires_in", + createdAt: Long = 1001001, + expiresIn: Long = 1000220, tokenType: String = "token_type", - id: String = "id", - type: String = "type" - ): SignInResponse { - val attributes = SignInAttributesResponse( + ): SignInResponseModel { + val data = SignInResponse( accessToken = accessToken, refreshToken = refreshToken, createdAt = createdAt, expiresIn = expiresIn, tokenType = tokenType ) - return SignInResponse(attributes = attributes, id = id, type = type) + return SignInResponseModel(data = data) } } \ No newline at end of file diff --git a/data/src/test/java/com/mkdev/data/factory/SurveyEntityFactory.kt b/data/src/test/java/com/mkdev/data/factory/SurveyEntityFactory.kt index 5bc0cc8..f2f4e22 100644 --- a/data/src/test/java/com/mkdev/data/factory/SurveyEntityFactory.kt +++ b/data/src/test/java/com/mkdev/data/factory/SurveyEntityFactory.kt @@ -5,7 +5,6 @@ import com.mkdev.data.datasource.local.database.room.entity.SurveyEntity object SurveyEntityFactory { fun createSurveyEntity( - id: String = "survey_id", title: String = "Survey Title", description: String = "Survey Description", coverImageUrl: String = "https://example.com/image.jpg", @@ -13,7 +12,6 @@ object SurveyEntityFactory { surveyType: String = "customer_satisfaction" ): SurveyEntity { return SurveyEntity( - id = id, title = title, description = description, coverImageUrl = coverImageUrl, @@ -24,7 +22,7 @@ object SurveyEntityFactory { fun createSurveyEntityList(count: Int = 5): List { return (1..count).map { - createSurveyEntity(id = "survey_id_$it") + createSurveyEntity() } } } \ No newline at end of file diff --git a/data/src/test/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt b/data/src/test/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt index b6b7ae2..8553767 100644 --- a/data/src/test/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt +++ b/data/src/test/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt @@ -4,12 +4,10 @@ import com.mkdev.data.datasource.local.database.room.entity.SurveyRemoteKeyEntit object SurveyRemoteKeyEntityFactory { fun createSurveyRemoteKeyEntity( - surveyId: String = "survey_id", prevPage: Int? = 1, nextPage: Int? = 2 ): SurveyRemoteKeyEntity { return SurveyRemoteKeyEntity( - surveyId = surveyId, prevPage = prevPage, nextPage = nextPage ) @@ -17,7 +15,7 @@ object SurveyRemoteKeyEntityFactory { fun createSurveyRemoteKeyEntityList(count: Int = 5): List { return (1..count).map { - createSurveyRemoteKeyEntity(surveyId = "survey_id_$it") + createSurveyRemoteKeyEntity() } } } \ No newline at end of file diff --git a/data/src/test/java/com/mkdev/data/factory/SurveyResponseFactory.kt b/data/src/test/java/com/mkdev/data/factory/SurveyResponseFactory.kt index 3fc994d..d9569d9 100644 --- a/data/src/test/java/com/mkdev/data/factory/SurveyResponseFactory.kt +++ b/data/src/test/java/com/mkdev/data/factory/SurveyResponseFactory.kt @@ -1,38 +1,49 @@ package com.mkdev.data.factory -import com.mkdev.data.datasource.remote.model.response.survey.SurveyAttributesResponse +import com.infinum.jsonapix.core.resources.DefaultError import com.mkdev.data.datasource.remote.model.response.survey.SurveyResponse +import com.mkdev.data.datasource.remote.model.response.survey.SurveyResponseItem +import com.mkdev.data.datasource.remote.model.response.survey.SurveyResponseList object SurveyResponseFactory { fun createSurveyResponse( - id: String = "survey_id", title: String = "Survey Title", description: String = "Survey Description", coverImageUrl: String = "https://example.com/image.jpg", isActive: Boolean = true, surveyType: String = "customer_satisfaction" - ): SurveyResponse { - return SurveyResponse( - attributes = SurveyAttributesResponse( - activeAt = "", - coverImageUrl = coverImageUrl, - createdAt = "", - description = description, - inactiveAt = Any(), - isActive = isActive, - surveyType = surveyType, - thankEmailAboveThreshold = "", - thankEmailBelowThreshold = "", - title = title - ), - id = id, - type = "" + ): SurveyResponseItem { + val surveyResponseItem = SurveyResponse( + activeAt = "", + coverImageUrl = coverImageUrl, + createdAt = "", + description = description, + inactiveAt = "", + isActive = isActive, + surveyType = surveyType, + thankEmailAboveThreshold = "", + thankEmailBelowThreshold = "", + title = title ) + + return SurveyResponseItem(data = surveyResponseItem) } - fun createSurveyResponseList(count: Int = 5): List { - return (1..count).map { - createSurveyResponse(id = "survey_id_$it") + fun createSurveyResponseList(count: Int = 5): SurveyResponseList { + val surveyResponses = mutableListOf() + + for (i in 1..count) { + surveyResponses.add(createSurveyResponse()) } + return SurveyResponseList(data = surveyResponses) + } + + fun createSurveyErrorResponse(): SurveyResponseList { + val surveyResponses = mutableListOf() + surveyResponses.add(createSurveyResponse()) + return SurveyResponseList( + data = emptyList(), + errors = listOf(DefaultError(code = "", title = "", detail = "", status = "")) + ) } } \ No newline at end of file diff --git a/domain/src/main/java/com/mkdev/domain/model/survey/SurveyModel.kt b/domain/src/main/java/com/mkdev/domain/model/survey/SurveyModel.kt index 1d32f80..c30e9e6 100644 --- a/domain/src/main/java/com/mkdev/domain/model/survey/SurveyModel.kt +++ b/domain/src/main/java/com/mkdev/domain/model/survey/SurveyModel.kt @@ -1,7 +1,6 @@ package com.mkdev.domain.model.survey data class SurveyModel( - val id: String, val title: String, val description: String, val coverImageUrl: String, diff --git a/gradle.properties b/gradle.properties index 20e2a01..260c6e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,6 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +kapt.use.worker.api=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1938739..a9db6bd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] compileSdk = "35" +retrofit2KotlinxSerializationConverter = "1.0.0" targetSdk = "35" minSdk = "23" agp = "8.7.3" @@ -22,7 +23,7 @@ glideCompose = "2.4.4" pagingCompose = "3.3.5" retrofit2 = "2.9.0" okhttp3 = "5.0.0-alpha.12" -kotlinxSerializationJson = "1.6.3" +kotlinxSerializationJson = "1.7.3" lifecycleCompose = "2.8.7" composeRuntimeLivedata = "1.7.6" datastore = "1.1.1" @@ -44,6 +45,7 @@ hiltAndroidTesting = "2.44" testRunner = "1.6.2" uiTestJunit4 = "1.7.6" navigationTesting = "2.8.5" +jsonapix = "1.0.3" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -72,7 +74,6 @@ paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pag paging-common-ktx = { module = "androidx.paging:paging-common-ktx", version.ref = "pagingCompose" } retrofit2 = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit2" } retrofit2-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit2" } -retrofit2-adapter-rxjava2 = { module = "com.squareup.retrofit2:adapter-rxjava2", version.ref = "retrofit2" } okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp3" } okhttp3-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp3" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } @@ -99,6 +100,10 @@ lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = " hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hiltAndroidTesting" } test-runner = { module = "androidx.test:runner", version.ref = "testRunner" } navigation-testing = { module = "androidx.navigation:navigation-testing", version.ref = "navigationTesting" } +jsonapix-core = { module = "com.infinum.jsonapix:core", version.ref = "jsonapix" } +jsonapix-processor = { module = "com.infinum.jsonapix:processor", version.ref = "jsonapix" } +jsonapix-retrofit = { module = "com.infinum.jsonapix:retrofit", version.ref = "jsonapix" } +retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" } [plugins] app-namespace = { id = "com.mkdev.nimblesurvey" } @@ -112,4 +117,6 @@ jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrai dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } protobuf = { id = "com.google.protobuf", version.ref = "protobuf" } +kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 72cbb55..9495ca4 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -4,6 +4,8 @@ plugins { alias(libs.plugins.kotlin.compose) alias(libs.plugins.dagger.hilt) alias(libs.plugins.ksp) + alias(libs.plugins.kotlinx.serialization) + alias(libs.plugins.kotlin.kapt) } android { diff --git a/presentation/src/main/java/com/mkdev/presentation/viewmodel/HomeViewModel.kt b/presentation/src/main/java/com/mkdev/presentation/viewmodel/HomeViewModel.kt index 0738a52..579aa92 100644 --- a/presentation/src/main/java/com/mkdev/presentation/viewmodel/HomeViewModel.kt +++ b/presentation/src/main/java/com/mkdev/presentation/viewmodel/HomeViewModel.kt @@ -16,7 +16,6 @@ internal class HomeViewModel @Inject constructor( getSurveysUseCase: GetSurveysUseCase, ) : ViewModel() { - @OptIn(FlowPreview::class) internal val surveysPaging: Flow> = getSurveysUseCase().cachedIn(viewModelScope) } \ No newline at end of file From 0a351a9272b6f7059f7f857a863bdfeca2f57c9e Mon Sep 17 00:00:00 2001 From: Masoud Khoshkam Date: Sat, 28 Dec 2024 22:27:04 +0330 Subject: [PATCH 2/6] fix unit test cases --- .../data/factory/SignInResponseFactory.kt | 21 ++++++ .../data/repository/AuthRepositoryTest.kt | 72 ++++++++++--------- 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt b/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt index ebbba3e..905ac10 100644 --- a/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt +++ b/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt @@ -1,5 +1,6 @@ package com.mkdev.data.factory +import com.infinum.jsonapix.core.resources.DefaultError import com.mkdev.data.datasource.remote.model.response.singIn.SignInResponse import com.mkdev.data.datasource.remote.model.response.singIn.SignInResponseModel @@ -21,4 +22,24 @@ object SignInResponseFactory { ) return SignInResponseModel(data = data) } + + fun createSignInErrorResponse( + accessToken: String = "access_token", + refreshToken: String = "refresh_token", + createdAt: Long = 1001001, + expiresIn: Long = 1000220, + tokenType: String = "token_type", + ): SignInResponseModel { + val data = SignInResponse( + accessToken = accessToken, + refreshToken = refreshToken, + createdAt = createdAt, + expiresIn = expiresIn, + tokenType = tokenType + ) + return SignInResponseModel( + data = data, + errors = listOf(DefaultError(code = "", title = "", detail = "", status = "")) + ) + } } \ No newline at end of file diff --git a/data/src/test/java/com/mkdev/data/repository/AuthRepositoryTest.kt b/data/src/test/java/com/mkdev/data/repository/AuthRepositoryTest.kt index 12d976f..28bb591 100644 --- a/data/src/test/java/com/mkdev/data/repository/AuthRepositoryTest.kt +++ b/data/src/test/java/com/mkdev/data/repository/AuthRepositoryTest.kt @@ -3,17 +3,18 @@ package com.mkdev.data.repository import androidx.arch.core.executor.testing.InstantTaskExecutorRule import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import com.infinum.jsonapix.core.resources.DefaultError import com.mkdev.data.datasource.local.datastore.UserLocalSource import com.mkdev.data.datasource.local.mapper.SignInMapper import com.mkdev.data.datasource.remote.api.AuthApi -import com.mkdev.data.datasource.remote.model.response.base.BaseApiResponse -import com.mkdev.data.datasource.remote.model.response.singIn.SignInResponse -import com.mkdev.data.factory.MetaResponseFactory +import com.mkdev.data.datasource.remote.model.response.resetPassword.ResetPasswordMetaResponse +import com.mkdev.data.datasource.remote.model.response.resetPassword.ResetPasswordResponse +import com.mkdev.data.datasource.remote.model.response.resetPassword.ResetPasswordResponseModel import com.mkdev.data.factory.SignInRequestFactory import com.mkdev.data.factory.SignInResponseFactory import com.mkdev.data.factory.UserLocalFactory import com.mkdev.data.utils.ApiErrorHandler -import com.mkdev.data.utils.ApiException.HttpError +import com.mkdev.data.utils.ApiException import com.mkdev.data.utils.ClientKeysNdkWrapper import com.mkdev.domain.utils.Resource import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -26,11 +27,12 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.Mock -import org.mockito.Mockito.* +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.kotlin.anyOrNull +import retrofit2.HttpException import retrofit2.Response -import retrofit2.Response as RetrofitResponse @ExperimentalCoroutinesApi class AuthRepositoryTest { @@ -76,18 +78,13 @@ class AuthRepositoryTest { // Given val signInRequest = SignInRequestFactory.createSignInRequest() val signInResponse = SignInResponseFactory.createSignInResponse() - val metaResponse = MetaResponseFactory.createMetaResponse() + val userLocal = UserLocalFactory.createUserLocal() - val apiResponse = RetrofitResponse.success( - BaseApiResponse( - signInResponse, - metaResponse - ) - ) + `when`(clientKeysNdkWrapperMock.getClientId()).thenReturn("mocked_client_id") `when`(clientKeysNdkWrapperMock.getClientSecret()).thenReturn("mocked_client_secret") - `when`(authApi.signIn(anyOrNull())).thenReturn(apiResponse) - `when`(signInMapper.mapToUserLocal(signInResponse)).thenReturn(userLocal) + `when`(authApi.signIn(anyOrNull())).thenReturn(signInResponse) + `when`(signInMapper.mapToUserLocal(signInResponse.data)).thenReturn(userLocal) // When-Then authRepository.signIn( @@ -105,11 +102,16 @@ class AuthRepositoryTest { fun `signIn should emit loading and then error on failure`() = runTest { // Given val signInRequest = SignInRequestFactory.createSignInRequest() - val errorResponse = - Response.error>(400, "client error".toResponseBody()) - val expectedApiException = HttpError(400, "Client error message") - `when`(authApi.signIn(anyOrNull())).thenReturn(errorResponse) + val expectedApiException = ApiException.HttpError(400, "Client error message") + + `when`(authApi.signIn(anyOrNull())).thenThrow( + HttpException( + Response.error(400, + "{\"errors\": [{\"detail\": \"Mocked error message\"}]}".toResponseBody() + ) + ) + ) `when`(apiErrorHandler.handleError(anyOrNull())).thenReturn(expectedApiException) // When-Then @@ -152,17 +154,14 @@ class AuthRepositoryTest { // Given val email = "test@example.com" - val metaResponse = MetaResponseFactory.createMetaResponse() - val apiResponse = RetrofitResponse.success( - BaseApiResponse( - Unit, - metaResponse - ) + val response = ResetPasswordResponseModel( + data = ResetPasswordResponse(), + rootMeta = ResetPasswordMetaResponse(message = "") ) `when`(clientKeysNdkWrapperMock.getClientId()).thenReturn("mocked_client_id") `when`(clientKeysNdkWrapperMock.getClientSecret()).thenReturn("mocked_client_secret") - `when`(authApi.resetPassword(anyOrNull())).thenReturn(apiResponse) + `when`(authApi.resetPassword(anyOrNull())).thenReturn(response) // When-Then authRepository.resetPassword(email).test { @@ -177,14 +176,23 @@ class AuthRepositoryTest { fun `resetPassword should emit loading and error on failure`() = runTest { val email = "test@example.com" - val errorResponse = - Response.error>(400, "client error".toResponseBody()) - val expectedApiException = HttpError(400, "Client error message") + val errorResponse = ResetPasswordResponseModel( + data = ResetPasswordResponse(), + errors = listOf(DefaultError(code = "", title = "", detail = "", status = "")) + ) - `when`(authApi.resetPassword(anyOrNull())).thenReturn(errorResponse) - `when`(apiErrorHandler.handleError(anyOrNull())).thenReturn(expectedApiException) + `when`(authApi.resetPassword(anyOrNull())).thenThrow( + HttpException( + Response.error(400, + "{\"errors\": [{\"detail\": \"Mocked error message\"}]}".toResponseBody() + ) + ) + ) + val expectedApiException = ApiException.HttpError(400, "Client error message") - `when`(authApi.resetPassword(anyOrNull())).thenReturn(errorResponse) + `when`(clientKeysNdkWrapperMock.getClientId()).thenReturn("mocked_client_id") + `when`(clientKeysNdkWrapperMock.getClientSecret()).thenReturn("mocked_client_secret") + `when`(apiErrorHandler.handleError(anyOrNull())).thenReturn(expectedApiException) // When-Then authRepository.resetPassword(email).test { From d4c20bfacdcc008c20f774a2f4da4da0b4dc6dc0 Mon Sep 17 00:00:00 2001 From: Masoud Khoshkam Date: Sat, 28 Dec 2024 22:28:43 +0330 Subject: [PATCH 3/6] remove unused codes --- .../remote/mapper/SurveyDomainMapper.kt | 11 ---------- .../data/factory/SignInResponseFactory.kt | 21 ------------------- .../mkdev/data/factory/SurveyEntityFactory.kt | 6 ------ .../factory/SurveyRemoteKeyEntityFactory.kt | 6 ------ .../data/repository/AuthRepositoryTest.kt | 6 ------ .../presentation/viewmodel/HomeViewModel.kt | 1 - 6 files changed, 51 deletions(-) diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/mapper/SurveyDomainMapper.kt b/data/src/main/java/com/mkdev/data/datasource/remote/mapper/SurveyDomainMapper.kt index ce71392..6ff4af6 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/mapper/SurveyDomainMapper.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/mapper/SurveyDomainMapper.kt @@ -1,21 +1,10 @@ package com.mkdev.data.datasource.remote.mapper import com.mkdev.data.datasource.local.database.room.entity.SurveyEntity -import com.mkdev.data.datasource.remote.model.response.survey.SurveyResponse import com.mkdev.domain.model.survey.SurveyModel import javax.inject.Inject class SurveyDomainMapper @Inject constructor() { - fun mapToSurveyDomainModel(surveyResponse: SurveyResponse): SurveyModel { - return SurveyModel( - title = surveyResponse.title, - description = surveyResponse.description, - coverImageUrl = surveyResponse.coverImageUrl, - isActive = surveyResponse.isActive, - surveyType = surveyResponse.surveyType - ) - } - fun mapToSurveyDomainModel(surveyEntity: SurveyEntity): SurveyModel { return SurveyModel( title = surveyEntity.title, diff --git a/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt b/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt index 905ac10..ebbba3e 100644 --- a/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt +++ b/data/src/test/java/com/mkdev/data/factory/SignInResponseFactory.kt @@ -1,6 +1,5 @@ package com.mkdev.data.factory -import com.infinum.jsonapix.core.resources.DefaultError import com.mkdev.data.datasource.remote.model.response.singIn.SignInResponse import com.mkdev.data.datasource.remote.model.response.singIn.SignInResponseModel @@ -22,24 +21,4 @@ object SignInResponseFactory { ) return SignInResponseModel(data = data) } - - fun createSignInErrorResponse( - accessToken: String = "access_token", - refreshToken: String = "refresh_token", - createdAt: Long = 1001001, - expiresIn: Long = 1000220, - tokenType: String = "token_type", - ): SignInResponseModel { - val data = SignInResponse( - accessToken = accessToken, - refreshToken = refreshToken, - createdAt = createdAt, - expiresIn = expiresIn, - tokenType = tokenType - ) - return SignInResponseModel( - data = data, - errors = listOf(DefaultError(code = "", title = "", detail = "", status = "")) - ) - } } \ No newline at end of file diff --git a/data/src/test/java/com/mkdev/data/factory/SurveyEntityFactory.kt b/data/src/test/java/com/mkdev/data/factory/SurveyEntityFactory.kt index f2f4e22..7c5f383 100644 --- a/data/src/test/java/com/mkdev/data/factory/SurveyEntityFactory.kt +++ b/data/src/test/java/com/mkdev/data/factory/SurveyEntityFactory.kt @@ -19,10 +19,4 @@ object SurveyEntityFactory { surveyType = surveyType ) } - - fun createSurveyEntityList(count: Int = 5): List { - return (1..count).map { - createSurveyEntity() - } - } } \ No newline at end of file diff --git a/data/src/test/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt b/data/src/test/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt index 8553767..47467be 100644 --- a/data/src/test/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt +++ b/data/src/test/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt @@ -12,10 +12,4 @@ object SurveyRemoteKeyEntityFactory { nextPage = nextPage ) } - - fun createSurveyRemoteKeyEntityList(count: Int = 5): List { - return (1..count).map { - createSurveyRemoteKeyEntity() - } - } } \ No newline at end of file diff --git a/data/src/test/java/com/mkdev/data/repository/AuthRepositoryTest.kt b/data/src/test/java/com/mkdev/data/repository/AuthRepositoryTest.kt index 28bb591..1f8ac1f 100644 --- a/data/src/test/java/com/mkdev/data/repository/AuthRepositoryTest.kt +++ b/data/src/test/java/com/mkdev/data/repository/AuthRepositoryTest.kt @@ -3,7 +3,6 @@ package com.mkdev.data.repository import androidx.arch.core.executor.testing.InstantTaskExecutorRule import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import com.infinum.jsonapix.core.resources.DefaultError import com.mkdev.data.datasource.local.datastore.UserLocalSource import com.mkdev.data.datasource.local.mapper.SignInMapper import com.mkdev.data.datasource.remote.api.AuthApi @@ -176,11 +175,6 @@ class AuthRepositoryTest { fun `resetPassword should emit loading and error on failure`() = runTest { val email = "test@example.com" - val errorResponse = ResetPasswordResponseModel( - data = ResetPasswordResponse(), - errors = listOf(DefaultError(code = "", title = "", detail = "", status = "")) - ) - `when`(authApi.resetPassword(anyOrNull())).thenThrow( HttpException( Response.error(400, diff --git a/presentation/src/main/java/com/mkdev/presentation/viewmodel/HomeViewModel.kt b/presentation/src/main/java/com/mkdev/presentation/viewmodel/HomeViewModel.kt index 579aa92..1aa95d5 100644 --- a/presentation/src/main/java/com/mkdev/presentation/viewmodel/HomeViewModel.kt +++ b/presentation/src/main/java/com/mkdev/presentation/viewmodel/HomeViewModel.kt @@ -7,7 +7,6 @@ import androidx.paging.cachedIn import com.mkdev.domain.model.survey.SurveyModel import com.mkdev.domain.usecase.GetSurveysUseCase import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import javax.inject.Inject From 7a7bb24644a3520e21423268d373e0881bb9ee99 Mon Sep 17 00:00:00 2001 From: Masoud Khoshkam Date: Sat, 28 Dec 2024 23:29:03 +0330 Subject: [PATCH 4/6] use older version of Serialization --- gradle/libs.versions.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a9db6bd..46853c7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ glideCompose = "2.4.4" pagingCompose = "3.3.5" retrofit2 = "2.9.0" okhttp3 = "5.0.0-alpha.12" -kotlinxSerializationJson = "1.7.3" +kotlinxSerializationJson = "1.6.2" lifecycleCompose = "2.8.7" composeRuntimeLivedata = "1.7.6" datastore = "1.1.1" @@ -119,4 +119,3 @@ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } protobuf = { id = "com.google.protobuf", version.ref = "protobuf" } kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } - From f8b33babe7808592e446c74c1cc36c8928efb6ee Mon Sep 17 00:00:00 2001 From: Masoud Khoshkam Date: Sun, 29 Dec 2024 00:05:04 +0330 Subject: [PATCH 5/6] fix database test cases --- .../data/datasource/local/database/room/dao/SurveyDaoTest.kt | 2 +- .../local/database/room/dao/SurveyRemoteKeyDaoTest.kt | 2 +- .../java/com/mkdev/data/factory/SurveyEntityFactory.kt | 4 +--- .../com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt | 4 ++-- .../data/datasource/local/database/room/dao/SurveyDao.kt | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDaoTest.kt b/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDaoTest.kt index 595007c..7d7f520 100644 --- a/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDaoTest.kt +++ b/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDaoTest.kt @@ -93,7 +93,7 @@ class SurveyDaoTest { @Test fun getById_should_return_null_when_survey_not_found() = runTest { // Given - val nonexistentId = "nonexistent_id" + val nonexistentId = 100001 // When val retrievedSurvey = surveyDao.getById(nonexistentId) diff --git a/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDaoTest.kt b/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDaoTest.kt index 92cd655..3e51e61 100644 --- a/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDaoTest.kt +++ b/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDaoTest.kt @@ -99,7 +99,7 @@ class SurveyRemoteKeyDaoTest { @Test fun remoteKeysId_should_return_null_when_remote_key_not_found() = runTest(testDispatcher) { // Given - val nonexistentId = "nonexistent_id" + val nonexistentId = 100001 // When val retrievedRemoteKey = surveyRemoteKeyDao.remoteKeysId(nonexistentId) diff --git a/data/src/androidTest/java/com/mkdev/data/factory/SurveyEntityFactory.kt b/data/src/androidTest/java/com/mkdev/data/factory/SurveyEntityFactory.kt index 5bc0cc8..f2f4e22 100644 --- a/data/src/androidTest/java/com/mkdev/data/factory/SurveyEntityFactory.kt +++ b/data/src/androidTest/java/com/mkdev/data/factory/SurveyEntityFactory.kt @@ -5,7 +5,6 @@ import com.mkdev.data.datasource.local.database.room.entity.SurveyEntity object SurveyEntityFactory { fun createSurveyEntity( - id: String = "survey_id", title: String = "Survey Title", description: String = "Survey Description", coverImageUrl: String = "https://example.com/image.jpg", @@ -13,7 +12,6 @@ object SurveyEntityFactory { surveyType: String = "customer_satisfaction" ): SurveyEntity { return SurveyEntity( - id = id, title = title, description = description, coverImageUrl = coverImageUrl, @@ -24,7 +22,7 @@ object SurveyEntityFactory { fun createSurveyEntityList(count: Int = 5): List { return (1..count).map { - createSurveyEntity(id = "survey_id_$it") + createSurveyEntity() } } } \ No newline at end of file diff --git a/data/src/androidTest/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt b/data/src/androidTest/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt index b6b7ae2..a11dd3f 100644 --- a/data/src/androidTest/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt +++ b/data/src/androidTest/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt @@ -4,7 +4,7 @@ import com.mkdev.data.datasource.local.database.room.entity.SurveyRemoteKeyEntit object SurveyRemoteKeyEntityFactory { fun createSurveyRemoteKeyEntity( - surveyId: String = "survey_id", + surveyId: Int = 1, prevPage: Int? = 1, nextPage: Int? = 2 ): SurveyRemoteKeyEntity { @@ -17,7 +17,7 @@ object SurveyRemoteKeyEntityFactory { fun createSurveyRemoteKeyEntityList(count: Int = 5): List { return (1..count).map { - createSurveyRemoteKeyEntity(surveyId = "survey_id_$it") + createSurveyRemoteKeyEntity(surveyId = it) } } } \ No newline at end of file diff --git a/data/src/main/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDao.kt b/data/src/main/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDao.kt index b7be0a7..1b16150 100644 --- a/data/src/main/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDao.kt +++ b/data/src/main/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDao.kt @@ -16,7 +16,7 @@ interface SurveyDao { fun getByPaging(): PagingSource @Query("SELECT * FROM survey_table WHERE id =:id LIMIT 1") - suspend fun getById(id: String): SurveyEntity + suspend fun getById(id: Int): SurveyEntity @Query("DELETE FROM survey_table") suspend fun clearAll() From 99ef652b1ebb86aa7520e150e9c2ae20e7337bca Mon Sep 17 00:00:00 2001 From: Masoud Khoshkam Date: Sun, 29 Dec 2024 12:11:08 +0330 Subject: [PATCH 6/6] try to fix reset password issue --- .../response/resetPassword/ResetPasswordResponse.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/data/src/main/java/com/mkdev/data/datasource/remote/model/response/resetPassword/ResetPasswordResponse.kt b/data/src/main/java/com/mkdev/data/datasource/remote/model/response/resetPassword/ResetPasswordResponse.kt index 826d2c6..2671d7f 100644 --- a/data/src/main/java/com/mkdev/data/datasource/remote/model/response/resetPassword/ResetPasswordResponse.kt +++ b/data/src/main/java/com/mkdev/data/datasource/remote/model/response/resetPassword/ResetPasswordResponse.kt @@ -7,14 +7,13 @@ import com.infinum.jsonapix.core.resources.Meta import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -@SerialName("resetPassword") -@JsonApiX("resetPassword") @Serializable -data class ResetPasswordResponse(val test: String? = null) : JsonApiModel() +@JsonApiX("resetPassword", isNullable = true) +class ResetPasswordResponse : JsonApiModel() -@SerialName("resetPassword") @Serializable @JsonApiXMeta("resetPassword") data class ResetPasswordMetaResponse( + @SerialName("message") val message: String -) : Meta \ No newline at end of file +) : Meta