Skip to content

Commit 72860c0

Browse files
DavidDavid
authored andcommitted
Price Change Alert tests
1 parent 29e556b commit 72860c0

2 files changed

Lines changed: 272 additions & 0 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.davidcrespo.onewallet.data.repository
2+
3+
import android.content.SharedPreferences
4+
import io.mockk.every
5+
import io.mockk.mockk
6+
import io.mockk.mockkStatic
7+
import io.mockk.unmockkStatic
8+
import io.mockk.verify
9+
import kotlinx.coroutines.test.runTest
10+
import org.junit.jupiter.api.AfterEach
11+
import org.junit.jupiter.api.Assertions.assertFalse
12+
import org.junit.jupiter.api.Assertions.assertTrue
13+
import org.junit.jupiter.api.BeforeEach
14+
import org.junit.jupiter.api.Test
15+
import java.time.LocalDate
16+
17+
class PriceAlertNotificationRepositoryImplTest {
18+
19+
private val sharedPreferences = mockk<SharedPreferences>(relaxed = true)
20+
private val editor = mockk<SharedPreferences.Editor>(relaxed = true)
21+
private lateinit var repository: PriceAlertNotificationRepositoryImpl
22+
private val fixedDate = LocalDate.of(2026, 4, 25)
23+
24+
@BeforeEach
25+
fun setUp() {
26+
mockkStatic(LocalDate::class)
27+
every { LocalDate.now() } returns fixedDate
28+
every { sharedPreferences.edit() } returns editor
29+
every { editor.putString(any(), any()) } returns editor
30+
31+
repository = PriceAlertNotificationRepositoryImpl(sharedPreferences)
32+
}
33+
34+
@AfterEach
35+
fun tearDown() {
36+
unmockkStatic(LocalDate::class)
37+
}
38+
39+
@Test
40+
fun `should return true when symbol was notified today`() = runTest {
41+
// Given
42+
val symbol = "AAPL"
43+
val key = "alert_last_date_$symbol"
44+
every { sharedPreferences.getString(key, null) } returns fixedDate.toString()
45+
46+
// When
47+
val result = repository.wasNotifiedToday(symbol)
48+
49+
// Then
50+
assertTrue(result)
51+
verify { sharedPreferences.getString(key, null) }
52+
}
53+
54+
@Test
55+
fun `should return false when symbol was not notified today`() = runTest {
56+
// Given
57+
val symbol = "AAPL"
58+
val key = "alert_last_date_$symbol"
59+
every { sharedPreferences.getString(key, null) } returns "2026-04-24"
60+
61+
// When
62+
val result = repository.wasNotifiedToday(symbol)
63+
64+
// Then
65+
assertFalse(result)
66+
}
67+
68+
@Test
69+
fun `should return false when symbol has no notification date recorded`() = runTest {
70+
// Given
71+
val symbol = "AAPL"
72+
val key = "alert_last_date_$symbol"
73+
every { sharedPreferences.getString(key, null) } returns null
74+
75+
// When
76+
val result = repository.wasNotifiedToday(symbol)
77+
78+
// Then
79+
assertFalse(result)
80+
}
81+
82+
@Test
83+
fun `should mark symbol as notified today`() = runTest {
84+
// Given
85+
val symbol = "AAPL"
86+
val key = "alert_last_date_$symbol"
87+
val todayStr = fixedDate.toString()
88+
89+
// When
90+
repository.markNotifiedToday(symbol)
91+
92+
// Then
93+
verify { editor.putString(key, todayStr) }
94+
verify { editor.apply() }
95+
}
96+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package com.davidcrespo.onewallet.domain.usecase.portfolio
2+
3+
import com.davidcrespo.onewallet.domain.model.investment.Currency
4+
import com.davidcrespo.onewallet.domain.model.investment.Investment
5+
import com.davidcrespo.onewallet.domain.model.investment.InvestmentType
6+
import com.davidcrespo.onewallet.domain.repository.FinancialRepository
7+
import io.mockk.coEvery
8+
import io.mockk.coVerify
9+
import io.mockk.every
10+
import io.mockk.mockk
11+
import kotlinx.coroutines.flow.flowOf
12+
import kotlinx.coroutines.test.runTest
13+
import org.junit.jupiter.api.Assertions.assertEquals
14+
import org.junit.jupiter.api.BeforeEach
15+
import org.junit.jupiter.api.Test
16+
17+
class RefreshPortfolioPricesUseCaseTest {
18+
19+
private val getPortfolioItemsUseCase = mockk<GetPortfolioItemsUseCase>()
20+
private val getInvestmentPriceUseCase = mockk<GetInvestmentPriceUseCase>()
21+
private val financialRepository = mockk<FinancialRepository>()
22+
private val saveMonthlyPortfolioUseCase = mockk<SaveMonthlyPortfolioUseCase>()
23+
24+
private lateinit var useCase: RefreshPortfolioPricesUseCase
25+
26+
private val selectedCurrency = Currency("EUR")
27+
28+
@BeforeEach
29+
fun setUp() {
30+
useCase = RefreshPortfolioPricesUseCase(
31+
getPortfolioItemsUseCase,
32+
getInvestmentPriceUseCase,
33+
financialRepository,
34+
saveMonthlyPortfolioUseCase
35+
)
36+
every { financialRepository.getSelectedCurrency() } returns selectedCurrency
37+
coEvery { saveMonthlyPortfolioUseCase(any()) } returns Unit
38+
}
39+
40+
@Test
41+
fun `should return empty list when portfolio is empty`() = runTest {
42+
// Given
43+
every { getPortfolioItemsUseCase() } returns flowOf(emptyList())
44+
45+
// When
46+
val result = useCase()
47+
48+
// Then
49+
assertEquals(0, result.size)
50+
coVerify(exactly = 0) { saveMonthlyPortfolioUseCase(any()) }
51+
}
52+
53+
@Test
54+
fun `should update market items and save portfolio`() = runTest {
55+
// Given
56+
val marketItem = Investment(
57+
symbol = "AAPL",
58+
name = "Apple",
59+
quantity = 1.0,
60+
price = 150.0,
61+
previousPrice = 140.0,
62+
currency = Currency("USD"),
63+
type = InvestmentType.STOCK,
64+
year = 2026,
65+
month = 4
66+
)
67+
val updatedPrice = 160.0
68+
val updatedItem = marketItem.copy(price = updatedPrice, previousPrice = 150.0)
69+
70+
every { getPortfolioItemsUseCase() } returns flowOf(listOf(marketItem))
71+
coEvery {
72+
getInvestmentPriceUseCase(
73+
symbol = marketItem.symbol,
74+
type = marketItem.type,
75+
name = marketItem.name,
76+
selectedCurrency = selectedCurrency,
77+
investmentCurrency = marketItem.currency,
78+
preferredApi = marketItem.preferredApi
79+
)
80+
} returns Result.success(updatedItem)
81+
82+
// When
83+
val result = useCase()
84+
85+
// Then
86+
assertEquals(1, result.size)
87+
assertEquals(updatedPrice, result[0].first.price)
88+
// (160 - 150) / 150 * 100 = 6.666...
89+
assertEquals(6.666666666666667, result[0].second)
90+
91+
coVerify { saveMonthlyPortfolioUseCase(listOf(updatedItem)) }
92+
}
93+
94+
@Test
95+
fun `should handle mixed market and manual items`() = runTest {
96+
// Given
97+
val marketItem = Investment(
98+
symbol = "BTC",
99+
name = "Bitcoin",
100+
quantity = 0.1,
101+
price = 50000.0,
102+
previousPrice = 49000.0,
103+
currency = Currency("USD"),
104+
type = InvestmentType.CRYPTO,
105+
year = 2026,
106+
month = 4
107+
)
108+
val manualItem = Investment(
109+
symbol = "CASH",
110+
name = "Savings",
111+
quantity = 1000.0,
112+
price = 1.0,
113+
previousPrice = 1.0,
114+
currency = Currency("EUR"),
115+
type = InvestmentType.BANK,
116+
year = 2026,
117+
month = 4
118+
)
119+
val updatedPrice = 51000.0
120+
val updatedMarketItem = marketItem.copy(price = updatedPrice, previousPrice = 50000.0)
121+
122+
every { getPortfolioItemsUseCase() } returns flowOf(listOf(marketItem, manualItem))
123+
coEvery {
124+
getInvestmentPriceUseCase(
125+
symbol = marketItem.symbol,
126+
type = marketItem.type,
127+
name = marketItem.name,
128+
selectedCurrency = selectedCurrency,
129+
investmentCurrency = marketItem.currency,
130+
preferredApi = marketItem.preferredApi
131+
)
132+
} returns Result.success(updatedMarketItem)
133+
134+
// When
135+
val result = useCase()
136+
137+
// Then
138+
assertEquals(1, result.size) // invoke returns updatedMarketItems
139+
assertEquals(updatedPrice, result[0].first.price)
140+
141+
coVerify { saveMonthlyPortfolioUseCase(match {
142+
it.size == 2 && it.contains(updatedMarketItem) && it.contains(manualItem)
143+
}) }
144+
}
145+
146+
@Test
147+
fun `should keep old price when market update fails`() = runTest {
148+
// Given
149+
val marketItem = Investment(
150+
symbol = "AAPL",
151+
name = "Apple",
152+
quantity = 1.0,
153+
price = 150.0,
154+
previousPrice = 140.0,
155+
currency = Currency("USD"),
156+
type = InvestmentType.STOCK,
157+
year = 2026,
158+
month = 4
159+
)
160+
161+
every { getPortfolioItemsUseCase() } returns flowOf(listOf(marketItem))
162+
coEvery {
163+
getInvestmentPriceUseCase(any(), any(), any(), any(), any(), any(), any())
164+
} returns Result.failure(Exception("Network error"))
165+
166+
// When
167+
val result = useCase()
168+
169+
// Then
170+
assertEquals(1, result.size)
171+
assertEquals(150.0, result[0].first.price)
172+
assertEquals(0.0, result[0].second)
173+
174+
coVerify { saveMonthlyPortfolioUseCase(listOf(marketItem)) }
175+
}
176+
}

0 commit comments

Comments
 (0)