diff --git a/shared/src/androidMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateTimeFormatter.kt b/shared/src/androidMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateTimeFormatter.kt new file mode 100644 index 00000000..203b18de --- /dev/null +++ b/shared/src/androidMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateTimeFormatter.kt @@ -0,0 +1,12 @@ +package co.nimblehq.blisskmmic.domain.platform.datetime + +actual class DateTimeFormatterImpl: DateTimeFormatter { + + actual override fun getFormattedString( + secondsSinceEpoch: Long, + format: DateFormat + ): String { + // TODO: implement date for Android + return "" + } +} diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/UserApiModel.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/UserApiModel.kt index ea5f5389..317d4d3a 100644 --- a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/UserApiModel.kt +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/model/UserApiModel.kt @@ -13,7 +13,7 @@ data class UserApiModel( @SerialName("avatar_url") val avatarUrl: String ) { - fun toUser() = User( + fun toUser(): User = User( name, avatarUrl ) diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/datasource/NetworkDataSource.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/datasource/NetworkDataSource.kt index 43cea69e..786274bd 100644 --- a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/datasource/NetworkDataSource.kt +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/datasource/NetworkDataSource.kt @@ -18,7 +18,7 @@ interface NetworkDataSource { fun logIn(target: LoginTargetType): Flow fun resetPassword(target: ResetPasswordTargetType): Flow fun survey(target: SurveySelectionTargetType): Flow, PaginationMetaApiModel>> - fun getProfile(target: UserProfileTargetType): Flow + fun profile(target: UserProfileTargetType): Flow } class NetworkDataSourceImpl(private val networkClient: NetworkClient): NetworkDataSource { @@ -36,7 +36,7 @@ class NetworkDataSourceImpl(private val networkClient: NetworkClient): NetworkDa return networkClient.fetchWithMeta(target.requestBuilder()) } - override fun getProfile(target: UserProfileTargetType): Flow { + override fun profile(target: UserProfileTargetType): Flow { return networkClient.fetch(target.requestBuilder()) } diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/target/UserProfileTargetType.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/target/UserProfileTargetType.kt index e97f1161..981bf6b8 100644 --- a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/target/UserProfileTargetType.kt +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/network/target/UserProfileTargetType.kt @@ -3,7 +3,7 @@ package co.nimblehq.blisskmmic.data.network.target import co.nimblehq.blisskmmic.data.network.helpers.TargetType import io.ktor.http.* -class UserProfileTargetType(): TargetType { +class UserProfileTargetType: TargetType { override var path = "me" override var method = HttpMethod.Get diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/repository/UserRepositoryImpl.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/repository/UserRepositoryImpl.kt index 2cf163fe..8da2b149 100644 --- a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/repository/UserRepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/data/repository/UserRepositoryImpl.kt @@ -13,7 +13,7 @@ class UserRepositoryImpl( override fun getProfile(): Flow { return networkDataSource - .getProfile(UserProfileTargetType()) + .profile(UserProfileTargetType()) .map { it.toUser() } } } diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateFormat.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateFormat.kt new file mode 100644 index 00000000..7a9d4289 --- /dev/null +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateFormat.kt @@ -0,0 +1,5 @@ +package co.nimblehq.blisskmmic.domain.platform.datetime + +sealed class DateFormat(val value: String) { + object DayOfWeekMonthDay: DateFormat("EEEE, MMMM d") +} diff --git a/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateTimeFormatter.kt b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateTimeFormatter.kt new file mode 100644 index 00000000..cea30528 --- /dev/null +++ b/shared/src/commonMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateTimeFormatter.kt @@ -0,0 +1,11 @@ +package co.nimblehq.blisskmmic.domain.platform.datetime + +interface DateTimeFormatter { + + fun getFormattedString(secondsSinceEpoch: Long, format: DateFormat): String +} + +expect class DateTimeFormatterImpl(): DateTimeFormatter { + + override fun getFormattedString(secondsSinceEpoch: Long, format: DateFormat): String +} diff --git a/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/datasource/NetworkDataSourceTest.kt b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/datasource/NetworkDataSourceTest.kt index ec439b2a..e67fd394 100644 --- a/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/datasource/NetworkDataSourceTest.kt +++ b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/datasource/NetworkDataSourceTest.kt @@ -6,10 +6,8 @@ import co.nimblehq.blisskmmic.data.network.datasource.NetworkDataSourceImpl import co.nimblehq.blisskmmic.data.network.target.LoginTargetType import co.nimblehq.blisskmmic.data.network.target.ResetPasswordTargetType import co.nimblehq.blisskmmic.data.network.target.SurveySelectionTargetType -import co.nimblehq.blisskmmic.helpers.json.ERROR_JSON_RESULT -import co.nimblehq.blisskmmic.helpers.json.LOG_IN_JSON_RESULT -import co.nimblehq.blisskmmic.helpers.json.RESET_PASSWORD_JSON_RESULT -import co.nimblehq.blisskmmic.helpers.json.SURVEY_LIST_JSON_RESULT +import co.nimblehq.blisskmmic.data.network.target.UserProfileTargetType +import co.nimblehq.blisskmmic.helpers.json.* import co.nimblehq.blisskmmic.helpers.mock.ktor.jsonMockEngine import co.nimblehq.jsonapi.model.JsonApiException import io.kotest.matchers.collections.shouldContain @@ -101,4 +99,21 @@ class NetworkDataSourceTest { } } } + + // Profile + + @Test + fun `When calling profile with success response, it returns correct object`() = runTest { + val engine = jsonMockEngine(USER_PROFILE_JSON_RESULT, "me") + val networkClient = NetworkClient(engine = engine) + val dataSource = NetworkDataSourceImpl(networkClient) + dataSource + .profile(UserProfileTargetType()) + .test { + val response = awaitItem() + response.email shouldBe "mail@mail.com" + response.name shouldBe "Name" + awaitComplete() + } + } } diff --git a/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/repository/DeviceInfoRepositoryTest.kt b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/repository/DeviceInfoRepositoryTest.kt new file mode 100644 index 00000000..2e85eee6 --- /dev/null +++ b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/repository/DeviceInfoRepositoryTest.kt @@ -0,0 +1,43 @@ +package co.nimblehq.blisskmmic.data.repository + +import app.cash.turbine.test +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import kotlinx.datetime.* +import org.kodein.mock.Mocker +import org.kodein.mock.UsesMocks +import kotlin.test.BeforeTest +import kotlin.test.Test + +@UsesMocks(Clock::class) +@ExperimentalCoroutinesApi +class DeviceInfoRepositoryTest { + + private val mocker = Mocker() + private val clock = MockClock(mocker) + private val instant = Instant.DISTANT_PAST + private val deviceInfoRepository = DeviceInfoRepositoryImpl(clock) + + @BeforeTest + fun setUp() { + mocker.reset() + } + + @Test + fun `When calling profile with success response, it returns correct object`() = runTest { + mocker.every { + clock.now() + } returns instant + deviceInfoRepository + .getCurrentDate() + .test { + val item = awaitItem() + val local = instant.toLocalDateTime(TimeZone.currentSystemDefault()) + item.day shouldBe local.dayOfMonth + item.month shouldBe local.monthNumber + item.dayOfWeek shouldBe local.dayOfWeek.isoDayNumber + awaitComplete() + } + } +} diff --git a/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/repository/UserRepositoryTest.kt b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/repository/UserRepositoryTest.kt new file mode 100644 index 00000000..424ff599 --- /dev/null +++ b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/data/repository/UserRepositoryTest.kt @@ -0,0 +1,60 @@ +package co.nimblehq.blisskmmic.data.repository + +import app.cash.turbine.test +import co.nimblehq.blisskmmic.data.model.UserApiModel +import co.nimblehq.blisskmmic.data.model.fakeUserApiModel +import co.nimblehq.blisskmmic.data.network.datasource.MockNetworkDataSource +import co.nimblehq.blisskmmic.data.network.datasource.NetworkDataSource +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.kodein.mock.Mocker +import org.kodein.mock.UsesFakes +import org.kodein.mock.UsesMocks +import kotlin.test.BeforeTest +import kotlin.test.Test + +@UsesMocks(NetworkDataSource::class) +@UsesFakes(UserApiModel::class) +@ExperimentalCoroutinesApi +class UserRepositoryTest { + + private val mocker = Mocker() + private val networkDataSource = MockNetworkDataSource(mocker) + private val user = fakeUserApiModel() + private val userRepository = UserRepositoryImpl(networkDataSource) + + @BeforeTest + fun setUp() { + mocker.reset() + } + + @Test + fun `When calling getProfile with success response, it returns correct object`() = runTest { + mocker.every { + networkDataSource.profile(isAny()) + } returns flowOf(user) + userRepository + .getProfile() + .test { + val item = awaitItem() + item.name shouldBe user.name + item.avatarUrl shouldBe user.avatarUrl + awaitComplete() + } + } + + @Test + fun `When calling getProfile with failure response, it returns correct error`() = runTest { + mocker.every { + networkDataSource.profile(isAny()) + } returns flow { error("Fail") } + userRepository + .getProfile() + .test { + awaitError().message shouldBe "Fail" + } + } +} diff --git a/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/domain/usecase/GetCurrentDateUseCaseTest.kt b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/domain/usecase/GetCurrentDateUseCaseTest.kt new file mode 100644 index 00000000..b62f88ed --- /dev/null +++ b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/domain/usecase/GetCurrentDateUseCaseTest.kt @@ -0,0 +1,58 @@ +package co.nimblehq.blisskmmic.domain.usecase + +import app.cash.turbine.test +import co.nimblehq.blisskmmic.domain.model.DateComponents +import co.nimblehq.blisskmmic.domain.repository.DeviceInfoRepository +import co.nimblehq.blisskmmic.domain.repository.MockDeviceInfoRepository +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.kodein.mock.Mocker +import org.kodein.mock.UsesMocks +import kotlin.test.BeforeTest +import kotlin.test.Test + +@UsesMocks(DeviceInfoRepository::class) +@ExperimentalCoroutinesApi +class GetCurrentDateUseCaseTest { + + private val mocker = Mocker() + private val deviceInfoRepository = MockDeviceInfoRepository(mocker) + private val getCurrentDateUseCase = GetCurrentDateUseCaseImpl(deviceInfoRepository) + private val dateComponents = DateComponents(day = 2, month = 2, dayOfWeek = 3) + + @BeforeTest + fun setUp() { + mocker.reset() + } + + @Test + fun `When calling getCurrent with a success response, it returns correct object`() = runTest { + mocker.every { + deviceInfoRepository.getCurrentDate() + } returns flowOf(dateComponents) + + getCurrentDateUseCase() + .test { + val item = awaitItem() + item.day shouldBe 2 + item.dayOfWeek shouldBe "WEDNESDAY" + item.month shouldBe "FEBRUARY" + awaitComplete() + } + } + + @Test + fun `When calling getCurrent with a failure response, it returns correct error`() = runTest { + mocker.every { + deviceInfoRepository.getCurrentDate() + } returns flow { error("Fail") } + + getCurrentDateUseCase() + .test { + awaitError().message shouldBe "Fail" + } + } +} diff --git a/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/domain/usecase/GetProfileUseCaseTest.kt b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/domain/usecase/GetProfileUseCaseTest.kt new file mode 100644 index 00000000..d0984fa1 --- /dev/null +++ b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/domain/usecase/GetProfileUseCaseTest.kt @@ -0,0 +1,63 @@ +package co.nimblehq.blisskmmic.domain.usecase + +import app.cash.turbine.test +import co.nimblehq.blisskmmic.domain.model.PaginationMeta +import co.nimblehq.blisskmmic.domain.model.Survey +import co.nimblehq.blisskmmic.domain.model.User +import co.nimblehq.blisskmmic.domain.model.fakeUser +import co.nimblehq.blisskmmic.domain.repository.MockUserRepository +import co.nimblehq.blisskmmic.domain.repository.SurveyRepository +import co.nimblehq.blisskmmic.domain.repository.UserRepository +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.kodein.mock.Mocker +import org.kodein.mock.UsesFakes +import org.kodein.mock.UsesMocks +import kotlin.test.BeforeTest +import kotlin.test.Test + +@OptIn(ExperimentalCoroutinesApi::class) +@UsesMocks(UserRepository::class) +@UsesFakes(User::class) +class GetProfileUseCaseTest { + + private val mocker = Mocker() + private val userRepository = MockUserRepository(mocker) + private val user = fakeUser() + private val getProfileUseCase = GetProfileUseCaseImpl(userRepository) + + @BeforeTest + fun setUp() { + mocker.reset() + } + + @Test + fun `When calling getProfile with a success response, it returns correct object`() = runTest { + mocker.every { + userRepository.getProfile() + } returns flowOf(user) + + getProfileUseCase() + .test { + val item = awaitItem() + item.name shouldBe user.name + item.avatarUrl shouldBe user.avatarUrl + awaitComplete() + } + } + + @Test + fun `When calling getProfile with a failure response, it returns correct error`() = runTest { + mocker.every { + userRepository.getProfile() + } returns flow { error("Fail") } + + getProfileUseCase() + .test { + awaitError().message shouldBe "Fail" + } + } +} diff --git a/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/helpers/json/UserProfileJsonResult.kt b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/helpers/json/UserProfileJsonResult.kt new file mode 100644 index 00000000..a757ca56 --- /dev/null +++ b/shared/src/commonTest/kotlin/co/nimblehq/blisskmmic/helpers/json/UserProfileJsonResult.kt @@ -0,0 +1,15 @@ +package co.nimblehq.blisskmmic.helpers.json + +const val USER_PROFILE_JSON_RESULT = """ +{ + "data": { + "id": "1", + "type": "user", + "attributes": { + "name": "Name", + "email": "mail@mail.com", + "avatar_url": "https://api.adorable.io/avatar/mail@mail.com" + } + } +} +""" diff --git a/shared/src/iosMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateFormatter.kt b/shared/src/iosMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateFormatter.kt new file mode 100644 index 00000000..baf2745d --- /dev/null +++ b/shared/src/iosMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateFormatter.kt @@ -0,0 +1,29 @@ +package co.nimblehq.blisskmmic.domain.platform.datetime + +import platform.Foundation.* + +class DateFormatter { + + companion object { + + var dateFormatters:MutableMap = mutableMapOf() + + fun get(dateFormat: DateFormat): NSDateFormatter { + return initializeIfNeeded(dateFormat) + } + + private fun dateFormatter(dataFormat: DateFormat): NSDateFormatter { + return NSDateFormatter() + .also { + it.dateFormat = dataFormat.value + } + } + + private fun initializeIfNeeded(dateFormat: DateFormat): NSDateFormatter { + dateFormatters[dateFormat]?.let { return it } + val dateFormatter = dateFormatter(dateFormat) + dateFormatters[dateFormat] = dateFormatter + return dateFormatter + } + } +} diff --git a/shared/src/iosMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateTimeFormatterImpl.kt b/shared/src/iosMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateTimeFormatterImpl.kt new file mode 100644 index 00000000..ab33609b --- /dev/null +++ b/shared/src/iosMain/kotlin/co/nimblehq/blisskmmic/domain/platform/datetime/DateTimeFormatterImpl.kt @@ -0,0 +1,14 @@ +package co.nimblehq.blisskmmic.domain.platform.datetime + +import platform.Foundation.NSDate + +actual class DateTimeFormatterImpl: DateTimeFormatter { + + actual override fun getFormattedString( + secondsSinceEpoch: Long, + format: DateFormat + ): String { + val date = NSDate(secondsSinceEpoch.toDouble()) + return DateFormatter.get(format).stringFromDate(date) + } +} diff --git a/shared/src/iosTest/kotlin/co/nimblehq/blisskmmic/IosGreetingTest.kt b/shared/src/iosTest/kotlin/co/nimblehq/blisskmmic/IosGreetingTest.kt index 2e08d96f..6e44c2e2 100644 --- a/shared/src/iosTest/kotlin/co/nimblehq/blisskmmic/IosGreetingTest.kt +++ b/shared/src/iosTest/kotlin/co/nimblehq/blisskmmic/IosGreetingTest.kt @@ -7,6 +7,6 @@ class IosGreetingTest { @Test fun testExample() { - assertTrue(Greeting().greeting().contains("iOS"), "Check iOS is mentioned") + assertTrue(Greeting().greeting().contains("iOasdfasdfS"), "Check iOS is mentioned") } -} \ No newline at end of file +}