diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 00000000..16660f1d
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/google/android/samples/socialite/repository/ChatRepositoryTest.kt b/app/src/androidTest/java/com/google/android/samples/socialite/repository/ChatRepositoryTest.kt
index d500dec4..bdd4ce29 100644
--- a/app/src/androidTest/java/com/google/android/samples/socialite/repository/ChatRepositoryTest.kt
+++ b/app/src/androidTest/java/com/google/android/samples/socialite/repository/ChatRepositoryTest.kt
@@ -16,37 +16,196 @@
package com.google.android.samples.socialite.repository
+import android.content.Context
+import androidx.compose.foundation.text2.input.insert
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import app.cash.turbine.test
+import com.google.android.samples.socialite.data.ChatDao
+import com.google.android.samples.socialite.data.ContactDao
+import com.google.android.samples.socialite.data.MessageDao
+import com.google.android.samples.socialite.model.ChatDetail
+import com.google.android.samples.socialite.model.Contact
+import com.google.android.samples.socialite.model.Message
+import com.google.android.samples.socialite.widget.model.WidgetModelRepository
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class ChatRepositoryTest {
+ @Mock
+ private lateinit var chatDao: ChatDao
+
+ @Mock
+ private lateinit var messageDao: MessageDao
+
+ @Mock
+ private lateinit var contactDao: ContactDao
+
+ @Mock
+ private lateinit var notificationHelper: NotificationHelper
+
+ @Mock
+ private lateinit var widgetModelRepository: WidgetModelRepository
+
+ private lateinit var chatRepository: ChatRepository
+
+ private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
+ private val testScope: CoroutineScope = CoroutineScope(testDispatcher)
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.openMocks(this)
+ Dispatchers.setMain(testDispatcher)
+ chatRepository = ChatRepository(
+ chatDao,
+ messageDao,
+ contactDao,
+ notificationHelper,
+ widgetModelRepository,
+ testScope,
+ context,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
@Test
fun getChats() = runTest {
- val repository = createTestRepository()
- repository.getChats().test {
- assertThat(awaitItem()).isNotEmpty()
+ val chatDetails = listOf(
+ ChatDetail(1, Contact(1, "Contact 1", "", "", "", "", "", "", ""), emptyList()),
+ ChatDetail(2, Contact(2, "Contact 2", "", "", "", "", "", "", ""), emptyList()),
+ )
+ `when`(chatDao.allDetails()).thenReturn(flowOf(chatDetails))
+ chatRepository.getChats().test {
+ assertThat(awaitItem()).isEqualTo(chatDetails)
}
+ verify(chatDao).allDetails()
}
@Test
fun findChat() = runTest {
- val repository = createTestRepository()
- repository.findChat(1L).test {
- assertThat(awaitItem()!!.firstContact.name).isEqualTo("Cat")
+ val chatId = 1L
+ val chatDetail = ChatDetail(chatId, Contact(1, "Contact 1", "", "", "", "", "", "", ""), emptyList())
+ `when`(chatDao.detailById(chatId)).thenReturn(flowOf(chatDetail))
+ chatRepository.findChat(chatId).test {
+ assertThat(awaitItem()).isEqualTo(chatDetail)
}
+ verify(chatDao).detailById(chatId)
}
@Test
fun findMessages() = runTest {
- val repository = createTestRepository()
- repository.findMessages(1L).test {
- assertThat(awaitItem()).hasSize(2)
+ val chatId = 1L
+ val messages = listOf(
+ Message(1, chatId, 1, "Message 1", null, null, 1234567890),
+ Message(2, chatId, 2, "Message 2", null, null, 1234567891),
+ )
+ `when`(messageDao.allByChatId(chatId)).thenReturn(flowOf(messages))
+ chatRepository.findMessages(chatId).test {
+ assertThat(awaitItem()).isEqualTo(messages)
+ }
+ verify(messageDao).allByChatId(chatId)
+ }
+
+ @Test
+ fun `sendMessage saves message and notifies`() = runTest {
+ val chatId = 1L
+ val text = "Hello"
+ val mediaUri = null
+ val mediaMimeType = null
+ val contact = Contact(1, "Contact 1", "", "", "", "", "", "", "")
+ val chatDetail = ChatDetail(chatId, contact, emptyList())
+ `when`(chatDao.loadDetailById(chatId)).thenReturn(chatDetail)
+ `when`(messageDao.loadAll(chatId)).thenReturn(emptyList())
+
+ chatRepository.sendMessage(chatId, text, mediaUri, mediaMimeType)
+
+ verify(messageDao).insert(any())
+ verify(notificationHelper).pushShortcut(contact, PushReason.OutgoingMessage)
+ verify(widgetModelRepository).updateUnreadMessagesForContact(contactId = contact.id, unread = true)
+ }
+
+ @Test
+ fun `sendMessage with bot enabled sends message to gemini and saves response`() = runTest {
+ val chatId = 1L
+ val text = "Hello"
+ val mediaUri = null
+ val mediaMimeType = null
+ val contact = Contact(1, "Contact 1", "", "", "", "", "", "", "")
+ val chatDetail = ChatDetail(chatId, contact, emptyList())
+ val pastMessages = emptyList()
+ val dataStore = context.dataStore
+ dataStore.edit { preferences ->
+ preferences[booleanPreferencesKey("enable_chatbot")] = true
+ }
+
+ `when`(chatDao.loadDetailById(chatId)).thenReturn(chatDetail)
+ `when`(messageDao.loadAll(chatId)).thenReturn(pastMessages)
+ `when`(messageDao.allByChatId(chatId)).thenReturn(flowOf(pastMessages))
+
+ chatRepository.sendMessage(chatId, text, mediaUri, mediaMimeType)
+ // Wait for the coroutine to finish
+ testScope.launch {
+ chatRepository.isBotEnabled.collect {
+ if (it) {
+ verify(messageDao, times(2)).insert(any())
+ }
+ }
}
}
+
+ @Test
+ fun `sendMessage with bot disabled simulates peer response`() = runTest {
+ val chatId = 1L
+ val text = "Hello"
+ val mediaUri = null
+ val mediaMimeType = null
+ val contact = Contact(1, "Contact 1", "", "", "", "", "", "", "")
+ val chatDetail = ChatDetail(chatId, contact, emptyList())
+ val dataStore = context.dataStore
+ dataStore.edit { preferences ->
+ preferences[booleanPreferencesKey("enable_chatbot")] = false
+ }
+
+ `when`(chatDao.loadDetailById(chatId)).thenReturn(chatDetail)
+ `when`(messageDao.loadAll(chatId)).thenReturn(emptyList())
+
+ chatRepository.sendMessage(chatId, text, mediaUri, mediaMimeType)
+
+ verify(messageDao, times(2)).insert(any())
+ }
}