diff --git a/android/beagle/src/main/java/br/com/zup/beagle/android/cache/imagecomponent/ImageDownloader.kt b/android/beagle/src/main/java/br/com/zup/beagle/android/cache/imagecomponent/ImageDownloader.kt index 0d16b615ed..e933a6161c 100644 --- a/android/beagle/src/main/java/br/com/zup/beagle/android/cache/imagecomponent/ImageDownloader.kt +++ b/android/beagle/src/main/java/br/com/zup/beagle/android/cache/imagecomponent/ImageDownloader.kt @@ -23,9 +23,13 @@ import kotlinx.coroutines.withContext import java.io.InputStream import java.net.URL +internal object URLFactory { + fun createURL(url: String?) = URL(url) +} + internal class ImageDownloader { - suspend fun getRemoteImage(url: String, contentWidth: Int, contentHeight: Int) : Bitmap? { + suspend fun getRemoteImage(url: String, contentWidth: Int, contentHeight: Int) : Bitmap { val cacheId = LruImageCache.generateBitmapId(url, contentWidth, contentHeight) return withContext(CoroutineDispatchers.IO) { @@ -33,15 +37,15 @@ internal class ImageDownloader { bitmapCached ?: url.let { - downloadBitmap(it, contentWidth, contentHeight).apply { + downloadBitmap(it).apply { LruImageCache.put(cacheId, this) } } } } - private fun downloadBitmap(url: String?, contentWidth: Int, contentHeight: Int) : Bitmap { - val inputStream: InputStream = URL(url).openStream() + private fun downloadBitmap(url: String?) : Bitmap { + val inputStream: InputStream = URLFactory.createURL(url).openStream() return BitmapFactory.decodeStream(inputStream) } } diff --git a/android/beagle/src/main/java/br/com/zup/beagle/android/cache/imagecomponent/LruImageCache.kt b/android/beagle/src/main/java/br/com/zup/beagle/android/cache/imagecomponent/LruImageCache.kt index 606a8a273b..6de5ce5a24 100644 --- a/android/beagle/src/main/java/br/com/zup/beagle/android/cache/imagecomponent/LruImageCache.kt +++ b/android/beagle/src/main/java/br/com/zup/beagle/android/cache/imagecomponent/LruImageCache.kt @@ -41,5 +41,3 @@ internal object LruImageCache { contentHeight: Int ) = StringBuilder().append(url).append(contentWidth).append(contentHeight).toString() } - - diff --git a/android/beagle/src/main/java/br/com/zup/beagle/android/imagedownloader/DefaultImageDownloader.kt b/android/beagle/src/main/java/br/com/zup/beagle/android/imagedownloader/DefaultImageDownloader.kt index 67814f1eec..8cecc95c5f 100644 --- a/android/beagle/src/main/java/br/com/zup/beagle/android/imagedownloader/DefaultImageDownloader.kt +++ b/android/beagle/src/main/java/br/com/zup/beagle/android/imagedownloader/DefaultImageDownloader.kt @@ -22,7 +22,7 @@ import android.widget.ImageView import androidx.lifecycle.lifecycleScope import br.com.zup.beagle.android.cache.imagecomponent.ImageDownloader import br.com.zup.beagle.android.data.formatUrl -import br.com.zup.beagle.android.logger.BeagleLoggerProxy +import br.com.zup.beagle.android.logger.BeagleMessageLogs import br.com.zup.beagle.android.utils.CoroutineDispatchers import br.com.zup.beagle.android.widget.RootView import kotlinx.coroutines.launch @@ -34,18 +34,18 @@ internal class DefaultImageDownloader : BeagleImageDownloader { override fun download(url: String, imageView: ImageView, rootView: RootView) { imageView.post { - rootView.getLifecycleOwner().lifecycleScope.launch(CoroutineDispatchers.IO) { - val bitmap = try { - imageDownloader.getRemoteImage(url.formatUrl() ?: url, imageView.width, imageView.height) - } catch (e: Exception) { - BeagleLoggerProxy.error(e.message ?: "Error when try to download Image") - null - } + rootView.getLifecycleOwner().lifecycleScope.launch(CoroutineDispatchers.IO) { + val bitmap = try { + imageDownloader.getRemoteImage(url.formatUrl() ?: url, imageView.width, imageView.height) + } catch (e: Exception) { + BeagleMessageLogs.errorWhileTryingToDownloadImage(url, e) + null + } - bitmap?.let { - setImage(imageView, bitmap) - } + bitmap?.let { + setImage(imageView, bitmap) } + } } } diff --git a/android/beagle/src/main/java/br/com/zup/beagle/android/logger/BeagleMessageLogs.kt b/android/beagle/src/main/java/br/com/zup/beagle/android/logger/BeagleMessageLogs.kt index 56de040722..642a4b9c06 100644 --- a/android/beagle/src/main/java/br/com/zup/beagle/android/logger/BeagleMessageLogs.kt +++ b/android/beagle/src/main/java/br/com/zup/beagle/android/logger/BeagleMessageLogs.kt @@ -152,4 +152,9 @@ internal object BeagleMessageLogs { val warningMessage = "Cannot get some attributes of property $propertyName." BeagleLoggerProxy.warning(warningMessage) } + + fun errorWhileTryingToDownloadImage(image: String, ex: Exception) { + val errorMessage = "Error while trying to download image: $image" + BeagleLoggerProxy.error(errorMessage, ex) + } } diff --git a/android/beagle/src/test/java/br/com/zup/beagle/android/cache/imagecomponent/ImageDownloaderTest.kt b/android/beagle/src/test/java/br/com/zup/beagle/android/cache/imagecomponent/ImageDownloaderTest.kt index 662b17d848..337ca5ae9e 100644 --- a/android/beagle/src/test/java/br/com/zup/beagle/android/cache/imagecomponent/ImageDownloaderTest.kt +++ b/android/beagle/src/test/java/br/com/zup/beagle/android/cache/imagecomponent/ImageDownloaderTest.kt @@ -21,19 +21,23 @@ import android.graphics.BitmapFactory import android.util.LruCache import io.mockk.* import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test +import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.assertEquals +import java.io.InputStream +import java.net.URL -internal class ImageDownloaderTest { +@DisplayName("Given ImageDownloader") +class ImageDownloaderTest { private val imageDownloader = ImageDownloader() private val bitmap: Bitmap = mockk(relaxed = true) private val bitmapImproved: Bitmap = mockk(relaxed = true) private val contentWidth = 300 private val contentHeight = 200 - private val url = "https://vitafelice.com.br/wp-content/uploads/2019/01/beagle.jpg" + private val url = "https://www.stub.com/image.png" private val bitmapId = url + contentWidth + contentHeight + private val inputStream: InputStream = mockk(relaxed = true) + private val javaUrl: URL = mockk(relaxed = true) @BeforeEach fun setUp() { @@ -41,11 +45,14 @@ internal class ImageDownloaderTest { mockkObject(LruImageCache) mockkStatic(LruCache::class) mockkStatic(Bitmap::class) + mockkObject(URLFactory) every { bitmapImproved.width } returns contentWidth every { bitmapImproved.height } returns contentHeight every { LruImageCache.get(any()) } returns null every { LruImageCache.put(any(), any()) } just runs - every { BitmapFactory.decodeStream(any()) } returns bitmap + every { URLFactory.createURL(any()) } returns javaUrl + every { javaUrl.openStream() } returns inputStream + every { BitmapFactory.decodeStream(inputStream) } returns bitmap every { Bitmap.createScaledBitmap( any(), contentWidth, @@ -54,33 +61,68 @@ internal class ImageDownloaderTest { ) } returns bitmapImproved } - @Test - fun `GIVEN url and ImageViewSize WHEN download image bitmap THEN keep size and save on cache`() = runBlocking { - // Given - every { bitmap.width } returns contentWidth - every { bitmap.height } returns contentHeight + @AfterEach + fun tearDown() { + unmockkAll() + } + + @DisplayName("When download image") + @Nested + inner class Download { + + @DisplayName("Then should keep passed size and save the bitmap on cache") + @Test + fun keepSizeAndSaveOnCache() = runBlocking { + // Given + every { bitmap.width } returns contentWidth + every { bitmap.height } returns contentHeight + + // When + val bitmap = imageDownloader.getRemoteImage(url, contentWidth, contentHeight) - // When - val bitmap = imageDownloader.getRemoteImage(url, contentWidth, contentHeight) + // Then + verify(exactly = 1) { LruImageCache.put(bitmapId, bitmap) } + verify(exactly = 1) { LruImageCache.get(bitmapId) } + assertEquals(contentWidth, bitmap.width) + assertEquals(contentHeight, bitmap.height) + } - // Then - verify(exactly = 1) { LruImageCache.put(bitmapId, bitmap) } - verify(exactly = 1) { LruImageCache.get(bitmapId) } - assertEquals(contentWidth, bitmap?.width) - assertEquals(contentHeight, bitmap?.height) + @DisplayName("Then should load bitmap from cache when exists") + @Test + fun loadBitmapFromCache() = runBlocking { + // Given + every { LruImageCache.get(any()) } returns bitmapImproved + + // When + val bitmap = imageDownloader.getRemoteImage(url, contentWidth, contentHeight) + + // Then + verify(exactly = 1) { LruImageCache.get(bitmapId) } + assertEquals(contentWidth, bitmap.width) + assertEquals(contentHeight, bitmap.height) + } } +} + +@DisplayName("Given URLFactory") +class ImageDownloaderUrlFactory { + + @DisplayName("When createURL method in URLFactory is called") + @Nested + inner class Factory { - @Test - fun `GIVEN bitmap data WHEN download image THEN should load image bitmap from cache`() = runBlocking { - // Given - every { LruImageCache.get(any()) } returns bitmapImproved + @DisplayName("Then should return a new instance of URL") + @Test + fun factoryUrl() { + // Given + val url = "https://www.stub.com" - // When - val bitmap = imageDownloader.getRemoteImage(url, contentWidth, contentHeight) + // When + val result = URLFactory.createURL(url) - // Then - verify(exactly = 1) { LruImageCache.get(bitmapId) } - assertEquals(contentWidth, bitmap?.width) - assertEquals(contentHeight, bitmap?.height) + // Then + assertEquals("https", result.protocol) + assertEquals("www.stub.com", result.host) + } } -} \ No newline at end of file +} diff --git a/android/beagle/src/test/java/br/com/zup/beagle/android/logger/BeagleMessageLogsTest.kt b/android/beagle/src/test/java/br/com/zup/beagle/android/logger/BeagleMessageLogsTest.kt index 93b22a8556..884010ce29 100644 --- a/android/beagle/src/test/java/br/com/zup/beagle/android/logger/BeagleMessageLogsTest.kt +++ b/android/beagle/src/test/java/br/com/zup/beagle/android/logger/BeagleMessageLogsTest.kt @@ -418,4 +418,23 @@ internal class BeagleMessageLogsTest { verify(exactly = 1) { BeagleLoggerProxy.warning(expectedMessage) } } } + + @DisplayName("When try to download an invalid image") + @Nested + inner class DownloadImage { + + @DisplayName("Then should call BeagleLoggerProxy.error with image and exception") + @Test + fun downloadInvalidImage() { + // Given + val image = "/image" + val exceptionMessage = "Error while trying to download image: $image" + + // When + BeagleMessageLogs.errorWhileTryingToDownloadImage(image, exception) + + // Then + verify(exactly = 1) { BeagleLoggerProxy.error(exceptionMessage, exception) } + } + } }