diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/AWSClientTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/AWSClientTest.kt new file mode 100644 index 0000000..c627bb4 --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/AWSClientTest.kt @@ -0,0 +1,340 @@ +package com.amazon.connect.chat.sdk.network + +import com.amazon.connect.chat.sdk.model.ConnectionDetails +import com.amazon.connect.chat.sdk.model.ContentType +import com.amazon.connect.chat.sdk.model.GlobalConfig +import com.amazon.connect.chat.sdk.utils.Constants +import com.amazonaws.regions.Region +import com.amazonaws.regions.Regions +import com.amazonaws.services.connectparticipant.AmazonConnectParticipantClient +import com.amazonaws.services.connectparticipant.model.CompleteAttachmentUploadRequest +import com.amazonaws.services.connectparticipant.model.CompleteAttachmentUploadResult +import com.amazonaws.services.connectparticipant.model.ConnectionCredentials +import com.amazonaws.services.connectparticipant.model.CreateParticipantConnectionRequest +import com.amazonaws.services.connectparticipant.model.CreateParticipantConnectionResult +import com.amazonaws.services.connectparticipant.model.DisconnectParticipantRequest +import com.amazonaws.services.connectparticipant.model.DisconnectParticipantResult +import com.amazonaws.services.connectparticipant.model.GetAttachmentRequest +import com.amazonaws.services.connectparticipant.model.GetAttachmentResult +import com.amazonaws.services.connectparticipant.model.GetTranscriptRequest +import com.amazonaws.services.connectparticipant.model.GetTranscriptResult +import com.amazonaws.services.connectparticipant.model.SendEventRequest +import com.amazonaws.services.connectparticipant.model.SendEventResult +import com.amazonaws.services.connectparticipant.model.SendMessageRequest +import com.amazonaws.services.connectparticipant.model.SendMessageResult +import com.amazonaws.services.connectparticipant.model.StartAttachmentUploadRequest +import com.amazonaws.services.connectparticipant.model.StartAttachmentUploadResult +import com.amazonaws.services.connectparticipant.model.Websocket +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import java.util.Date + +@ExperimentalCoroutinesApi +@RunWith(RobolectricTestRunner::class) +class AWSClientTest { + + @Mock + private lateinit var mockConnectParticipantClient: AmazonConnectParticipantClient + + private lateinit var awsClient: AWSClientImpl + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private val testParticipantToken = "test-participant-token" + private val testConnectionToken = "test-connection-token" + private val testWebsocketUrl = "wss://test-websocket-url" + private val testExpiry = "2023-12-31T23:59:59Z" + private val testMessage = "test-message" + private val testContent = "test-content" + private val testAttachmentId = "test-attachment-id" + private val testRegion = Regions.US_WEST_2 + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + awsClient = AWSClientImpl(mockConnectParticipantClient) + } + + @Test + fun `test configure sets region on client`() { + val config = GlobalConfig(region = testRegion) + + awsClient.configure(config) + + verify(mockConnectParticipantClient).setRegion(Region.getRegion(testRegion.getName())) + } + + @Test + fun `test createParticipantConnection success`() = testScope.runTest { + val mockWebsocket = Mockito.mock(Websocket::class.java) + val mockConnectionCredentials = Mockito.mock(ConnectionCredentials::class.java) + val mockResult = Mockito.mock(CreateParticipantConnectionResult::class.java) + + `when`(mockWebsocket.url).thenReturn(testWebsocketUrl) + `when`(mockWebsocket.connectionExpiry).thenReturn(testExpiry) + `when`(mockConnectionCredentials.connectionToken).thenReturn(testConnectionToken) + `when`(mockResult.websocket).thenReturn(mockWebsocket) + `when`(mockResult.connectionCredentials).thenReturn(mockConnectionCredentials) + `when`(mockConnectParticipantClient.createParticipantConnection(Mockito.any())).thenReturn(mockResult) + + val result = awsClient.createParticipantConnection(testParticipantToken) + + assertTrue(result.isSuccess) + val connectionDetails = result.getOrNull() + assertEquals(testWebsocketUrl, connectionDetails?.websocketUrl) + assertEquals(testConnectionToken, connectionDetails?.connectionToken) + assertEquals(testExpiry, connectionDetails?.expiry) + + // Verify request + val requestCaptor = ArgumentCaptor.forClass(CreateParticipantConnectionRequest::class.java) + verify(mockConnectParticipantClient).createParticipantConnection(requestCaptor.capture()) + val capturedRequest = requestCaptor.value + assertEquals(testParticipantToken, capturedRequest.participantToken) + assertEquals(Constants.ACPS_REQUEST_TYPES, capturedRequest.type) + } + + @Test + fun `test createParticipantConnection failure`() = testScope.runTest { + val exception = RuntimeException("Test exception") + `when`(mockConnectParticipantClient.createParticipantConnection(Mockito.any())).thenThrow(exception) + + val result = awsClient.createParticipantConnection(testParticipantToken) + + assertTrue(result.isFailure) + assertEquals(exception, result.exceptionOrNull()) + } + + @Test + fun `test disconnectParticipantConnection success`() = testScope.runTest { + val mockResult = Mockito.mock(DisconnectParticipantResult::class.java) + `when`(mockConnectParticipantClient.disconnectParticipant(Mockito.any())).thenReturn(mockResult) + + val result = awsClient.disconnectParticipantConnection(testConnectionToken) + + assertTrue(result.isSuccess) + assertEquals(mockResult, result.getOrNull()) + + val requestCaptor = ArgumentCaptor.forClass(DisconnectParticipantRequest::class.java) + verify(mockConnectParticipantClient).disconnectParticipant(requestCaptor.capture()) + val capturedRequest = requestCaptor.value + assertEquals(testConnectionToken, capturedRequest.connectionToken) + } + + @Test + fun `test disconnectParticipantConnection failure`() = testScope.runTest { + val exception = RuntimeException("Test exception") + `when`(mockConnectParticipantClient.disconnectParticipant(Mockito.any())).thenThrow(exception) + + val result = awsClient.disconnectParticipantConnection(testConnectionToken) + + assertTrue(result.isFailure) + assertEquals(exception, result.exceptionOrNull()) + } + + @Test + fun `test sendMessage success`() = testScope.runTest { + val mockResult = Mockito.mock(SendMessageResult::class.java) + `when`(mockConnectParticipantClient.sendMessage(Mockito.any())).thenReturn(mockResult) + val contentType = ContentType.PLAIN_TEXT + + val result = awsClient.sendMessage(testConnectionToken, contentType, testMessage) + + assertTrue(result.isSuccess) + assertEquals(mockResult, result.getOrNull()) + + // Verify request + val requestCaptor = ArgumentCaptor.forClass(SendMessageRequest::class.java) + verify(mockConnectParticipantClient).sendMessage(requestCaptor.capture()) + val capturedRequest = requestCaptor.value + assertEquals(testConnectionToken, capturedRequest.connectionToken) + assertEquals(contentType.type, capturedRequest.contentType) + assertEquals(testMessage, capturedRequest.content) + } + + @Test + fun `test sendMessage failure`() = testScope.runTest { + val exception = RuntimeException("Test exception") + `when`(mockConnectParticipantClient.sendMessage(Mockito.any())).thenThrow(exception) + val contentType = ContentType.PLAIN_TEXT + + val result = awsClient.sendMessage(testConnectionToken, contentType, testMessage) + + assertTrue(result.isFailure) + assertEquals(exception, result.exceptionOrNull()) + } + + @Test + fun `test sendEvent success`() = testScope.runTest { + val mockResult = Mockito.mock(SendEventResult::class.java) + `when`(mockConnectParticipantClient.sendEvent(Mockito.any())).thenReturn(mockResult) + val contentType = ContentType.TYPING + + val result = awsClient.sendEvent(testConnectionToken, contentType, testContent) + + assertTrue(result.isSuccess) + assertEquals(mockResult, result.getOrNull()) + + // Verify request + val requestCaptor = ArgumentCaptor.forClass(SendEventRequest::class.java) + verify(mockConnectParticipantClient).sendEvent(requestCaptor.capture()) + val capturedRequest = requestCaptor.value + assertEquals(testConnectionToken, capturedRequest.connectionToken) + assertEquals(contentType.type, capturedRequest.contentType) + assertEquals(testContent, capturedRequest.content) + } + + @Test + fun `test sendEvent failure`() = testScope.runTest { + val exception = RuntimeException("Test exception") + `when`(mockConnectParticipantClient.sendEvent(Mockito.any())).thenThrow(exception) + val contentType = ContentType.TYPING + + val result = awsClient.sendEvent(testConnectionToken, contentType, testContent) + + assertTrue(result.isFailure) + assertEquals(exception, result.exceptionOrNull()) + } + + @Test + fun `test startAttachmentUpload success`() = testScope.runTest { + val mockResult = Mockito.mock(StartAttachmentUploadResult::class.java) + `when`(mockConnectParticipantClient.startAttachmentUpload(Mockito.any())).thenReturn(mockResult) + val request = StartAttachmentUploadRequest().apply { + connectionToken = testConnectionToken + attachmentName = "test.txt" + contentType = "text/plain" + } + + val result = awsClient.startAttachmentUpload(testConnectionToken, request) + + assertTrue(result.isSuccess) + assertEquals(mockResult, result.getOrNull()) + + // Verify request + verify(mockConnectParticipantClient).startAttachmentUpload(request) + } + + @Test + fun `test startAttachmentUpload failure`() = testScope.runTest { + val exception = RuntimeException("Test exception") + `when`(mockConnectParticipantClient.startAttachmentUpload(Mockito.any())).thenThrow(exception) + val request = StartAttachmentUploadRequest().apply { + connectionToken = testConnectionToken + attachmentName = "test.txt" + contentType = "text/plain" + } + + val result = awsClient.startAttachmentUpload(testConnectionToken, request) + + assertTrue(result.isFailure) + assertEquals(exception, result.exceptionOrNull()) + } + + @Test + fun `test completeAttachmentUpload success`() = testScope.runTest { + val mockResult = Mockito.mock(CompleteAttachmentUploadResult::class.java) + `when`(mockConnectParticipantClient.completeAttachmentUpload(Mockito.any())).thenReturn(mockResult) + val request = CompleteAttachmentUploadRequest().apply { + connectionToken = testConnectionToken + setAttachmentIds(listOf(testAttachmentId)) + } + + val result = awsClient.completeAttachmentUpload(testConnectionToken, request) + + assertTrue(result.isSuccess) + assertEquals(mockResult, result.getOrNull()) + + // Verify request + verify(mockConnectParticipantClient).completeAttachmentUpload(request) + } + + @Test + fun `test completeAttachmentUpload failure`() = testScope.runTest { + val exception = RuntimeException("Test exception") + `when`(mockConnectParticipantClient.completeAttachmentUpload(Mockito.any())).thenThrow(exception) + val request = CompleteAttachmentUploadRequest().apply { + connectionToken = testConnectionToken + setAttachmentIds(listOf(testAttachmentId)) + } + + val result = awsClient.completeAttachmentUpload(testConnectionToken, request) + + assertTrue(result.isFailure) + assertEquals(exception, result.exceptionOrNull()) + } + + + @Test + fun `test getAttachment success`() = testScope.runTest { + val mockResult = Mockito.mock(GetAttachmentResult::class.java) + `when`(mockConnectParticipantClient.getAttachment(Mockito.any())).thenReturn(mockResult) + + val result = awsClient.getAttachment(testConnectionToken, testAttachmentId) + + assertTrue(result.isSuccess) + assertEquals(mockResult, result.getOrNull()) + + // Verify request + val requestCaptor = ArgumentCaptor.forClass(GetAttachmentRequest::class.java) + verify(mockConnectParticipantClient).getAttachment(requestCaptor.capture()) + val capturedRequest = requestCaptor.value + assertEquals(testConnectionToken, capturedRequest.connectionToken) + assertEquals(testAttachmentId, capturedRequest.attachmentId) + } + + @Test + fun `test getAttachment failure`() = testScope.runTest { + val exception = RuntimeException("Test exception") + `when`(mockConnectParticipantClient.getAttachment(Mockito.any())).thenThrow(exception) + + val result = awsClient.getAttachment(testConnectionToken, testAttachmentId) + + assertTrue(result.isFailure) + assertEquals(exception, result.exceptionOrNull()) + } + + @Test + fun `test getTranscript success`() = testScope.runTest { + val mockResult = Mockito.mock(GetTranscriptResult::class.java) + `when`(mockConnectParticipantClient.getTranscript(Mockito.any())).thenReturn(mockResult) + val request = GetTranscriptRequest().apply { + connectionToken = testConnectionToken + } + + val result = awsClient.getTranscript(request) + + assertTrue(result.isSuccess) + assertEquals(mockResult, result.getOrNull()) + + // Verify request + verify(mockConnectParticipantClient).getTranscript(request) + } + + @Test + fun `test getTranscript failure`() = testScope.runTest { + val exception = RuntimeException("Test exception") + `when`(mockConnectParticipantClient.getTranscript(Mockito.any())).thenThrow(exception) + val request = GetTranscriptRequest().apply { + connectionToken = testConnectionToken + } + + val result = awsClient.getTranscript(request) + + assertTrue(result.isFailure) + assertEquals(exception, result.exceptionOrNull()) + } +} \ No newline at end of file diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/NetworkConnectionManagerTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/NetworkConnectionManagerTest.kt new file mode 100644 index 0000000..d7b26d0 --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/NetworkConnectionManagerTest.kt @@ -0,0 +1,132 @@ +package com.amazon.connect.chat.sdk.network + +import android.content.Context +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner + +@ExperimentalCoroutinesApi +@RunWith(RobolectricTestRunner::class) +class NetworkConnectionManagerTest { + + @Mock + private lateinit var mockContext: Context + + @Mock + private lateinit var mockConnectivityManager: ConnectivityManager + + @Mock + private lateinit var mockNetwork: Network + + @Mock + private lateinit var mockNetworkCapabilities: NetworkCapabilities + + @Captor + private lateinit var networkCallbackCaptor: ArgumentCaptor + + private lateinit var networkConnectionManager: NetworkConnectionManager + private lateinit var capturedCallback: ConnectivityManager.NetworkCallback + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + `when`(mockContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mockConnectivityManager) + networkConnectionManager = NetworkConnectionManager(mockContext) + + // Register and capture the callback in setup + networkConnectionManager.registerNetworkCallback() + verify(mockConnectivityManager).registerNetworkCallback( + org.mockito.ArgumentMatchers.any(NetworkRequest::class.java), + networkCallbackCaptor.capture() + ) + capturedCallback = networkCallbackCaptor.value + } + + @Test + fun `test initial network state is false`() = runTest { + // When initialized, network state should be false + assertFalse(networkConnectionManager.isNetworkAvailable.first()) + } + + @Test + fun `test registerNetworkCallback registers with correct request`() { + // This is already verified in setUp() + // Just verify that we have a callback + assertTrue(capturedCallback is ConnectivityManager.NetworkCallback) + } + + @Test + fun `test onAvailable sets network state to true`() = runTest { + capturedCallback.onAvailable(mockNetwork) + + assertTrue(networkConnectionManager.isNetworkAvailable.first()) + } + + @Test + fun `test onLost sets network state to false`() = runTest { + // First set it to true + capturedCallback.onAvailable(mockNetwork) + assertTrue(networkConnectionManager.isNetworkAvailable.first()) + + capturedCallback.onLost(mockNetwork) + + assertFalse(networkConnectionManager.isNetworkAvailable.first()) + } + + @Test + fun `test onCapabilitiesChanged with internet capability sets state to true`() = runTest { + `when`(mockNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)).thenReturn(true) + + capturedCallback.onCapabilitiesChanged(mockNetwork, mockNetworkCapabilities) + + assertTrue(networkConnectionManager.isNetworkAvailable.first()) + } + + @Test + fun `test onCapabilitiesChanged without internet capability sets state to false`() = runTest { + `when`(mockNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)).thenReturn(false) + + capturedCallback.onCapabilitiesChanged(mockNetwork, mockNetworkCapabilities) + + assertFalse(networkConnectionManager.isNetworkAvailable.first()) + } + + @Test + fun `test getInstance returns singleton instance`() { + val instance1 = NetworkConnectionManager.getInstance(mockContext) + + val instance2 = NetworkConnectionManager.getInstance(mockContext) + + assertSame(instance1, instance2) + } + + @Test + fun `test getInstance creates new instance when none exists`() { + // Access the INSTANCE field via reflection to set it to null + val field = NetworkConnectionManager::class.java.getDeclaredField("INSTANCE") + field.isAccessible = true + field.set(null, null) + + val instance = NetworkConnectionManager.getInstance(mockContext) + + assertTrue(instance is NetworkConnectionManager) + } +} diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/ResourceTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/ResourceTest.kt new file mode 100644 index 0000000..f90b5c2 --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/ResourceTest.kt @@ -0,0 +1,119 @@ +package com.amazon.connect.chat.sdk.network + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class ResourceTest { + + @Test + fun `test Success constructor sets data correctly`() { + val testData = "test data" + + val resource = Resource.Success(testData) + + assertEquals(testData, resource.data) + assertNull(resource.message) + } + + @Test + fun `test Error constructor sets message and data correctly`() { + val testMessage = "error message" + val testData = "test data" + + val resource = Resource.Error(testMessage, testData) + + assertEquals(testMessage, resource.message) + assertEquals(testData, resource.data) + } + + @Test + fun `test Error constructor with null data`() { + val testMessage = "error message" + + val resource = Resource.Error(testMessage) + + assertEquals(testMessage, resource.message) + assertNull(resource.data) + } + + @Test + fun `test Loading constructor with data`() { + val testData = "test data" + + val resource = Resource.Loading(testData) + + assertEquals(testData, resource.data) + assertNull(resource.message) + } + + @Test + fun `test Loading constructor with null data`() { + val resource = Resource.Loading() + + assertNull(resource.data) + assertNull(resource.message) + } + + @Test + fun `test Resource is sealed class with correct subclasses`() { + val success = Resource.Success("data") + val error = Resource.Error("error") + val loading = Resource.Loading() + + assert(success is Resource<*>) + assert(error is Resource<*>) + assert(loading is Resource<*>) + } + + @Test + fun `test type safety with different data types`() { + // Test with String + val stringSuccess = Resource.Success("string data") + assertEquals("string data", stringSuccess.data) + + // Test with Int + val intSuccess = Resource.Success(42) + assertEquals(42, intSuccess.data) + + // Test with custom class + val customData = TestData("name", 25) + val customSuccess = Resource.Success(customData) + assertEquals(customData, customSuccess.data) + } + + @Test + fun `test when expression with Resource`() { + val successResource: Resource = Resource.Success("success") + val errorResource: Resource = Resource.Error("error") + val loadingResource: Resource = Resource.Loading() + + val successResult = when (successResource) { + is Resource.Success -> "Success: ${successResource.data}" + is Resource.Error -> "Error: ${successResource.message}" + is Resource.Loading -> "Loading" + } + + val errorResult = when (errorResource) { + is Resource.Success -> "Success: ${errorResource.data}" + is Resource.Error -> "Error: ${errorResource.message}" + is Resource.Loading -> "Loading" + } + + val loadingResult = when (loadingResource) { + is Resource.Success -> "Success: ${loadingResource.data}" + is Resource.Error -> "Error: ${loadingResource.message}" + is Resource.Loading -> "Loading" + } + + assertEquals("Success: success", successResult) + assertEquals("Error: error", errorResult) + assertEquals("Loading", loadingResult) + } + + // Helper class + data class TestData(val name: String, val age: Int) +} \ No newline at end of file diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/RetrofitServiceCreatorTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/RetrofitServiceCreatorTest.kt new file mode 100644 index 0000000..e577733 --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/RetrofitServiceCreatorTest.kt @@ -0,0 +1,104 @@ +package com.amazon.connect.chat.sdk.network + +import com.amazon.connect.chat.sdk.network.api.ApiUrl +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import retrofit2.Retrofit + +@RunWith(RobolectricTestRunner::class) +class RetrofitServiceCreatorTest { + + @Mock + private lateinit var mockRetrofitBuilder: Retrofit.Builder + + @Mock + private lateinit var mockRetrofit: Retrofit + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + `when`(mockRetrofitBuilder.baseUrl(org.mockito.ArgumentMatchers.anyString())).thenReturn(mockRetrofitBuilder) + `when`(mockRetrofitBuilder.build()).thenReturn(mockRetrofit) + } + + @Test + fun `test createService with explicit URL`() { + val explicitUrl = "https://explicit-url.com/api/" + val mockService = TestService() + `when`(mockRetrofit.create(TestService::class.java)).thenReturn(mockService) + + val service = RetrofitServiceCreator.createService(TestService::class.java, mockRetrofitBuilder, explicitUrl) + + verify(mockRetrofitBuilder).baseUrl(explicitUrl) + verify(mockRetrofit).create(TestService::class.java) + assertNotNull(service) + assertEquals(mockService, service) + } + + @Test + fun `test createService with annotated service class`() { + val mockService = AnnotatedTestService() + `when`(mockRetrofit.create(AnnotatedTestService::class.java)).thenReturn(mockService) + + val service = RetrofitServiceCreator.createService(AnnotatedTestService::class.java, mockRetrofitBuilder) + + verify(mockRetrofitBuilder).baseUrl("https://annotated-url.com/api/") + verify(mockRetrofit).create(AnnotatedTestService::class.java) + assertNotNull(service) + assertEquals(mockService, service) + } + + @Test + fun `test createService with default URL`() { + val mockService = TestService() + `when`(mockRetrofit.create(TestService::class.java)).thenReturn(mockService) + + val service = RetrofitServiceCreator.createService(TestService::class.java, mockRetrofitBuilder) + + verify(mockRetrofitBuilder).baseUrl("https://www.example.com/v1/") + verify(mockRetrofit).create(TestService::class.java) + assertNotNull(service) + assertEquals(mockService, service) + } + + @Test + fun `test createService prioritizes explicit URL over annotation`() { + val explicitUrl = "https://explicit-url.com/api/" + val mockService = AnnotatedTestService() + `when`(mockRetrofit.create(AnnotatedTestService::class.java)).thenReturn(mockService) + + val service = RetrofitServiceCreator.createService(AnnotatedTestService::class.java, mockRetrofitBuilder, explicitUrl) + + verify(mockRetrofitBuilder).baseUrl(explicitUrl) + verify(mockRetrofit).create(AnnotatedTestService::class.java) + assertNotNull(service) + assertEquals(mockService, service) + } + + @Test + fun `test createService with null URL uses annotation or default`() { + val mockService = AnnotatedTestService() + `when`(mockRetrofit.create(AnnotatedTestService::class.java)).thenReturn(mockService) + + val service = RetrofitServiceCreator.createService(AnnotatedTestService::class.java, mockRetrofitBuilder, null) + + verify(mockRetrofitBuilder).baseUrl("https://annotated-url.com/api/") + verify(mockRetrofit).create(AnnotatedTestService::class.java) + assertNotNull(service) + assertEquals(mockService, service) + } + + // Test service classes + open class TestService + + @ApiUrl("https://annotated-url.com/api/") + open class AnnotatedTestService +} \ No newline at end of file diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/WebSocketManagerTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/WebSocketManagerTest.kt new file mode 100644 index 0000000..25617c8 --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/WebSocketManagerTest.kt @@ -0,0 +1,140 @@ +package com.amazon.connect.chat.sdk.network + +import com.amazon.connect.chat.sdk.provider.ConnectionDetailsProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import okhttp3.OkHttpClient +import okhttp3.WebSocket +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import java.lang.reflect.Field + +@ExperimentalCoroutinesApi +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE) +class WebSocketManagerTest { + + @Mock + private lateinit var mockNetworkConnectionManager: NetworkConnectionManager + + @Mock + private lateinit var mockConnectionDetailsProvider: ConnectionDetailsProvider + + @Mock + private lateinit var mockOkHttpClient: OkHttpClient + + @Mock + private lateinit var mockWebSocket: WebSocket + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + private lateinit var webSocketManager: WebSocketManagerImpl + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + + // Create a StateFlow for network availability + val networkAvailableFlow = MutableStateFlow(true) + + // Mock the isNetworkAvailable property to return our flow + Mockito.`when`(mockNetworkConnectionManager.isNetworkAvailable).thenReturn(networkAvailableFlow) + + webSocketManager = WebSocketManagerImpl( + dispatcher = testDispatcher, + networkConnectionManager = mockNetworkConnectionManager, + connectionDetailsProvider = mockConnectionDetailsProvider + ) + + setPrivateField(webSocketManager, "client", mockOkHttpClient) + setPrivateField(webSocketManager, "isConnectedToNetwork", true) + setPrivateField(webSocketManager, "isChatActive", false) + } + + @Test + fun `suspendWebSocketConnection sets isChatSuspended to true`() { + setPrivateField(webSocketManager, "webSocket", mockWebSocket) + webSocketManager.suspendWebSocketConnection() + val isChatSuspended = getPrivateField(webSocketManager, "isChatSuspended") as Boolean + assertTrue(isChatSuspended) + } + + @Test + fun `resumeWebSocketConnection sets isChatSuspended to false`() { + setPrivateField(webSocketManager, "isChatSuspended", true) + webSocketManager.resumeWebSocketConnection() + val isChatSuspended = getPrivateField(webSocketManager, "isChatSuspended") as Boolean + assertFalse(isChatSuspended) + } + + @Test + fun `check webSocket connection state`() { + // Test with webSocket set + setPrivateField(webSocketManager, "webSocket", mockWebSocket) + val webSocketWithMock = getPrivateField(webSocketManager, "webSocket") + assertNotNull(webSocketWithMock) + + // Test with webSocket null + setPrivateField(webSocketManager, "webSocket", null) + val webSocketWithNull = getPrivateField(webSocketManager, "webSocket") + assertNull(webSocketWithNull) + } + + @Test + fun `resumeWebSocketConnection does not change state when chat is already active`() { + setPrivateField(webSocketManager, "isChatActive", true) + webSocketManager.resumeWebSocketConnection() + val isChatSuspended = getPrivateField(webSocketManager, "isChatSuspended") as Boolean + assertFalse(isChatSuspended) + } + + // Reflection helper methods + private fun setPrivateField(obj: Any, fieldName: String, value: Any?) { + try { + val field = findField(obj.javaClass, fieldName) + field.isAccessible = true + field.set(obj, value) + } catch (e: NoSuchFieldException) { + try { + val fallbackField = obj.javaClass.declaredFields.firstOrNull() + fallbackField?.let { + it.isAccessible = true + it.set(obj, value) + } + } catch (ex: Exception) { + println("Error setting fallback field $fieldName: ${ex.message}") + } + } catch (e: Exception) { + println("Error setting field $fieldName: ${e.message}") + } + } + + private fun getPrivateField(obj: Any, fieldName: String): Any? { + return try { + val field = findField(obj.javaClass, fieldName) + field.isAccessible = true + field.get(obj) + } catch (e: Exception) { + println("Error getting field $fieldName: ${e.message}") + null + } + } + + private fun findField(clazz: Class<*>, fieldName: String): Field { + return try { + clazz.getDeclaredField(fieldName) + } catch (e: NoSuchFieldException) { + clazz.superclass?.let { findField(it, fieldName) } ?: throw e + } + } +} \ No newline at end of file diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/api/APIClientTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/api/APIClientTest.kt new file mode 100644 index 0000000..5839bff --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/api/APIClientTest.kt @@ -0,0 +1,118 @@ +package com.amazon.connect.chat.sdk.network.api + +import com.amazon.connect.chat.sdk.model.Dimension +import com.amazon.connect.chat.sdk.model.Metric +import com.amazon.connect.chat.sdk.model.MetricRequestBody +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE) +class APIClientTest { + + @Mock + private lateinit var mockMetricsInterface: MetricsInterface + + @Mock + private lateinit var mockAttachmentsInterface: AttachmentsInterface + + @Mock + private lateinit var mockMetricsCall: Call + + private lateinit var apiClient: APIClient + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + apiClient = APIClient(mockMetricsInterface, mockAttachmentsInterface) + } + + @Test + fun `sendMetrics calls metricsInterface with correct parameters and handles success`() { + // Given + val dimension = Dimension("contactId", "contact-123") + val metric = Metric( + dimensions = listOf(dimension), + metricName = "TestMetric", + namespace = "TestNamespace", + optionalDimensions = emptyList(), + timestamp = "2023-01-01T12:00:00Z", + unit = "Count", + value = 1 + ) + val metricRequestBody = MetricRequestBody( + metricList = listOf(metric), + metricNamespace = "TestNamespace" + ) + val successResponse = Response.success(Unit) + + // Set up the mock to return our call for any MetricRequestBody + Mockito.`when`(mockMetricsInterface.sendMetrics(metricRequestBody)).thenReturn(mockMetricsCall) + + // Set up the enqueue behavior + Mockito.doAnswer { invocation -> + val callback = invocation.arguments[0] as Callback + callback.onResponse(mockMetricsCall, successResponse) + null + }.`when`(mockMetricsCall).enqueue(Mockito.any()) + + var capturedResponse: Response? = null + apiClient.sendMetrics(metricRequestBody) { response -> + capturedResponse = response + } + + // Verify metricsInterface was called with the correct parameters + Mockito.verify(mockMetricsInterface).sendMetrics(metricRequestBody) + + // Verify the callback was called with the correct response + assertEquals(successResponse, capturedResponse) + } + + @Test + fun `sendMetrics handles failure correctly`() { + val dimension = Dimension("contactId", "contact-123") + val metric = Metric( + dimensions = listOf(dimension), + metricName = "TestMetric", + namespace = "TestNamespace", + optionalDimensions = emptyList(), + timestamp = "2023-01-01T12:00:00Z", + unit = "Count", + value = 1 + ) + val metricRequestBody = MetricRequestBody( + metricList = listOf(metric), + metricNamespace = "TestNamespace" + ) + val throwable = Throwable("Network error") + + // Set up the mock to return our call for any MetricRequestBody + Mockito.`when`(mockMetricsInterface.sendMetrics(metricRequestBody)).thenReturn(mockMetricsCall) + + // Set up the enqueue behavior + Mockito.doAnswer { invocation -> + val callback = invocation.arguments[0] as Callback + callback.onFailure(mockMetricsCall, throwable) + null + }.`when`(mockMetricsCall).enqueue(Mockito.any()) + + var capturedResponse: Response? = null + apiClient.sendMetrics(metricRequestBody) { response -> + capturedResponse = response + } + + // Verify the callback was called with null response + assertNull(capturedResponse) + } +} \ No newline at end of file diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/api/ApiUrlTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/api/ApiUrlTest.kt new file mode 100644 index 0000000..29ae896 --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/network/api/ApiUrlTest.kt @@ -0,0 +1,40 @@ +package com.amazon.connect.chat.sdk.network.api + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE) +class ApiUrlTest { + + @Test + fun `ApiUrl annotation stores url value correctly`() { + @ApiUrl("https://test.example.com") + class TestClass + + val annotation = TestClass::class.java.getAnnotation(ApiUrl::class.java) + + assertNotNull("TestClass should have ApiUrl annotation", annotation) + assertEquals("ApiUrl should store the URL correctly", + "https://test.example.com", + annotation.url) + } + + @Test + fun `ApiUrl annotation can be retrieved at runtime`() { + @ApiUrl("https://runtime.example.com") + class RuntimeTestClass + + val clazz = RuntimeTestClass::class.java + val annotation = clazz.getAnnotation(ApiUrl::class.java) + + assertNotNull("Should find ApiUrl annotation", annotation) + assertEquals("ApiUrl annotation should have the correct URL", + "https://runtime.example.com", + annotation.url) + } +} \ No newline at end of file diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/provider/ChatSessionProviderTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/provider/ChatSessionProviderTest.kt new file mode 100644 index 0000000..68145b5 --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/provider/ChatSessionProviderTest.kt @@ -0,0 +1,99 @@ +package com.amazon.connect.chat.sdk.provider + +import android.content.Context +import com.amazon.connect.chat.sdk.ChatSession +import com.amazon.connect.chat.sdk.ChatSessionImpl +import com.amazon.connect.chat.sdk.network.api.APIClient +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import java.lang.reflect.Field + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE) +class ChatSessionProviderTest { + + @Mock + private lateinit var mockContext: Context + + @Mock + private lateinit var mockApplicationContext: Context + + @Mock + private lateinit var mockChatSession: ChatSession + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + resetChatSessionSingleton() + Mockito.`when`(mockContext.applicationContext).thenReturn(mockApplicationContext) + } + + @Test + fun `getChatSession returns same instance on multiple calls`() { + setPrivateField(ChatSessionProvider, "chatSession", mockChatSession) + + val session1 = ChatSessionProvider.getChatSession(mockContext) + val session2 = ChatSessionProvider.getChatSession(mockContext) + + assertNotNull(session1) + assertSame("Multiple calls should return the same instance", session1, session2) + } + + @Test + fun `isHiltAvailable returns false when class not found`() { + val method = ChatSessionProvider.javaClass.getDeclaredMethod("isHiltAvailable").apply { + isAccessible = true + } + val result = method.invoke(ChatSessionProvider) as Boolean + assertFalse("Expected Hilt to be unavailable", result) + } + + @Test + fun `createRetrofitBuilder returns builder with GsonConverterFactory`() { + val method = ChatSessionProvider.javaClass.getDeclaredMethod("createRetrofitBuilder").apply { + isAccessible = true + } + val builder = method.invoke(ChatSessionProvider) + assertNotNull("Builder should not be null", builder) + } + + @Test + fun `createAPIClient returns valid APIClient`() { + val retrofitBuilderMethod = ChatSessionProvider.javaClass.getDeclaredMethod("createRetrofitBuilder").apply { + isAccessible = true + } + val retrofitBuilder = retrofitBuilderMethod.invoke(ChatSessionProvider) as retrofit2.Retrofit.Builder + + val method = ChatSessionProvider.javaClass.getDeclaredMethod("createAPIClient", retrofit2.Retrofit.Builder::class.java).apply { + isAccessible = true + } + val result = method.invoke(ChatSessionProvider, retrofitBuilder) + assertTrue(result is APIClient) + } + + // Helper methods for reflection + private fun resetChatSessionSingleton() { + setPrivateField(ChatSessionProvider, "chatSession", null) + } + + private fun setPrivateField(target: Any, fieldName: String, value: Any?) { + val field = findField(target.javaClass, fieldName) + field.isAccessible = true + field.set(target, value) + } + + private fun findField(clazz: Class<*>, fieldName: String): Field { + return try { + clazz.getDeclaredField(fieldName) + } catch (e: NoSuchFieldException) { + clazz.superclass?.let { findField(it, fieldName) } ?: throw e + } + } +} diff --git a/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/provider/ConnectionDetailsProviderImplTest.kt b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/provider/ConnectionDetailsProviderImplTest.kt new file mode 100644 index 0000000..43bebd4 --- /dev/null +++ b/chat-sdk/src/test/java/com/amazon/connect/chat/sdk/provider/ConnectionDetailsProviderImplTest.kt @@ -0,0 +1,147 @@ +package com.amazon.connect.chat.sdk.provider + +import com.amazon.connect.chat.sdk.model.ChatDetails +import com.amazon.connect.chat.sdk.model.ConnectionDetails +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE) +class ConnectionDetailsProviderImplTest { + + private lateinit var connectionDetailsProvider: ConnectionDetailsProviderImpl + + @Mock + private lateinit var mockConnectionDetails: ConnectionDetails + + @Mock + private lateinit var mockChatDetails: ChatDetails + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + connectionDetailsProvider = ConnectionDetailsProviderImpl() + } + + @Test + fun `updateConnectionDetails stores connection details`() { + connectionDetailsProvider.updateConnectionDetails(mockConnectionDetails) + + val result = connectionDetailsProvider.getConnectionDetails() + assertSame(mockConnectionDetails, result) + } + + @Test + fun `updateChatDetails stores chat details`() { + connectionDetailsProvider.updateChatDetails(mockChatDetails) + + val result = connectionDetailsProvider.getChatDetails() + assertSame(mockChatDetails, result) + } + + @Test + fun `getConnectionDetails returns null when not set`() { + val result = connectionDetailsProvider.getConnectionDetails() + + assertNull(result) + } + + @Test + fun `getChatDetails returns null when not set`() { + val result = connectionDetailsProvider.getChatDetails() + + assertNull(result) + } + + @Test + fun `isChatSessionActive returns false by default`() { + val result = connectionDetailsProvider.isChatSessionActive() + + assertFalse(result) + } + + @Test + fun `setChatSessionState updates chat session state`() { + connectionDetailsProvider.setChatSessionState(true) + + val result = connectionDetailsProvider.isChatSessionActive() + assertTrue(result) + } + + @Test + fun `chatSessionState flow emits current state`() = runBlocking { + connectionDetailsProvider.setChatSessionState(true) + + val result = connectionDetailsProvider.chatSessionState.first() + + assertTrue(result) + } + + @Test + fun `reset clears all data`() { + connectionDetailsProvider.updateConnectionDetails(mockConnectionDetails) + connectionDetailsProvider.updateChatDetails(mockChatDetails) + connectionDetailsProvider.setChatSessionState(true) + + connectionDetailsProvider.reset() + + assertNull(connectionDetailsProvider.getConnectionDetails()) + assertNull(connectionDetailsProvider.getChatDetails()) + assertFalse(connectionDetailsProvider.isChatSessionActive()) + } + + @Test + fun `updateConnectionDetails replaces previous value`() { + val initialConnectionDetails = ConnectionDetails( + websocketUrl = "initial-url", + connectionToken = "initial-token", + expiry = "2023-12-31T23:59:59Z" + ) + val newConnectionDetails = ConnectionDetails( + websocketUrl = "new-url", + connectionToken = "new-token", + expiry = "2024-12-31T23:59:59Z" + ) + + connectionDetailsProvider.updateConnectionDetails(initialConnectionDetails) + connectionDetailsProvider.updateConnectionDetails(newConnectionDetails) + + val result = connectionDetailsProvider.getConnectionDetails() + assertEquals("new-url", result?.websocketUrl) + assertEquals("new-token", result?.connectionToken) + } + + @Test + fun `updateChatDetails replaces previous value`() { + val initialChatDetails = ChatDetails( + contactId = "initial-contact-id", + participantId = "initial-participant-id", + participantToken = "initial-token" + ) + val newChatDetails = ChatDetails( + contactId = "new-contact-id", + participantId = "new-participant-id", + participantToken = "new-token" + ) + + connectionDetailsProvider.updateChatDetails(initialChatDetails) + connectionDetailsProvider.updateChatDetails(newChatDetails) + + val result = connectionDetailsProvider.getChatDetails() + assertEquals("new-contact-id", result?.contactId) + assertEquals("new-participant-id", result?.participantId) + assertEquals("new-token", result?.participantToken) + } +} \ No newline at end of file