* fix(#472): refactor ChatViewModelTest to use Fake* + Turbine Replace Mockk-heavy test setup with FakeChatPersistenceRepository (+ in-memory FakeChatMessageDao) and add Turbine for Flow assertions. Changes: - Add Turbine v1.2.0 dependency (libs.versions.toml + build.gradle.kts) - Create FakeChatMessageDao – in-memory DAO with MutableStateFlow - Create FakeChatPersistenceRepository – delegates to FakeDao - ChatViewModel: make repo injectable (@VisibleForTesting lateinit var) - Rewrite ChatViewModelTest (751 lines vs 1,212): clearer helpers, Turbine test for streaming Flow sequence, same 29 tests with identical assertions, ~38% line reduction Test: ktlint --format passes clean * fix(#472): address review — constructor injection, ConcurrentHashMap, MockWebServer tests Addresses all feedback from Sourcery review on PR #474: Constructor injection (was lateinit var): - Move repo to ChatViewModel primary constructor with default - Remove @VisibleForTesting lateinit var + init block assignment - Tests pass repo directly via constructor Simplify FakeChatMessageDao: - Replace manual synchronized(lock) with ConcurrentHashMap - Same thread safety, 48% less code, no boilerplate lock object New MockWebServer tests: - HermesApiServiceMockWebServerTest: 6 tests covering getStatus, getSessions, getSessionMessages + edge cases (null fields, empty lists, slash-encoded session IDs) * fix: CI compilation errors — open class, TestScope receiver, runBlocking Three compilation errors blocking CI: - ChatPersistenceRepository was final, but FakeChatPersistenceRepository extends it → make it open class - createViewModelWithSession() called advanceUntilIdle (TestScope ext) and emit() (suspend) without a TestScope receiver or coroutine ctx → make it fun TestScope.xxx() and use tryEmit() for non-suspend emission - HermesApiServiceMockWebServerTest called suspend API methods from regular @Test methods → wrap each test in runBlocking * fix: test runtime failures — suspend helper + fix JSON escaping - Use so emit() (suspend) is valid and events are delivered synchronously instead of buffered via tryEmit() which was causing 17 test failures - Fix MalformedJsonException in MockWebServer test by using plain string content instead of unescaped JSON-within-JSON * fix: correct req-id values in ChatViewModelTest — SESSION_CREATE is req-id-3 * fix(test): correct ChatViewModelTest assertions for Fakes+Turbine refactor - testMessageStreamingFlow: collect streamingState via Turbine (ThinkingDelta/MessageToken mutate streamingState, not uiState) - testToolExecution_finalizesPreviousStreamingMessage: account for messages[0] = "Session created" system message - testMessageStart_finalizesPreviousStreamingMessage: same index offset (messages[1] is the finalized stream) - testClarifyRequestCustomResponse: options is emptyList(), not null; message count is 3 (session-create + user clarify reply) Fixes CI failures introduced in PR #474. --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
d5fa70b34b
commit
1c3a5386f4
8 files changed
+645
-715
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,42 @@
|
||||
package com.m57.hermescontrol.ui.chat.fakes
|
||||
|
||||
import com.m57.hermescontrol.data.local.ChatMessageDao
|
||||
import com.m57.hermescontrol.data.local.ChatMessageEntity
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
|
||||
/**
|
||||
* In-memory [ChatMessageDao] for use in tests.
|
||||
* Uses [ConcurrentHashMap] for thread safety — no manual synchronization needed.
|
||||
*/
|
||||
class FakeChatMessageDao : ChatMessageDao {
|
||||
private val messages: ConcurrentMap<String, ChatMessageEntity> = ConcurrentHashMap()
|
||||
|
||||
override suspend fun sessionExists(sessionId: String): Boolean = messages.values.any { it.sessionId == sessionId }
|
||||
|
||||
override suspend fun getMessagesForSession(sessionId: String): List<ChatMessageEntity> =
|
||||
messages.values
|
||||
.filter { it.sessionId == sessionId }
|
||||
.sortedBy { it.timestamp }
|
||||
|
||||
override suspend fun upsert(message: ChatMessageEntity) {
|
||||
messages[message.id] = message
|
||||
}
|
||||
|
||||
override suspend fun upsertAll(messageList: List<ChatMessageEntity>) {
|
||||
messageList.forEach { messages[it.id] = it }
|
||||
}
|
||||
|
||||
/** Direct access for test setup — bypasses the suspend modifier. */
|
||||
fun addMessageDirect(message: ChatMessageEntity) {
|
||||
messages[message.id] = message
|
||||
}
|
||||
|
||||
/** Reset all stored messages. */
|
||||
fun clear() {
|
||||
messages.clear()
|
||||
}
|
||||
|
||||
/** Returns the number of stored messages. */
|
||||
fun count(): Int = messages.size
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.m57.hermescontrol.ui.chat.fakes
|
||||
|
||||
import com.m57.hermescontrol.ui.chat.ChatPersistenceRepository
|
||||
|
||||
/**
|
||||
* In-memory [ChatPersistenceRepository] for tests.
|
||||
*
|
||||
* Wraps [FakeChatMessageDao] so tests can read/write messages
|
||||
* without Room or SQLCipher. Pass to [ChatViewModel]'s `repo`
|
||||
* constructor parameter in tests.
|
||||
*
|
||||
* ```
|
||||
* val fakeRepo = FakeChatPersistenceRepository()
|
||||
* fakeRepo.dao.addMessageDirect(someEntity)
|
||||
* val vm = ChatViewModel(app, startCleanup, fakeRepo)
|
||||
* ```
|
||||
*/
|
||||
class FakeChatPersistenceRepository(
|
||||
val dao: FakeChatMessageDao = FakeChatMessageDao(),
|
||||
) : ChatPersistenceRepository(dao) {
|
||||
/** Clear all stored messages. */
|
||||
fun clear() = dao.clear()
|
||||
}
|
||||
Reference in new issue
Block a user