package com.contentsquare.rn.csq.autocapture import androidx.test.ext.junit.runners.AndroidJUnit4 import com.contentsquare.rn.csq.InvocationEventEmitter import com.contentsquare.rn.csq.utils.* import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Dynamic import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableType import com.facebook.react.bridge.WritableMap import io.heap.core.logs.HeapLogger import io.heap.core.support.HeapBridgeSupport import io.heap.core.support.InvalidParametersException import io.heap.core.support.UnknownMethodException import io.mockk.* import org.junit.After import org.junit.Assert.* import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) class CSQAutocaptureTest { // Mocks private val mockContext = mockk(relaxed = true) private val mockEventEmitter = mockk(relaxed = true) private val mockBridgeSupport = mockk(relaxed = true) private val mockPromise = mockk(relaxed = true) private val mockReadableMap = mockk(relaxed = true) private val mockDynamic = mockk(relaxed = true) // Class under test private lateinit var underTest: CSQAutocapture @Before fun setUp() { MockKAnnotations.init(this) mockkStatic(Arguments::class) mockkObject(HeapLogger) mockkConstructor(HeapBridgeSupport::class) every { anyConstructed().delegate = any() } just runs every { HeapLogger.error(any(), any(), any()) } just runs every { Arguments.createMap() } returns mockk(relaxed = true) underTest = CSQAutocapture(mockContext, mockEventEmitter) // Replace the bridgeSupport with our mock val bridgeSupportField = CSQAutocapture::class.java.getDeclaredField("bridgeSupport") bridgeSupportField.isAccessible = true bridgeSupportField.set(underTest, mockBridgeSupport) } @After fun tearDown() { unmockkAll() } @Test fun `constructor initializes bridgeSupport and sets delegate`() { // Given / When val autocapture = CSQAutocapture(mockContext, mockEventEmitter) // Then verify { anyConstructed().delegate = autocapture } } @Test fun `handleInvocation resolves promise with converted result on success`() { // Given val method = "testMethod" val argsMap = hashMapOf("key" to "value") val result = mapOf("result" to "success") val latch = CountDownLatch(1) every { mockReadableMap.toMap() } returns argsMap every { mockBridgeSupport.handleInvocation(method, any()) } returns result every { mockPromise.resolve(any()) } answers { latch.countDown() Unit } // When underTest.handleInvocation(method, mockReadableMap, mockPromise) latch.await(1, TimeUnit.SECONDS) // Then verify { mockBridgeSupport.handleInvocation(method, any()) } verify { mockPromise.resolve(any()) } } @Test fun `handleInvocation resolves promise with null when result is null`() { // Given val method = "testMethod" val argsMap = hashMapOf() val latch = CountDownLatch(1) every { mockReadableMap.toMap() } returns argsMap every { mockBridgeSupport.handleInvocation(method, argsMap as Map) } returns null every { mockPromise.resolve(null) } answers { latch.countDown() Unit } // When underTest.handleInvocation(method, mockReadableMap, mockPromise) latch.await(1, TimeUnit.SECONDS) // Then verify { mockPromise.resolve(null) } } @Test fun `handleInvocation rejects promise with NOT_IMPLEMENTED on UnknownMethodException`() { // Given val method = "unknownMethod" val argsMap = hashMapOf() val exception = UnknownMethodException() val latch = CountDownLatch(1) every { mockReadableMap.toMap() } returns argsMap every { mockBridgeSupport.handleInvocation(method, argsMap as Map) } throws exception every { mockPromise.reject(any(), any(), any()) } answers { latch.countDown() Unit } // When underTest.handleInvocation(method, mockReadableMap, mockPromise) latch.await(1, TimeUnit.SECONDS) // Then verify { mockPromise.reject( "NOT_IMPLEMENTED", "The method $method is not implemented in the Java runtime.", exception ) } } @Test fun `handleInvocation rejects promise with BAD_ARGS on InvalidParametersException`() { // Given val method = "testMethod" val argsMap = hashMapOf() val exception = InvalidParametersException() val latch = CountDownLatch(1) every { mockReadableMap.toMap() } returns argsMap every { mockBridgeSupport.handleInvocation(method, argsMap as Map) } throws exception every { mockPromise.reject(any(), any(), any()) } answers { latch.countDown() Unit } // When underTest.handleInvocation(method, mockReadableMap, mockPromise) latch.await(1, TimeUnit.SECONDS) // Then verify { mockPromise.reject( "BAD_ARGS", "Invalid arguments sent to $method in the Java runtime. See logs for details.", exception ) } } @Test fun `handleInvocation rejects promise with UNKNOWN on generic exception`() { // Given val method = "testMethod" val argsMap = hashMapOf() val exception = RuntimeException("Something went wrong") val latch = CountDownLatch(1) every { mockReadableMap.toMap() } returns argsMap every { mockBridgeSupport.handleInvocation(method, argsMap as Map) } throws exception every { mockPromise.reject(any(), any(), any()) } answers { latch.countDown() Unit } // When underTest.handleInvocation(method, mockReadableMap, mockPromise) latch.await(1, TimeUnit.SECONDS) // Then verify { mockPromise.reject( "UNKNOWN", "An unknown error occurred while calling $method in the Java runtime. See logs for details.", exception ) } } @Test fun `handleInvocation executes asynchronously`() { // Given val method = "testMethod" val argsMap = hashMapOf() val latch = CountDownLatch(1) val threadName = slot() every { mockReadableMap.toMap() } returns argsMap every { mockBridgeSupport.handleInvocation(method, argsMap as Map) } answers { threadName.captured = Thread.currentThread().name latch.countDown() null } // When underTest.handleInvocation(method, mockReadableMap, mockPromise) latch.await(1, TimeUnit.SECONDS) // Then assertNotEquals("main", threadName.captured) } @Test fun `handleResult with Dynamic data calls bridgeSupport with converted data`() { // Given val callbackId = "callback-123" val data = "test-data" val latch = CountDownLatch(1) every { mockDynamic.type } returns ReadableType.String every { mockDynamic.asString() } returns data every { mockBridgeSupport.handleResult(callbackId, data, null) } answers { latch.countDown() Unit } // When underTest.handleResult(callbackId, mockDynamic, null) latch.await(1, TimeUnit.SECONDS) // Then verify { mockBridgeSupport.handleResult(callbackId, data, null) } } @Test fun `handleResult with null Dynamic data calls bridgeSupport with null`() { // Given val callbackId = "callback-123" val latch = CountDownLatch(1) every { mockBridgeSupport.handleResult(callbackId, null, null) } answers { latch.countDown() Unit } // When underTest.handleResult(callbackId, null as Dynamic?, null) latch.await(1, TimeUnit.SECONDS) // Then verify { mockBridgeSupport.handleResult(callbackId, null, null) } } @Test fun `handleResult with Dynamic data and error passes error to bridgeSupport`() { // Given val callbackId = "callback-123" val error = "An error occurred" val latch = CountDownLatch(1) every { mockDynamic.type } returns ReadableType.Null every { mockBridgeSupport.handleResult(callbackId, null, error) } answers { latch.countDown() Unit } // When underTest.handleResult(callbackId, mockDynamic, error) latch.await(1, TimeUnit.SECONDS) // Then verify { mockBridgeSupport.handleResult(callbackId, null, error) } } @Test fun `handleResult with ReadableMap data calls bridgeSupport with converted map`() { // Given val callbackId = "callback-456" val dataMap = hashMapOf("key" to "value") val latch = CountDownLatch(1) every { mockReadableMap.toMap() } returns dataMap every { mockBridgeSupport.handleResult(callbackId, dataMap, null) } answers { latch.countDown() Unit } // When underTest.handleResult(callbackId, mockReadableMap, null) latch.await(1, TimeUnit.SECONDS) // Then verify { mockBridgeSupport.handleResult(callbackId, dataMap, null) } } @Test fun `handleResult with null ReadableMap data calls bridgeSupport with null`() { // Given val callbackId = "callback-456" val latch = CountDownLatch(1) every { mockBridgeSupport.handleResult(callbackId, null, null) } answers { latch.countDown() Unit } // When underTest.handleResult(callbackId, null as ReadableMap?, null) latch.await(1, TimeUnit.SECONDS) // Then verify { mockBridgeSupport.handleResult(callbackId, null, null) } } @Test fun `handleResult with ReadableMap data and error passes error to bridgeSupport`() { // Given val callbackId = "callback-456" val dataMap = hashMapOf("key" to "value") val error = "An error occurred" val latch = CountDownLatch(1) every { mockReadableMap.toMap() } returns dataMap every { mockBridgeSupport.handleResult(callbackId, dataMap, error) } answers { latch.countDown() Unit } // When underTest.handleResult(callbackId, mockReadableMap, error) latch.await(1, TimeUnit.SECONDS) // Then verify { mockBridgeSupport.handleResult(callbackId, dataMap, error) } } @Test fun `sendInvocation with all parameters emits correct payload`() { // Given val method = "testMethod" val arguments: Map<*, *> = mapOf("arg1" to "value1") val callbackId = "callback-789" val mockPayload = mockk(relaxed = true) val latch = CountDownLatch(1) every { Arguments.createMap() } returns mockPayload every { mockEventEmitter.sendInvocationEvent(mockPayload) } answers { latch.countDown() Unit } // When underTest.sendInvocation(method, arguments, callbackId) latch.await(1, TimeUnit.SECONDS) // Then verify { mockPayload.putString("type", "invocation") mockPayload.putString("method", method) mockPayload.putMap("arguments", any()) mockPayload.putString("callbackId", callbackId) mockEventEmitter.sendInvocationEvent(mockPayload) } } @Test fun `sendInvocation with exception and callbackId calls bridgeSupport handleResult with error`() { // Given val method = "testMethod" val arguments: Map<*, *> = mapOf("arg1" to "value1") val callbackId = "callback-789" val exception = RuntimeException("Event emission failed") val mockPayload = mockk(relaxed = true) val latch = CountDownLatch(1) every { Arguments.createMap() } returns mockPayload every { mockEventEmitter.sendInvocationEvent(mockPayload) } throws exception every { mockBridgeSupport.handleResult(callbackId, null, any()) } answers { latch.countDown() Unit } // When underTest.sendInvocation(method, arguments, callbackId) latch.await(1, TimeUnit.SECONDS) // Then verify { mockBridgeSupport.handleResult( callbackId, null, match { it.contains("Sending invocation failed") && it.contains(exception.toString()) } ) } } @Test fun `executeAsync executes block successfully`() { // Given val latch = CountDownLatch(1) var executed = false // When underTest.executeAsync { executed = true latch.countDown() } latch.await(1, TimeUnit.SECONDS) // Then assertTrue(executed) } @Test fun `executeAsync catches and logs exceptions`() { // Given val exception = RuntimeException("Test exception") val latch = CountDownLatch(1) every { HeapLogger.error(any(), any(), exception) } answers { latch.countDown() Unit } // When underTest.executeAsync { throw exception } latch.await(1, TimeUnit.SECONDS) // Then verify { HeapLogger.error( "Heap encountered an error in the runtime bridge", "HeapReactNativeBridge", exception ) } } @Test fun `executeAsync rethrows ThreadDeath`() { // Given val threadDeath = ThreadDeath() val latch = CountDownLatch(1) var caughtThreadDeath = false // When underTest.executeAsync { try { throw threadDeath } catch (td: ThreadDeath) { caughtThreadDeath = true latch.countDown() throw td } } latch.await(1, TimeUnit.SECONDS) // Then assertTrue(caughtThreadDeath) } }