Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b50f5694 authored by Ellen Poe's avatar Ellen Poe
Browse files

test: Add some tests for TileDownloadManager

parent 8f6e47ba
Loading
Loading
Loading
Loading
Loading
+8 −8
Original line number Diff line number Diff line
@@ -81,7 +81,7 @@ class TileDownloadManager(
    /**
     * Determines if the basemap download phase is complete for the given area
     */
    private suspend fun isBasemapPhaseComplete(areaId: String): Boolean {
    suspend fun isBasemapPhaseComplete(areaId: String): Boolean {
        val expectedTileCount =
            downloadedTileDao.getDownloadedTileCountForAreaAndType(areaId, TileType.BASEMAP)
        Log.d(TAG, "Found $expectedTileCount basemap tiles for area $areaId")
@@ -106,7 +106,7 @@ class TileDownloadManager(
    /**
     * Determines which phase the download should resume from based on current progress
     */
    private suspend fun determineResumePhase(areaId: String): DownloadStatus {
    suspend fun determineResumePhase(areaId: String): DownloadStatus {
        val existingArea =
            offlineAreaDao.getOfflineAreaById(areaId) ?: return DownloadStatus.DOWNLOADING_BASEMAP

@@ -156,7 +156,7 @@ class TileDownloadManager(
    /**
     * Handle logic for existing areas: check if exists, determine resume phase, skip if completed, or create new area
     */
    private suspend fun handleExistingArea(
    suspend fun handleExistingArea(
        areaId: String, name: String, boundingBox: BoundingBox, minZoom: Int, maxZoom: Int
    ) {
        val existingArea = offlineAreaDao.getOfflineAreaById(areaId)
@@ -191,7 +191,7 @@ class TileDownloadManager(
    /**
     * Create a new offline area
     */
    private suspend fun createNewOfflineArea(
    suspend fun createNewOfflineArea(
        areaId: String, name: String, boundingBox: BoundingBox, minZoom: Int, maxZoom: Int
    ) {
        val offlineArea = OfflineArea(
@@ -216,7 +216,7 @@ class TileDownloadManager(
    /**
     * Update area status
     */
    private suspend fun updateAreaStatus(areaId: String, status: DownloadStatus) {
    suspend fun updateAreaStatus(areaId: String, status: DownloadStatus) {
        val area = offlineAreaDao.getOfflineAreaById(areaId)
        if (area != null) {
            val updatedArea = area.copy(status = status)
@@ -627,7 +627,7 @@ class TileDownloadManager(
    /**
     * Download basemap tiles for the given bounds
     */
    private suspend fun downloadBasemapTiles(
    suspend fun downloadBasemapTiles(
        boundingBox: BoundingBox, minZoom: Int, maxZoom: Int, areaId: String, areaName: String
    ): Pair<Int, Int> {
        var db: SQLiteDatabase? = null
@@ -1007,7 +1007,7 @@ class TileDownloadManager(
    /**
     * Process a batch of tiles
     */
    private suspend fun processBatch(
    suspend fun processBatch(
        chunk: List<Triple<Int, Int, Int>>,
        areaId: String,
        areaName: String,
@@ -1170,7 +1170,7 @@ class TileDownloadManager(
    /**
     * Download a single tile and return its data
     */
    private suspend fun downloadTile(
    suspend fun downloadTile(
        zoom: Int, x: Int, y: Int, layer: String
    ): Pair<Boolean, ByteArray?> = withContext(Dispatchers.IO) {
        try {
+573 −0
Original line number Diff line number Diff line
package earth.maps.cardinal.tileserver

import android.content.Context
import earth.maps.cardinal.MainCoroutineRule
import earth.maps.cardinal.data.BoundingBox
import earth.maps.cardinal.data.room.DownloadStatus
import earth.maps.cardinal.data.room.DownloadedTile
import earth.maps.cardinal.data.room.DownloadedTileDao
import earth.maps.cardinal.data.room.OfflineArea
import earth.maps.cardinal.data.room.OfflineAreaDao
import earth.maps.cardinal.data.room.TileType
import earth.maps.cardinal.geocoding.TileProcessor
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import java.util.concurrent.atomic.AtomicInteger

@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
class TileDownloadManagerTest {

    @get:Rule
    val coroutineRule = MainCoroutineRule()

    private lateinit var context: Context
    private lateinit var tileDownloadManager: TileDownloadManager
    private val mockDownloadedTileDao = mockk<DownloadedTileDao>()
    private val mockOfflineAreaDao = mockk<OfflineAreaDao>()
    private val mockTileProcessor = mockk<TileProcessor>()
    private val mockProgressReporter = mockk<DownloadProgressReporter>()

    @Before
    fun setup() {
        context = RuntimeEnvironment.application
        tileDownloadManager = TileDownloadManager(
            context = context,
            downloadedTileDao = mockDownloadedTileDao,
            offlineAreaDao = mockOfflineAreaDao,
            tileProcessor = mockTileProcessor,
            progressReporter = mockProgressReporter
        )
    }

    @Test
    fun `isBasemapPhaseComplete should return true when all expected tiles are downloaded`() =
        runTest {
            // Arrange
            val areaId = "test_area"
            val expectedTileCount = 3824

            coEvery {
                mockDownloadedTileDao.getDownloadedTileCountForAreaAndType(
                    areaId,
                    TileType.BASEMAP
                )
            } returns expectedTileCount
            coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns OfflineArea(
                id = areaId,
                name = "Test Area",
                north = 40.0,
                south = 39.0,
                east = -74.0,
                west = -75.0,
                minZoom = 10,
                maxZoom = 14,
                downloadDate = System.currentTimeMillis(),
                fileSize = 0L,
                status = DownloadStatus.DOWNLOADING_BASEMAP
            )

            // Act
            val result = tileDownloadManager.isBasemapPhaseComplete(areaId)

            // Assert
            assertTrue(result)
            coVerify {
                mockDownloadedTileDao.getDownloadedTileCountForAreaAndType(
                    areaId,
                    TileType.BASEMAP
                )
            }
            coVerify { mockOfflineAreaDao.getOfflineAreaById(areaId) }
        }

    @Test
    fun `isBasemapPhaseComplete should return false when not all tiles are downloaded`() = runTest {
        // Arrange
        val areaId = "test_area"
        val expectedTileCount = 50

        coEvery {
            mockDownloadedTileDao.getDownloadedTileCountForAreaAndType(
                areaId,
                TileType.BASEMAP
            )
        } returns expectedTileCount
        coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns OfflineArea(
            id = areaId,
            name = "Test Area",
            north = 40.0,
            south = 39.0,
            east = -74.0,
            west = -75.0,
            minZoom = 10,
            maxZoom = 14,
            downloadDate = System.currentTimeMillis(),
            fileSize = 0L,
            status = DownloadStatus.DOWNLOADING_BASEMAP
        )

        // Act
        val result = tileDownloadManager.isBasemapPhaseComplete(areaId)

        // Assert
        assertFalse(result)
    }

    @Test
    fun `isBasemapPhaseComplete should return false when area does not exist`() = runTest {
        // Arrange
        val areaId = "test_area"

        coEvery {
            mockDownloadedTileDao.getDownloadedTileCountForAreaAndType(
                areaId,
                TileType.BASEMAP
            )
        } returns 0
        coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns null

        // Act
        val result = tileDownloadManager.isBasemapPhaseComplete(areaId)

        // Assert
        assertFalse(result)
    }

    @Test
    fun `determineResumePhase should return DOWNLOADING_BASEMAP for PENDING area`() = runTest {
        // Arrange
        val areaId = "test_area"
        val area = OfflineArea(
            id = areaId,
            name = "Test Area",
            north = 40.0,
            south = 39.0,
            east = -74.0,
            west = -75.0,
            minZoom = 10,
            maxZoom = 14,
            downloadDate = System.currentTimeMillis(),
            fileSize = 0L,
            status = DownloadStatus.PENDING
        )

        coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns area

        // Act
        val result = tileDownloadManager.determineResumePhase(areaId)

        // Assert
        assertEquals(DownloadStatus.DOWNLOADING_BASEMAP, result)
    }

    @Test
    fun `determineResumePhase should return DOWNLOADING_VALHALLA when basemap is complete`() =
        runTest {
            // Arrange
            val areaId = "test_area"
            val area = OfflineArea(
                id = areaId,
                name = "Test Area",
                north = 40.0,
                south = 39.0,
                east = -74.0,
                west = -75.0,
                minZoom = 10,
                maxZoom = 14,
                downloadDate = System.currentTimeMillis(),
                fileSize = 0L,
                status = DownloadStatus.DOWNLOADING_BASEMAP
            )

            coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns area
            coEvery {
                mockDownloadedTileDao.getDownloadedTileCountForAreaAndType(
                    areaId,
                    TileType.BASEMAP
                )
            } returns 3824

            // Act
            val result = tileDownloadManager.determineResumePhase(areaId)

            // Assert
            assertEquals(DownloadStatus.DOWNLOADING_VALHALLA, result)
        }

    @Test
    fun `determineResumePhase should return DOWNLOADING_BASEMAP when basemap is not complete`() =
        runTest {
            // Arrange
            val areaId = "test_area"
            val area = OfflineArea(
                id = areaId,
                name = "Test Area",
                north = 40.0,
                south = 39.0,
                east = -74.0,
                west = -75.0,
                minZoom = 10,
                maxZoom = 14,
                downloadDate = System.currentTimeMillis(),
                fileSize = 0L,
                status = DownloadStatus.DOWNLOADING_BASEMAP
            )

            coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns area
            coEvery {
                mockDownloadedTileDao.getDownloadedTileCountForAreaAndType(
                    areaId,
                    TileType.BASEMAP
                )
            } returns 50

            // Act
            val result = tileDownloadManager.determineResumePhase(areaId)

            // Assert
            assertEquals(DownloadStatus.DOWNLOADING_BASEMAP, result)
        }

    @Test
    fun `determineResumePhase should return DOWNLOADING_VALHALLA for DOWNLOADING_VALHALLA area`() =
        runTest {
            // Arrange
            val areaId = "test_area"
            val area = OfflineArea(
                id = areaId,
                name = "Test Area",
                north = 40.0,
                south = 39.0,
                east = -74.0,
                west = -75.0,
                minZoom = 10,
                maxZoom = 14,
                downloadDate = System.currentTimeMillis(),
                fileSize = 0L,
                status = DownloadStatus.DOWNLOADING_VALHALLA
            )

            coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns area

            // Act
            val result = tileDownloadManager.determineResumePhase(areaId)

            // Assert
            assertEquals(DownloadStatus.DOWNLOADING_VALHALLA, result)
        }

    @Test
    fun `determineResumePhase should return PROCESSING_GEOCODER for PROCESSING_GEOCODER area`() =
        runTest {
            // Arrange
            val areaId = "test_area"
            val area = OfflineArea(
                id = areaId,
                name = "Test Area",
                north = 40.0,
                south = 39.0,
                east = -74.0,
                west = -75.0,
                minZoom = 10,
                maxZoom = 14,
                downloadDate = System.currentTimeMillis(),
                fileSize = 0L,
                status = DownloadStatus.PROCESSING_GEOCODER
            )

            coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns area

            // Act
            val result = tileDownloadManager.determineResumePhase(areaId)

            // Assert
            assertEquals(DownloadStatus.PROCESSING_GEOCODER, result)
        }

    @Test
    fun `determineResumePhase should return COMPLETED for COMPLETED area`() = runTest {
        // Arrange
        val areaId = "test_area"
        val area = OfflineArea(
            id = areaId,
            name = "Test Area",
            north = 40.0,
            south = 39.0,
            east = -74.0,
            west = -75.0,
            minZoom = 10,
            maxZoom = 14,
            downloadDate = System.currentTimeMillis(),
            fileSize = 0L,
            status = DownloadStatus.COMPLETED
        )

        coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns area

        // Act
        val result = tileDownloadManager.determineResumePhase(areaId)

        // Assert
        assertEquals(DownloadStatus.COMPLETED, result)
    }

    @Test
    fun `determineResumePhase should return DOWNLOADING_BASEMAP for FAILED area`() = runTest {
        // Arrange
        val areaId = "test_area"
        val area = OfflineArea(
            id = areaId,
            name = "Test Area",
            north = 40.0,
            south = 39.0,
            east = -74.0,
            west = -75.0,
            minZoom = 10,
            maxZoom = 14,
            downloadDate = System.currentTimeMillis(),
            fileSize = 0L,
            status = DownloadStatus.FAILED
        )

        coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns area

        // Act
        val result = tileDownloadManager.determineResumePhase(areaId)

        // Assert
        assertEquals(DownloadStatus.DOWNLOADING_BASEMAP, result)
    }

    @Test
    fun `determineResumePhase should return DOWNLOADING_BASEMAP when area does not exist`() =
        runTest {
            // Arrange
            val areaId = "test_area"

            coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns null

            // Act
            val result = tileDownloadManager.determineResumePhase(areaId)

            // Assert
            assertEquals(DownloadStatus.DOWNLOADING_BASEMAP, result)
        }

    @Test
    fun `calculateTotalTiles should return correct tile count for zoom range`() {
        // Arrange
        val boundingBox = BoundingBox(40.0, 39.0, -74.0, -75.0)
        val minZoom = 10
        val maxZoom = 12

        // Act
        val result = tileDownloadManager.calculateTotalTiles(boundingBox, minZoom, maxZoom)

        // Assert
        assertEquals(284, result)
    }

    @Test
    fun `handleExistingArea should create new area when it doesn't exist`() = runTest {
        // Arrange
        val areaId = "test_area"
        val name = "Test Area"
        val boundingBox = BoundingBox(40.0, 39.0, -74.0, -75.0)
        val minZoom = 10
        val maxZoom = 14

        coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns null
        coEvery { mockOfflineAreaDao.insertOfflineArea(any()) } returns Unit

        // Act
        tileDownloadManager.handleExistingArea(areaId, name, boundingBox, minZoom, maxZoom)

        // Assert
        coVerify {
            mockOfflineAreaDao.insertOfflineArea(match {
                it.id == areaId && it.name == name && it.status == DownloadStatus.DOWNLOADING_BASEMAP
            })
        }
    }

    @Test
    fun `handleExistingArea should handle resume logic when area exists`() = runTest {
        // Arrange
        val areaId = "test_area"
        val name = "Test Area"
        val boundingBox = BoundingBox(40.0, 39.0, -74.0, -75.0)
        val minZoom = 10
        val maxZoom = 14
        val existingArea = OfflineArea(
            id = areaId,
            name = name,
            north = 40.0,
            south = 39.0,
            east = -74.0,
            west = -75.0,
            minZoom = minZoom,
            maxZoom = maxZoom,
            downloadDate = System.currentTimeMillis(),
            fileSize = 0L,
            status = DownloadStatus.DOWNLOADING_BASEMAP
        )

        coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns existingArea
        coEvery {
            mockDownloadedTileDao.getDownloadedTileCountForAreaAndType(
                areaId,
                TileType.BASEMAP
            )
        } returns 0
        coEvery { mockOfflineAreaDao.updateOfflineArea(existingArea) } returns Unit

        // Act
        tileDownloadManager.handleExistingArea(areaId, name, boundingBox, minZoom, maxZoom)

        // Assert
        coVerify { mockOfflineAreaDao.getOfflineAreaById(areaId) }
    }

    @Test
    fun `updateAreaStatus should update area status`() = runTest {
        // Arrange
        val areaId = "test_area"
        val status = DownloadStatus.DOWNLOADING_VALHALLA
        val existingArea = OfflineArea(
            id = areaId,
            name = "Test Area",
            north = 40.0,
            south = 39.0,
            east = -74.0,
            west = -75.0,
            minZoom = 10,
            maxZoom = 14,
            downloadDate = System.currentTimeMillis(),
            fileSize = 0L,
            status = DownloadStatus.DOWNLOADING_BASEMAP
        )

        coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns existingArea
        coEvery { mockOfflineAreaDao.updateOfflineArea(existingArea.copy(status = status)) } returns Unit

        // Act
        tileDownloadManager.updateAreaStatus(areaId, status)

        // Assert
        coVerify {
            mockOfflineAreaDao.updateOfflineArea(match {
                it.id == areaId && it.status == status
            })
        }
    }

    @Test
    fun `updateAreaStatus should not update when area does not exist`() = runTest {
        // Arrange
        val areaId = "test_area"
        val status = DownloadStatus.DOWNLOADING_VALHALLA

        coEvery { mockOfflineAreaDao.getOfflineAreaById(areaId) } returns null

        // Act
        tileDownloadManager.updateAreaStatus(areaId, status)

        // Assert
        coVerify(exactly = 0) { mockOfflineAreaDao.updateOfflineArea(any()) }
    }

    @Test
    fun `createNewOfflineArea should create area with correct parameters`() = runTest {
        // Arrange
        val areaId = "test_area"
        val name = "Test Area"
        val boundingBox = BoundingBox(40.0, 39.0, -74.0, -75.0)
        val minZoom = 10
        val maxZoom = 14

        coEvery { mockOfflineAreaDao.insertOfflineArea(any()) } returns Unit

        // Act
        tileDownloadManager.createNewOfflineArea(areaId, name, boundingBox, minZoom, maxZoom)

        // Assert
        coVerify {
            mockOfflineAreaDao.insertOfflineArea(match {
                it.id == areaId &&
                        it.name == name &&
                        it.north == boundingBox.north &&
                        it.south == boundingBox.south &&
                        it.east == boundingBox.east &&
                        it.west == boundingBox.west &&
                        it.minZoom == minZoom &&
                        it.maxZoom == maxZoom &&
                        it.status == DownloadStatus.DOWNLOADING_BASEMAP
            })
        }
    }

    @Test
    fun `processBatch should skip already downloaded tiles`() = runTest {
        // Arrange
        val chunk = listOf(Triple(10, 100, 200))
        val areaId = "test_area"
        val areaName = "Test Area"
        val totalTiles = 1
        val downloadedCount = mockk<AtomicInteger>()
        val failedCount = mockk<AtomicInteger>()

        every { downloadedCount.get() } returns 0
        every { downloadedCount.incrementAndGet() } returns 1
        every {
            mockProgressReporter.updateProgress(
                areaId,
                areaName,
                any(),
                any(),
                any(),
                any(),
                any()
            )
        } returns Unit

        val existingTile = DownloadedTile(
            id = "basemap_${areaId}_10_100_200",
            areaId = areaId,
            tileType = TileType.BASEMAP,
            downloadTimestamp = System.currentTimeMillis(),
            retryCount = 0,
            zoom = 10,
            tileX = 100,
            tileY = 200
        )

        coEvery { mockDownloadedTileDao.getTileById("basemap_${areaId}_10_100_200") } returns existingTile

        // Act
        val result = tileDownloadManager.processBatch(
            chunk,
            areaId,
            areaName,
            totalTiles,
            downloadedCount,
            failedCount
        )

        // Assert
        assertTrue(result.isEmpty()) // Should skip already downloaded tiles
    }
}