Updated on 2026-08-14
This commit is contained in:
parent
0b8ff3e56e
commit
9b7bbe2564
8 changed files with 212 additions and 6 deletions
|
|
@ -5,8 +5,12 @@ plugins {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.notifications.models)
|
||||
|
||||
/* Tests */
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.mockk)
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.domain.notifications
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
class GetApplicationIdUseCase(
|
||||
private val notificationsRepository: NotificationsRepository,
|
||||
) {
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend operator fun invoke(): Either<Throwable, String> = Either.catch {
|
||||
val localApplicationId = notificationsRepository.getApplicationId()
|
||||
if (localApplicationId != null) return@catch localApplicationId
|
||||
|
||||
mutex.withLock {
|
||||
val doubleCheckedId = notificationsRepository.getApplicationId()
|
||||
if (doubleCheckedId != null) return@withLock doubleCheckedId
|
||||
|
||||
val newApplicationId = notificationsRepository.createApplicationId()
|
||||
notificationsRepository.saveApplicationId(newApplicationId)
|
||||
newApplicationId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.domain.notifications
|
||||
package com.tangem.domain.notifications.repository
|
||||
|
||||
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
|
||||
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package com.tangem.domain.notifications
|
||||
|
||||
import arrow.core.Either
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.net.SocketTimeoutException
|
||||
|
||||
class GetApplicationIdUseCaseTest {
|
||||
|
||||
private val notificationsRepository: NotificationsRepository = mockk()
|
||||
private val useCase = GetApplicationIdUseCase(notificationsRepository)
|
||||
|
||||
@Test
|
||||
fun `GIVEN local application ID exists WHEN invoke THEN return local application ID`() = runTest {
|
||||
// GIVEN
|
||||
val expectedApplicationId = "test-app-id"
|
||||
coEvery { notificationsRepository.getApplicationId() } returns expectedApplicationId
|
||||
|
||||
// WHEN
|
||||
val result = useCase()
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isEqualTo(expectedApplicationId)
|
||||
coVerify(exactly = 1) { notificationsRepository.getApplicationId() }
|
||||
coVerify(inverse = true) {
|
||||
notificationsRepository.createApplicationId()
|
||||
notificationsRepository.saveApplicationId(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN local application ID does not exist WHEN invoke THEN create and save new application ID`() = runTest {
|
||||
// GIVEN
|
||||
val newApplicationId = "new-app-id"
|
||||
coEvery { notificationsRepository.getApplicationId() } returns null
|
||||
coEvery { notificationsRepository.createApplicationId() } returns newApplicationId
|
||||
coEvery { notificationsRepository.saveApplicationId(newApplicationId) } returns Unit
|
||||
|
||||
// WHEN
|
||||
val result = useCase()
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isEqualTo(newApplicationId)
|
||||
coVerifyOrder {
|
||||
notificationsRepository.getApplicationId()
|
||||
notificationsRepository.getApplicationId()
|
||||
notificationsRepository.createApplicationId()
|
||||
notificationsRepository.saveApplicationId(newApplicationId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN repository throws exception WHEN invoke THEN return Either Left with error`() = runTest {
|
||||
// GIVEN
|
||||
val expectedError = SocketTimeoutException("Test error")
|
||||
coEvery { notificationsRepository.getApplicationId() } throws expectedError
|
||||
|
||||
// WHEN
|
||||
val result = useCase()
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(Either.Left::class.java)
|
||||
assertThat((result as Either.Left).value).isEqualTo(expectedError)
|
||||
coVerify(exactly = 1) { notificationsRepository.getApplicationId() }
|
||||
coVerify(inverse = true) {
|
||||
notificationsRepository.createApplicationId()
|
||||
notificationsRepository.saveApplicationId(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no local application ID WHEN multiple concurrent invokes THEN create only one application ID`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
val newApplicationId = "new-app-id"
|
||||
var isIdCreated = false
|
||||
|
||||
coEvery { notificationsRepository.getApplicationId() } answers {
|
||||
if (!isIdCreated) null else newApplicationId
|
||||
}
|
||||
coEvery { notificationsRepository.createApplicationId() } answers {
|
||||
isIdCreated = true
|
||||
newApplicationId
|
||||
}
|
||||
coEvery { notificationsRepository.saveApplicationId(newApplicationId) } returns Unit
|
||||
|
||||
// WHEN
|
||||
val results = coroutineScope {
|
||||
List(PARALLEL_COUNT) {
|
||||
async {
|
||||
delay(100)
|
||||
useCase()
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
|
||||
// THEN
|
||||
results.forEach { result ->
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isEqualTo(newApplicationId)
|
||||
}
|
||||
coVerify(exactly = PARALLEL_COUNT + 1) { notificationsRepository.getApplicationId() }
|
||||
coVerify(exactly = 1) { notificationsRepository.createApplicationId() }
|
||||
coVerify(exactly = 1) { notificationsRepository.saveApplicationId(newApplicationId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no local application ID WHEN multiple concurrent invokes with delay THEN create only one application ID`() = runTest {
|
||||
// GIVEN
|
||||
val newApplicationId = "new-app-id"
|
||||
var isIdCreated = false
|
||||
|
||||
coEvery { notificationsRepository.getApplicationId() } answers {
|
||||
if (!isIdCreated) null else newApplicationId
|
||||
}
|
||||
coEvery { notificationsRepository.createApplicationId() } answers {
|
||||
isIdCreated = true
|
||||
newApplicationId
|
||||
}
|
||||
coEvery { notificationsRepository.saveApplicationId(newApplicationId) } returns Unit
|
||||
|
||||
// WHEN
|
||||
val results = coroutineScope {
|
||||
List(PARALLEL_COUNT) {
|
||||
async {
|
||||
delay(100)
|
||||
useCase()
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
|
||||
// THEN
|
||||
results.forEach { result ->
|
||||
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||
assertThat((result as Either.Right).value).isEqualTo(newApplicationId)
|
||||
}
|
||||
coVerify(exactly = PARALLEL_COUNT + 1) { notificationsRepository.getApplicationId() }
|
||||
coVerify(exactly = 1) { notificationsRepository.createApplicationId() }
|
||||
coVerify(exactly = 1) { notificationsRepository.saveApplicationId(newApplicationId) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PARALLEL_COUNT = 100
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue