From 70f92389cd86ff8c5d95c54f40444945bfceedeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Papp=20=28TWiStErRob=29?= Date: Thu, 11 Jan 2024 22:02:14 +0000 Subject: [PATCH] Use the repository in the use case --- .../london/status/DomainHistoryRepository.kt | 10 +++ .../london/status/DomainRefreshUseCase.kt | 14 ++-- .../london/status/api/HistoryRepository.kt | 22 +++++ ...ainHistoryRepositoryUnitTest_getCurrent.kt | 52 ++++++++++++ ...mainHistoryRepositoryUnitTest_getLatest.kt | 62 ++++++++++++++ ...omainHistoryRepositoryUnitTest_history.kt} | 2 +- .../DomainHistoryRepositoryUnitTest_save.kt | 44 ++++++++++ .../status/DomainRefreshUseCaseUnitTest.kt | 80 +++++++++---------- .../travel/domain/london/status/Mocks.kt | 4 + .../travel/domain/london/status/mock.kt | 2 + 10 files changed, 242 insertions(+), 50 deletions(-) create mode 100644 domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_getCurrent.kt create mode 100644 domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_getLatest.kt rename domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/{DomainHistoryRepositoryUnitTest.kt => DomainHistoryRepositoryUnitTest_history.kt} (99%) create mode 100644 domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_save.kt diff --git a/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepository.kt b/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepository.kt index 983712d8..8b128006 100644 --- a/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepository.kt +++ b/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepository.kt @@ -22,6 +22,16 @@ class DomainHistoryRepository( return result.map { it.parse() } } + override fun getCurrent(feed: Feed): StatusItem = + statusDataSource.getCurrent(feed) + + override fun getLatest(feed: Feed): StatusItem? = + statusHistoryDataSource.getAll(feed, 1).singleOrNull() + + override fun save(item: StatusItem) { + statusHistoryDataSource.add(item) + } + private fun StatusItem.parse(): ParsedStatusItem = when (this) { is StatusItem.SuccessfulStatusItem -> { diff --git a/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/DomainRefreshUseCase.kt b/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/DomainRefreshUseCase.kt index f0b2c290..5bc60a29 100644 --- a/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/DomainRefreshUseCase.kt +++ b/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/DomainRefreshUseCase.kt @@ -1,22 +1,20 @@ package net.twisterrob.travel.domain.london.status +import net.twisterrob.travel.domain.london.status.api.HistoryRepository import net.twisterrob.travel.domain.london.status.api.RefreshResult import net.twisterrob.travel.domain.london.status.api.RefreshUseCase -import net.twisterrob.travel.domain.london.status.api.StatusHistoryDataSource -import net.twisterrob.travel.domain.london.status.api.StatusDataSource class DomainRefreshUseCase( - private val statusHistoryDataSource: StatusHistoryDataSource, - private val statusDataSource: StatusDataSource, + private val historyRepository: HistoryRepository, ) : RefreshUseCase { override fun refreshLatest(feed: Feed): RefreshResult { - val current = statusDataSource.getCurrent(feed) - val latest = statusHistoryDataSource.getAll(feed, 1).singleOrNull() + val current = historyRepository.getCurrent(feed) + val latest = historyRepository.getLatest(feed) return when { latest == null -> { - statusHistoryDataSource.add(current) + historyRepository.save(current) RefreshResult.Created(current) } @@ -29,7 +27,7 @@ class DomainRefreshUseCase( } else -> { - statusHistoryDataSource.add(current) + historyRepository.save(current) RefreshResult.Refreshed(current, latest) } } diff --git a/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/api/HistoryRepository.kt b/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/api/HistoryRepository.kt index 1dfdb16b..252e67ae 100644 --- a/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/api/HistoryRepository.kt +++ b/domain/status/src/commonMain/kotlin/net/twisterrob/travel/domain/london/status/api/HistoryRepository.kt @@ -1,12 +1,34 @@ package net.twisterrob.travel.domain.london.status.api import net.twisterrob.travel.domain.london.status.Feed +import net.twisterrob.travel.domain.london.status.StatusItem interface HistoryRepository { /** + * Returns the top [max] items from the history of [feed] ordered by [StatusItem.retrievedDate], + * including the current status if [includeCurrent] is true. + * * @param max maximum number of items to return, current is not included in the count. * @param includeCurrent whether to include the current status in the result. */ fun history(feed: Feed, max: Int, includeCurrent: Boolean): List + + /** + * @return the current live status. + */ + fun getCurrent(feed: Feed): StatusItem + + /** + * @return the latest item from history, or null if there is no history. + */ + fun getLatest(feed: Feed): StatusItem? + + /** + * Saves the item to history. + * If [StatusItem.retrievedDate] is later than the latest item in history, this item will become the latest. + * + * @param item the item to save. + */ + fun save(item: StatusItem) } diff --git a/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_getCurrent.kt b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_getCurrent.kt new file mode 100644 index 00000000..cde201cf --- /dev/null +++ b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_getCurrent.kt @@ -0,0 +1,52 @@ +package net.twisterrob.travel.domain.london.status + +import io.mockative.any +import io.mockative.every +import io.mockative.mock +import io.mockative.verify +import io.mockative.verifyNoUnmetExpectations +import io.mockative.verifyNoUnverifiedExpectations +import net.twisterrob.travel.domain.london.status.api.FeedParser +import net.twisterrob.travel.domain.london.status.api.HistoryRepository +import net.twisterrob.travel.domain.london.status.api.StatusDataSource +import net.twisterrob.travel.domain.london.status.api.StatusHistoryDataSource +import kotlin.test.AfterTest +import kotlin.test.Test +import kotlin.test.assertSame + +class DomainHistoryRepositoryUnitTest_getCurrent { + + private val mockHistory: StatusHistoryDataSource = mock() + private val mockStatus: StatusDataSource = mock() + private val mockParser: FeedParser = mock() + private val subject: HistoryRepository = DomainHistoryRepository(mockHistory, mockStatus, mockParser) + private val feed = Feed.TubeDepartureBoardsLineStatus + + @AfterTest + fun verify() { + listOf(mockHistory, mockStatus, mockParser).forEach { + verifyNoUnverifiedExpectations(it) + verifyNoUnmetExpectations(it) + } + } + + @Test fun `returns successful item`() { + val current = SuccessfulStatusItem() + every { mockStatus.getCurrent(any()) }.returns(current) + + val result = subject.getCurrent(feed) + assertSame(current, result) + + verify { mockStatus.getCurrent(feed) }.wasInvoked() + } + + @Test fun `returns failed item`() { + val current = FailedStatusItem() + every { mockStatus.getCurrent(any()) }.returns(current) + + val result = subject.getCurrent(feed) + assertSame(current, result) + + verify { mockStatus.getCurrent(feed) }.wasInvoked() + } +} diff --git a/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_getLatest.kt b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_getLatest.kt new file mode 100644 index 00000000..99e77922 --- /dev/null +++ b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_getLatest.kt @@ -0,0 +1,62 @@ +package net.twisterrob.travel.domain.london.status + +import io.mockative.any +import io.mockative.every +import io.mockative.mock +import io.mockative.verify +import io.mockative.verifyNoUnmetExpectations +import io.mockative.verifyNoUnverifiedExpectations +import net.twisterrob.travel.domain.london.status.api.FeedParser +import net.twisterrob.travel.domain.london.status.api.HistoryRepository +import net.twisterrob.travel.domain.london.status.api.StatusDataSource +import net.twisterrob.travel.domain.london.status.api.StatusHistoryDataSource +import kotlin.test.AfterTest +import kotlin.test.Test +import kotlin.test.assertNull +import kotlin.test.assertSame + +class DomainHistoryRepositoryUnitTest_getLatest { + + private val mockHistory: StatusHistoryDataSource = mock() + private val mockStatus: StatusDataSource = mock() + private val mockParser: FeedParser = mock() + private val subject: HistoryRepository = DomainHistoryRepository(mockHistory, mockStatus, mockParser) + private val feed = Feed.TubeDepartureBoardsLineStatus + + @AfterTest + fun verify() { + listOf(mockHistory, mockStatus, mockParser).forEach { + verifyNoUnverifiedExpectations(it) + verifyNoUnmetExpectations(it) + } + } + + @Test fun `returns successful item`() { + val latest = SuccessfulStatusItem() + every { mockHistory.getAll(any(), any()) }.returns(listOf(latest)) + + val result = subject.getLatest(feed) + assertSame(latest, result) + + verify { mockHistory.getAll(feed, 1) }.wasInvoked() + } + + @Test fun `returns failed item`() { + val latest = FailedStatusItem() + every { mockHistory.getAll(any(), any()) }.returns(listOf(latest)) + + val result = subject.getLatest(feed) + assertSame(latest, result) + + verify { mockHistory.getAll(feed, 1) }.wasInvoked() + } + + @Test fun `returns no item`() { + every { mockHistory.getAll(any(), any()) }.returns(emptyList()) + + val result = subject.getLatest(feed) + assertNull(result) + + verify { mockHistory.getAll(feed, 1) }.wasInvoked() + } +} diff --git a/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest.kt b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_history.kt similarity index 99% rename from domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest.kt rename to domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_history.kt index 295f9d7e..ec3884c6 100644 --- a/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest.kt +++ b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_history.kt @@ -16,7 +16,7 @@ import kotlin.test.AfterTest import kotlin.test.Test import kotlin.test.assertEquals -class DomainHistoryRepositoryUnitTest { +class DomainHistoryRepositoryUnitTest_history { private val mockHistory: StatusHistoryDataSource = mock() private val mockStatus: StatusDataSource = mock() diff --git a/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_save.kt b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_save.kt new file mode 100644 index 00000000..f0283a77 --- /dev/null +++ b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainHistoryRepositoryUnitTest_save.kt @@ -0,0 +1,44 @@ +package net.twisterrob.travel.domain.london.status + +import io.mockative.mock +import io.mockative.verify +import io.mockative.verifyNoUnmetExpectations +import io.mockative.verifyNoUnverifiedExpectations +import net.twisterrob.travel.domain.london.status.api.FeedParser +import net.twisterrob.travel.domain.london.status.api.HistoryRepository +import net.twisterrob.travel.domain.london.status.api.StatusDataSource +import net.twisterrob.travel.domain.london.status.api.StatusHistoryDataSource +import kotlin.test.AfterTest +import kotlin.test.Test + +class DomainHistoryRepositoryUnitTest_save { + + private val mockHistory: StatusHistoryDataSource = mock() + private val mockStatus: StatusDataSource = mock() + private val mockParser: FeedParser = mock() + private val subject: HistoryRepository = DomainHistoryRepository(mockHistory, mockStatus, mockParser) + + @AfterTest + fun verify() { + listOf(mockHistory, mockStatus, mockParser).forEach { + verifyNoUnverifiedExpectations(it) + verifyNoUnmetExpectations(it) + } + } + + @Test fun `returns successful item`() { + val current = SuccessfulStatusItem() + + subject.save(current) + + verify { mockHistory.add(current) }.wasInvoked() + } + + @Test fun `returns failed item`() { + val current = FailedStatusItem() + + subject.save(current) + + verify { mockHistory.add(current) }.wasInvoked() + } +} diff --git a/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainRefreshUseCaseUnitTest.kt b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainRefreshUseCaseUnitTest.kt index 932baf51..c3062b6c 100644 --- a/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainRefreshUseCaseUnitTest.kt +++ b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/DomainRefreshUseCaseUnitTest.kt @@ -6,24 +6,22 @@ import io.mockative.mock import io.mockative.verify import io.mockative.verifyNoUnmetExpectations import io.mockative.verifyNoUnverifiedExpectations +import net.twisterrob.travel.domain.london.status.api.HistoryRepository import net.twisterrob.travel.domain.london.status.api.RefreshResult import net.twisterrob.travel.domain.london.status.api.RefreshUseCase -import net.twisterrob.travel.domain.london.status.api.StatusHistoryDataSource -import net.twisterrob.travel.domain.london.status.api.StatusDataSource import kotlin.test.AfterTest import kotlin.test.Test import kotlin.test.assertEquals class DomainRefreshUseCaseUnitTest { - private val mockHistory: StatusHistoryDataSource = mock() - private val mockStatus: StatusDataSource = mock() - private val subject: RefreshUseCase = DomainRefreshUseCase(mockHistory, mockStatus) + private val mockHistory: HistoryRepository = mock() + private val subject: RefreshUseCase = DomainRefreshUseCase(mockHistory) private val feed = Feed.TubeDepartureBoardsLineStatus @AfterTest fun verify() { - listOf(mockHistory, mockStatus).forEach { + listOf(mockHistory).forEach { verifyNoUnverifiedExpectations(it) verifyNoUnmetExpectations(it) } @@ -31,98 +29,98 @@ class DomainRefreshUseCaseUnitTest { @Test fun `current will be saved when there are no previous statuses`() { val current = SuccessfulStatusItem() - every { mockStatus.getCurrent(any()) }.returns(current) - every { mockHistory.getAll(any(), any()) }.returns(emptyList()) + every { mockHistory.getCurrent(any()) }.returns(current) + every { mockHistory.getLatest(any()) }.returns(null) val result = subject.refreshLatest(feed) assertEquals(RefreshResult.Created(current), result) - verify { mockStatus.getCurrent(feed) }.wasInvoked() - verify { mockHistory.getAll(feed, 1) }.wasInvoked() - verify { mockHistory.add(current) }.wasInvoked() + verify { mockHistory.getCurrent(feed) }.wasInvoked() + verify { mockHistory.getLatest(feed) }.wasInvoked() + verify { mockHistory.save(current) }.wasInvoked() } @Test fun `current will not be saved when it is the same as the latest status`() { val current = SuccessfulStatusItem() val latest = SuccessfulStatusItem().copy(content = current.content) - every { mockStatus.getCurrent(any()) }.returns(current) - every { mockHistory.getAll(any(), any()) }.returns(listOf(latest)) + every { mockHistory.getCurrent(any()) }.returns(current) + every { mockHistory.getLatest(any()) }.returns(latest) val result = subject.refreshLatest(feed) assertEquals(RefreshResult.NoChange(current, latest), result) - verify { mockStatus.getCurrent(feed) }.wasInvoked() - verify { mockHistory.getAll(feed, 1) }.wasInvoked() - verify { mockHistory.add(any()) }.wasNotInvoked() + verify { mockHistory.getCurrent(feed) }.wasInvoked() + verify { mockHistory.getLatest(feed) }.wasInvoked() + verify { mockHistory.save(any()) }.wasNotInvoked() } @Test fun `current will be saved when it differs from the latest status`() { val current = SuccessfulStatusItem() val latest = SuccessfulStatusItem() - every { mockStatus.getCurrent(any()) }.returns(current) - every { mockHistory.getAll(any(), any()) }.returns(listOf(latest)) + every { mockHistory.getCurrent(any()) }.returns(current) + every { mockHistory.getLatest(any()) }.returns(latest) val result = subject.refreshLatest(feed) assertEquals(RefreshResult.Refreshed(current, latest), result) - verify { mockStatus.getCurrent(feed) }.wasInvoked() - verify { mockHistory.getAll(feed, 1) }.wasInvoked() - verify { mockHistory.add(current) }.wasInvoked() + verify { mockHistory.getCurrent(feed) }.wasInvoked() + verify { mockHistory.getLatest(feed) }.wasInvoked() + verify { mockHistory.save(current) }.wasInvoked() } @Test fun `current error will be saved when it differs from the latest status`() { val current = FailedStatusItem() val latest = SuccessfulStatusItem() - every { mockStatus.getCurrent(any()) }.returns(current) - every { mockHistory.getAll(any(), any()) }.returns(listOf(latest)) + every { mockHistory.getCurrent(any()) }.returns(current) + every { mockHistory.getLatest(any()) }.returns(latest) val result = subject.refreshLatest(feed) assertEquals(RefreshResult.Refreshed(current, latest), result) - verify { mockStatus.getCurrent(feed) }.wasInvoked() - verify { mockHistory.getAll(feed, 1) }.wasInvoked() - verify { mockHistory.add(current) }.wasInvoked() + verify { mockHistory.getCurrent(feed) }.wasInvoked() + verify { mockHistory.getLatest(feed) }.wasInvoked() + verify { mockHistory.save(current) }.wasInvoked() } @Test fun `current success will be saved when it differs from the latest error`() { val current = SuccessfulStatusItem() val latest = FailedStatusItem() - every { mockStatus.getCurrent(any()) }.returns(current) - every { mockHistory.getAll(any(), any()) }.returns(listOf(latest)) + every { mockHistory.getCurrent(any()) }.returns(current) + every { mockHistory.getLatest(any()) }.returns(latest) val result = subject.refreshLatest(feed) assertEquals(RefreshResult.Refreshed(current, latest), result) - verify { mockStatus.getCurrent(feed) }.wasInvoked() - verify { mockHistory.getAll(feed, 1) }.wasInvoked() - verify { mockHistory.add(current) }.wasInvoked() + verify { mockHistory.getCurrent(feed) }.wasInvoked() + verify { mockHistory.getLatest(feed) }.wasInvoked() + verify { mockHistory.save(current) }.wasInvoked() } @Test fun `current error will be saved when it differs from the latest error`() { val current = FailedStatusItem() val latest = FailedStatusItem() - every { mockStatus.getCurrent(any()) }.returns(current) - every { mockHistory.getAll(any(), any()) }.returns(listOf(latest)) + every { mockHistory.getCurrent(any()) }.returns(current) + every { mockHistory.getLatest(any()) }.returns(latest) val result = subject.refreshLatest(feed) assertEquals(RefreshResult.Refreshed(current, latest), result) - verify { mockStatus.getCurrent(feed) }.wasInvoked() - verify { mockHistory.getAll(feed, 1) }.wasInvoked() - verify { mockHistory.add(current) }.wasInvoked() + verify { mockHistory.getCurrent(feed) }.wasInvoked() + verify { mockHistory.getLatest(feed) }.wasInvoked() + verify { mockHistory.save(current) }.wasInvoked() } @Test fun `current error will not be saved when it is the same as the latest error`() { val current = FailedStatusItem() val latest = FailedStatusItem().copy(error = current.error) - every { mockStatus.getCurrent(any()) }.returns(current) - every { mockHistory.getAll(any(), any()) }.returns(listOf(latest)) + every { mockHistory.getCurrent(any()) }.returns(current) + every { mockHistory.getLatest(any()) }.returns(latest) val result = subject.refreshLatest(feed) assertEquals(RefreshResult.NoChange(current, latest), result) - verify { mockStatus.getCurrent(feed) }.wasInvoked() - verify { mockHistory.getAll(feed, 1) }.wasInvoked() - verify { mockHistory.add(current) }.wasNotInvoked() + verify { mockHistory.getCurrent(feed) }.wasInvoked() + verify { mockHistory.getLatest(feed) }.wasInvoked() + verify { mockHistory.save(current) }.wasNotInvoked() } } diff --git a/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/Mocks.kt b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/Mocks.kt index 3f948193..66e2d056 100644 --- a/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/Mocks.kt +++ b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/Mocks.kt @@ -4,12 +4,16 @@ import io.mockative.Mock import io.mockative.classOf import io.mockative.mock import net.twisterrob.travel.domain.london.status.api.FeedParser +import net.twisterrob.travel.domain.london.status.api.HistoryRepository import net.twisterrob.travel.domain.london.status.api.StatusHistoryDataSource import net.twisterrob.travel.domain.london.status.api.StatusDataSource @Suppress("unused") // Used by mockative KSP. object Mocks { + @Mock + private val historyRepository = mock(classOf()) + @Mock private val statusHistoryDataSource = mock(classOf()) diff --git a/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/mock.kt b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/mock.kt index ef2f6594..1bb55d8e 100644 --- a/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/mock.kt +++ b/domain/status/src/commonTest/kotlin/net/twisterrob/travel/domain/london/status/mock.kt @@ -3,11 +3,13 @@ package io.mockative import net.twisterrob.travel.domain.london.status.api.FeedParser +import net.twisterrob.travel.domain.london.status.api.HistoryRepository import net.twisterrob.travel.domain.london.status.api.StatusHistoryDataSource import net.twisterrob.travel.domain.london.status.api.StatusDataSource inline fun mock(): T = when (T::class) { + HistoryRepository::class -> mock(HistoryRepository::class) as T StatusHistoryDataSource::class -> mock(StatusHistoryDataSource::class) as T StatusDataSource::class -> mock(StatusDataSource::class) as T FeedParser::class -> mock(FeedParser::class) as T