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()) + } }