From e8b8f1d053afa7acd2373809f8d1ea4fb957f06c Mon Sep 17 00:00:00 2001 From: nabilBouzineDev Date: Mon, 12 Aug 2024 20:48:55 +0100 Subject: [PATCH] create room db, test DAOs and add android test workflow script --- .github/workflows/pull_request.yml | 36 +++++- .../kotlin/data/local/dao/AirportDAOTest.kt | 108 ++++++++++++++++++ .../{ => data/local/dao}/FavoriteDAOTest.kt | 33 +++++- .../nabilbdev/searchflight/MainActivity.kt | 10 +- .../searchflight/data/di/AppContainer.kt | 4 +- .../searchflight/data/local/dao/AirportDAO.kt | 5 +- .../dao/{FavouriteDAO.kt => FavoriteDAO.kt} | 1 - .../local/database/SearchFlightDatabase.kt | 2 +- .../searchflight/data/local/entity/Airport.kt | 6 +- .../repository/SearchFlightRepository.kt | 13 ++- .../nabilbdev/searchflight/ExampleUnitTest.kt | 18 --- .../data/local/fake/FakeAirportDAO.kt | 37 ++++++ .../data/local/fake/FakeDataSource.kt | 23 ++++ .../data/local/fake/FakeFavoriteDAO.kt | 22 ++++ .../OfflineSearchFlightRepositoryTest.kt | 99 ++++++++++++++++ 15 files changed, 381 insertions(+), 36 deletions(-) create mode 100644 app/src/androidTest/kotlin/data/local/dao/AirportDAOTest.kt rename app/src/androidTest/kotlin/{ => data/local/dao}/FavoriteDAOTest.kt (63%) rename app/src/main/java/com/nabilbdev/searchflight/data/local/dao/{FavouriteDAO.kt => FavoriteDAO.kt} (90%) rename app/src/main/java/com/nabilbdev/searchflight/data/{ => local}/repository/SearchFlightRepository.kt (77%) delete mode 100644 app/src/test/java/com/nabilbdev/searchflight/ExampleUnitTest.kt create mode 100644 app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeAirportDAO.kt create mode 100644 app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeDataSource.kt create mode 100644 app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeFavoriteDAO.kt create mode 100644 app/src/test/java/com/nabilbdev/searchflight/data/local/repository/OfflineSearchFlightRepositoryTest.kt diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index dccc3ae..824aed9 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -22,4 +22,38 @@ jobs: run: chmod +x gradlew - name: Run unit tests - run: ./gradlew clean testDebug \ No newline at end of file + run: ./gradlew clean testDebug + + android_test_job: + name: Android Test + runs-on: macos-latest + continue-on-error: true + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Grant execute permissions for gradlew + run: chmod +x gradlew + + - name: Restore Cache + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Run Android Instrumentation Tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 29 + script: ./gradlew connectedDebugAndroidTest + + - name: Upload Android Test Reports + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: android-test-reports + path: '**/build/reports/androidTests/' diff --git a/app/src/androidTest/kotlin/data/local/dao/AirportDAOTest.kt b/app/src/androidTest/kotlin/data/local/dao/AirportDAOTest.kt new file mode 100644 index 0000000..d5b395b --- /dev/null +++ b/app/src/androidTest/kotlin/data/local/dao/AirportDAOTest.kt @@ -0,0 +1,108 @@ +package data.local.dao + +import android.content.Context +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.nabilbdev.searchflight.data.local.dao.AirportDAO +import com.nabilbdev.searchflight.data.local.database.SearchFlightDatabase +import com.nabilbdev.searchflight.data.local.entity.Airport +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.io.IOException + + +private lateinit var airportDAO: AirportDAO +private lateinit var searchFlightDatabase: SearchFlightDatabase + +// Sample data from the actual database +private val airport1 = Airport( + 1, + "Francisco Sá Carneiro Airport", + "OPO", + 5053134 +) +private val airport2 = Airport( + 2, + "Stockholm Arlanda Airport", + "ARN", + 7494765 +) + +@RunWith(AndroidJUnit4::class) +class AirportDAOTest { + + @Before + fun createDB() { + val context: Context = ApplicationProvider.getApplicationContext() + + searchFlightDatabase = + Room.databaseBuilder(context, SearchFlightDatabase::class.java, "flight_search_test") + .createFromAsset("database/flight_search.db") + .allowMainThreadQueries() // Only for testing purposes + .build() + + airportDAO = searchFlightDatabase.airportDAO() + + } + + @After + @Throws(IOException::class) + fun closeDB() { + searchFlightDatabase.close() + } + + @Test + @Throws(IOException::class) + fun daoGetAllAirports_returnAllAirports() = runBlocking { + val allAirports = airportDAO.getAllAirports().first() + + assertTrue(allAirports.isNotEmpty()) + assertEquals(allAirports[0], airport1) + assertEquals(allAirports[1], airport2) + } + + @Test + @Throws(IOException::class) + fun daoGetAirportByCode_returnSingleAirport() = runBlocking { + val airportResultOne = airportDAO.getAirportByCode("OPO") + val airportResultTwo = airportDAO.getAirportByCode("ARN") + + assertEquals(airportResultOne.first(), airport1) + assertEquals(airportResultTwo.first(), airport2) + } + + @Test + @Throws(IOException::class) + fun daoGetAirportExcept_verifyNotExistInReturnedAirports() = runBlocking { + val allAirports = airportDAO.getAllAirportsExcept("OPO").first() + + assertTrue(airport1 !in allAirports) // airport1 is not in allAirports + assertTrue(airport2 in allAirports) + } + + @Test + @Throws(IOException::class) + fun daoGetAirportByQuery_verifyNotReturnAllResultsFromDB() = runBlocking { + val query = "FR" + val allAirportsByQuery = airportDAO.getAirportsByQuery("%$query%").first() + val allAirports = airportDAO.getAllAirports().first() + + assertTrue(allAirports.size > allAirportsByQuery.size) + } + + @Test + @Throws(IOException::class) + fun daoGetAirportByQuery_verifyReturnedAirportsIncludeQuery() = runBlocking { + val query = "FR" + val allAirportsByQuery = airportDAO.getAirportsByQuery("%$query%").first() + + assertTrue(allAirportsByQuery[0].name.contains(query, ignoreCase = true)) + assertTrue(allAirportsByQuery[1].name.contains(query, ignoreCase = true)) + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/FavoriteDAOTest.kt b/app/src/androidTest/kotlin/data/local/dao/FavoriteDAOTest.kt similarity index 63% rename from app/src/androidTest/kotlin/FavoriteDAOTest.kt rename to app/src/androidTest/kotlin/data/local/dao/FavoriteDAOTest.kt index b2edc4d..77bc049 100644 --- a/app/src/androidTest/kotlin/FavoriteDAOTest.kt +++ b/app/src/androidTest/kotlin/data/local/dao/FavoriteDAOTest.kt @@ -1,4 +1,4 @@ -package com.nabilbdev.searchflight +package data.local.dao import android.content.Context import androidx.room.Room @@ -10,7 +10,7 @@ import com.nabilbdev.searchflight.data.local.entity.Favorite import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After -import org.junit.Assert +import org.junit.Assert.* import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -50,14 +50,37 @@ class FavoriteDAOTest { } private suspend fun addTwoFavoriteToDB() { + favoriteDAO.insert(favorite1) favoriteDAO.insert(favorite2) } @Test @Throws(IOException::class) - fun daoInsert_InsertFavoritesToDB() = runBlocking { + fun daoInsert_insertFavoritesToDB() = runBlocking { addOneFavoriteToDB() - val allItems = favoriteDAO.getAllFavoriteAirports().first() - Assert.assertEquals(allItems[0], favorite1) + val allFavorites = favoriteDAO.getAllFavoriteAirports().first() + + assertEquals(allFavorites[0], favorite1) + } + + @Test + @Throws(IOException::class) + fun daoDelete_deleteFavoritesFromDB() = runBlocking { + addTwoFavoriteToDB() + favoriteDAO.delete(favorite1) + favoriteDAO.delete(favorite2) + + val allFavorites = favoriteDAO.getAllFavoriteAirports().first() + assertTrue(allFavorites.isEmpty()) + } + + @Test + @Throws(IOException::class) + fun daoGetAllFavorites_returnAllFavoritesFromDB() = runBlocking { + addTwoFavoriteToDB() + val allFavorites = favoriteDAO.getAllFavoriteAirports().first() + + assertEquals(allFavorites[0], favorite1) + assertEquals(allFavorites[1], favorite2) } } \ No newline at end of file diff --git a/app/src/main/java/com/nabilbdev/searchflight/MainActivity.kt b/app/src/main/java/com/nabilbdev/searchflight/MainActivity.kt index 063b913..a125b76 100644 --- a/app/src/main/java/com/nabilbdev/searchflight/MainActivity.kt +++ b/app/src/main/java/com/nabilbdev/searchflight/MainActivity.kt @@ -4,9 +4,12 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.nabilbdev.searchflight.ui.theme.SearchFlightTheme @@ -20,7 +23,12 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.surface ) { - // Code Here... + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text(text = "Room database is ready!") + } } } } diff --git a/app/src/main/java/com/nabilbdev/searchflight/data/di/AppContainer.kt b/app/src/main/java/com/nabilbdev/searchflight/data/di/AppContainer.kt index 2c353be..646ea90 100644 --- a/app/src/main/java/com/nabilbdev/searchflight/data/di/AppContainer.kt +++ b/app/src/main/java/com/nabilbdev/searchflight/data/di/AppContainer.kt @@ -2,8 +2,8 @@ package com.nabilbdev.searchflight.data.di import android.content.Context import com.nabilbdev.searchflight.data.local.database.SearchFlightDatabase -import com.nabilbdev.searchflight.data.repository.OfflineSearchFlightRepository -import com.nabilbdev.searchflight.data.repository.SearchFlightRepository +import com.nabilbdev.searchflight.data.local.repository.OfflineSearchFlightRepository +import com.nabilbdev.searchflight.data.local.repository.SearchFlightRepository interface AppContainer { val searchFlightRepository: SearchFlightRepository diff --git a/app/src/main/java/com/nabilbdev/searchflight/data/local/dao/AirportDAO.kt b/app/src/main/java/com/nabilbdev/searchflight/data/local/dao/AirportDAO.kt index 9ef16fc..5bf2508 100644 --- a/app/src/main/java/com/nabilbdev/searchflight/data/local/dao/AirportDAO.kt +++ b/app/src/main/java/com/nabilbdev/searchflight/data/local/dao/AirportDAO.kt @@ -17,5 +17,8 @@ interface AirportDAO { fun getAllAirportsExcept(airportCode: String): Flow> @Query("SELECT * FROM airport WHERE iata_code = :airportCode ") - fun getAirportsByCode(airportCode: String): Flow + fun getAirportByCode(airportCode: String): Flow + + @Query("SELECT * FROM airport") + fun getAllAirports(): Flow> } \ No newline at end of file diff --git a/app/src/main/java/com/nabilbdev/searchflight/data/local/dao/FavouriteDAO.kt b/app/src/main/java/com/nabilbdev/searchflight/data/local/dao/FavoriteDAO.kt similarity index 90% rename from app/src/main/java/com/nabilbdev/searchflight/data/local/dao/FavouriteDAO.kt rename to app/src/main/java/com/nabilbdev/searchflight/data/local/dao/FavoriteDAO.kt index 387d064..a05d159 100644 --- a/app/src/main/java/com/nabilbdev/searchflight/data/local/dao/FavouriteDAO.kt +++ b/app/src/main/java/com/nabilbdev/searchflight/data/local/dao/FavoriteDAO.kt @@ -5,7 +5,6 @@ import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import com.nabilbdev.searchflight.data.local.entity.Airport import com.nabilbdev.searchflight.data.local.entity.Favorite import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/com/nabilbdev/searchflight/data/local/database/SearchFlightDatabase.kt b/app/src/main/java/com/nabilbdev/searchflight/data/local/database/SearchFlightDatabase.kt index 7b24b07..944de57 100644 --- a/app/src/main/java/com/nabilbdev/searchflight/data/local/database/SearchFlightDatabase.kt +++ b/app/src/main/java/com/nabilbdev/searchflight/data/local/database/SearchFlightDatabase.kt @@ -23,7 +23,7 @@ abstract class SearchFlightDatabase : RoomDatabase() { fun getDatabase(context: Context): SearchFlightDatabase { return Instance ?: synchronized(this) { Room.databaseBuilder(context, SearchFlightDatabase::class.java, "flight_search") - .createFromAsset("/database/flight_search.db") + .createFromAsset("database/flight_search.db") .fallbackToDestructiveMigration() .build() .also { Instance = it } diff --git a/app/src/main/java/com/nabilbdev/searchflight/data/local/entity/Airport.kt b/app/src/main/java/com/nabilbdev/searchflight/data/local/entity/Airport.kt index 57da183..a160cf6 100644 --- a/app/src/main/java/com/nabilbdev/searchflight/data/local/entity/Airport.kt +++ b/app/src/main/java/com/nabilbdev/searchflight/data/local/entity/Airport.kt @@ -6,8 +6,10 @@ import androidx.room.PrimaryKey @Entity(tableName = "airport") data class Airport( - @PrimaryKey val id: Int, + @PrimaryKey + val id: Int, val name: String, - @ColumnInfo(name = "iata_code") val iataCode: String, + @ColumnInfo(name = "iata_code") + val iataCode: String, val passengers: Int, ) diff --git a/app/src/main/java/com/nabilbdev/searchflight/data/repository/SearchFlightRepository.kt b/app/src/main/java/com/nabilbdev/searchflight/data/local/repository/SearchFlightRepository.kt similarity index 77% rename from app/src/main/java/com/nabilbdev/searchflight/data/repository/SearchFlightRepository.kt rename to app/src/main/java/com/nabilbdev/searchflight/data/local/repository/SearchFlightRepository.kt index 68986cb..af9c547 100644 --- a/app/src/main/java/com/nabilbdev/searchflight/data/repository/SearchFlightRepository.kt +++ b/app/src/main/java/com/nabilbdev/searchflight/data/local/repository/SearchFlightRepository.kt @@ -1,4 +1,4 @@ -package com.nabilbdev.searchflight.data.repository +package com.nabilbdev.searchflight.data.local.repository import com.nabilbdev.searchflight.data.local.dao.AirportDAO import com.nabilbdev.searchflight.data.local.dao.FavoriteDAO @@ -12,10 +12,12 @@ interface SearchFlightRepository { fun getAllAirportsExceptStream(airportCode: String): Flow> - fun getAirportsByCodeStream(airportCode: String): Flow + fun getAirportByCodeStream(airportCode: String): Flow fun getAllFavoriteAirportsStream(): Flow> + fun getAllAirportsStream(): Flow> + suspend fun insertFavoriteAirport(favorite: Favorite) suspend fun deleteFavoriteAirport(favorite: Favorite) @@ -32,12 +34,15 @@ class OfflineSearchFlightRepository( override fun getAllAirportsExceptStream(airportCode: String): Flow> = airportDAO.getAllAirportsExcept(airportCode) - override fun getAirportsByCodeStream(airportCode: String): Flow = - airportDAO.getAirportsByCode(airportCode) + override fun getAirportByCodeStream(airportCode: String): Flow = + airportDAO.getAirportByCode(airportCode) override fun getAllFavoriteAirportsStream(): Flow> = favoriteDAO.getAllFavoriteAirports() + override fun getAllAirportsStream(): Flow> = + airportDAO.getAllAirports() + override suspend fun insertFavoriteAirport(favorite: Favorite) = favoriteDAO.insert(favorite) diff --git a/app/src/test/java/com/nabilbdev/searchflight/ExampleUnitTest.kt b/app/src/test/java/com/nabilbdev/searchflight/ExampleUnitTest.kt deleted file mode 100644 index 64682a6..0000000 --- a/app/src/test/java/com/nabilbdev/searchflight/ExampleUnitTest.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.nabilbdev.searchflight - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ - -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(6, 3 + 3) - } -} \ No newline at end of file diff --git a/app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeAirportDAO.kt b/app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeAirportDAO.kt new file mode 100644 index 0000000..9bc0805 --- /dev/null +++ b/app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeAirportDAO.kt @@ -0,0 +1,37 @@ +package com.nabilbdev.searchflight.data.local.fake + +import com.nabilbdev.searchflight.data.local.dao.AirportDAO +import com.nabilbdev.searchflight.data.local.entity.Airport +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flow + +class FakeAirportDAO : AirportDAO { + private val airports = FakeDataSource.fakeAirports + + /** + * We mimic the same SQLITE query behaviour exist in the [AirportDAO]. + * + * f.e: LIKE %query% changed to it.name.contains(query) + */ + override fun getAirportsByQuery(query: String): Flow> = flow { + + emit( + airports.filter { + it.name.contains(query, ignoreCase = true) || it.iataCode.contains(query) + } + ) + } + + override fun getAllAirportsExcept(airportCode: String): Flow> = flow { + emit(airports.filter { it.iataCode != airportCode }) + } + + override fun getAirportByCode(airportCode: String): Flow = flow { + airports.find { it.iataCode == airportCode }?.let { emit(it) } + }.filterNotNull() + + override fun getAllAirports(): Flow> = flow { + emit(airports) + } +} diff --git a/app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeDataSource.kt b/app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeDataSource.kt new file mode 100644 index 0000000..09e0794 --- /dev/null +++ b/app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeDataSource.kt @@ -0,0 +1,23 @@ +package com.nabilbdev.searchflight.data.local.fake + +import com.nabilbdev.searchflight.data.local.entity.Airport +import com.nabilbdev.searchflight.data.local.entity.Favorite + +object FakeDataSource { + + val fakeAirports = listOf( + Airport(1, "Los Angeles", "LAX", 100), + Airport(2, "New York", "JFK", 19), + Airport(3, "Chicago", "ORD", 20), + Airport(4, "San Francisco", "SFO", 40), + Airport(5, "Atlanta", "ATL", 599), + Airport(6, "Paris", "CDG", 30), + Airport(7, "London", "LHR", 19), + Airport(8, "Tokyo", "NRT", 201), + Airport(9, "Sydney", "SYD", 200), + Airport(10, "Dubai", "DXB", 1999) + ) + + val fakeFavoriteAirport1 = Favorite(0, "Lax", "FAN") + val fakeFavoriteAirport2 = Favorite(1, "DAM", "BAN") +} \ No newline at end of file diff --git a/app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeFavoriteDAO.kt b/app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeFavoriteDAO.kt new file mode 100644 index 0000000..b8d476b --- /dev/null +++ b/app/src/test/java/com/nabilbdev/searchflight/data/local/fake/FakeFavoriteDAO.kt @@ -0,0 +1,22 @@ +package com.nabilbdev.searchflight.data.local.fake + +import com.nabilbdev.searchflight.data.local.dao.FavoriteDAO +import com.nabilbdev.searchflight.data.local.entity.Favorite +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +class FakeFavoriteDAO : FavoriteDAO { + private val favoriteAirports = mutableListOf() + + override suspend fun insert(favorite: Favorite) { + favoriteAirports.add(favorite) + } + + override suspend fun delete(favorite: Favorite) { + favoriteAirports.remove(favorite) + } + + override fun getAllFavoriteAirports(): Flow> = flow { + emit(favoriteAirports) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/nabilbdev/searchflight/data/local/repository/OfflineSearchFlightRepositoryTest.kt b/app/src/test/java/com/nabilbdev/searchflight/data/local/repository/OfflineSearchFlightRepositoryTest.kt new file mode 100644 index 0000000..550a74f --- /dev/null +++ b/app/src/test/java/com/nabilbdev/searchflight/data/local/repository/OfflineSearchFlightRepositoryTest.kt @@ -0,0 +1,99 @@ +package com.nabilbdev.searchflight.data.local.repository + +import com.nabilbdev.searchflight.data.local.entity.Airport +import com.nabilbdev.searchflight.data.local.fake.FakeAirportDAO +import com.nabilbdev.searchflight.data.local.fake.FakeDataSource +import com.nabilbdev.searchflight.data.local.fake.FakeFavoriteDAO +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + + +private lateinit var fakeAirportDAO: FakeAirportDAO +private lateinit var fakeFavoriteDAO: FakeFavoriteDAO +private lateinit var offlineSearchFlightRepo: OfflineSearchFlightRepository +private lateinit var fakeAirports: List + +class OfflineSearchFlightRepositoryTest { + + @Before + fun setUp() { + fakeAirportDAO = FakeAirportDAO() + fakeFavoriteDAO = FakeFavoriteDAO() + offlineSearchFlightRepo = OfflineSearchFlightRepository(fakeAirportDAO, fakeFavoriteDAO) + fakeAirports = FakeDataSource.fakeAirports + } + + // Areas where Repository interact with AirportDAO + @Test + fun testGetAirportsByQuery_withMatchingQuery_returnsSingleExpectedAirport() = runBlocking { + val result = offlineSearchFlightRepo.getAirportsByQueryStream("LAX").first() + + assertTrue(result.size == 1) + assertEquals(result[0].iataCode, fakeAirports[0].iataCode) + } + + @Test + fun testGetAirportsByQuery_withUnmatchedQuery_returnsExpectedAirports() = runBlocking { + val result = offlineSearchFlightRepo.getAirportsByQueryStream("-%;").first() + + assertTrue(result.isEmpty()) + } + + @Test + fun testGetAllAirportsExcept_withExcludedAirport_returnsRemainingAirports() = runBlocking { + val result = offlineSearchFlightRepo.getAllAirportsExceptStream("LAX").first() + assertTrue(result.none { it.iataCode == fakeAirports[0].iataCode }) + } + + @Test + fun testGetAirportByCode_withValidCode_returnsMatchingAirport() = runBlocking { + val result = offlineSearchFlightRepo.getAirportByCodeStream("LAX").first() + assertEquals(result, fakeAirports[0]) + } + + @Test + fun testGetAllAirports_returnsCompleteListOfAirports() = runBlocking { + val result = offlineSearchFlightRepo.getAllAirportsStream().first() + assertEquals(result.size, fakeAirports.size) + assertEquals(result[0], fakeAirports[0]) + assertEquals(result[1], fakeAirports[1]) + assertEquals(result[5], fakeAirports[5]) + } + + // Areas where Repository interact with FavoriteDAO + private suspend fun addOneFavoriteToDB() { + fakeFavoriteDAO.insert(FakeDataSource.fakeFavoriteAirport1) + } + + private suspend fun addTwoFavoriteToDB() { + fakeFavoriteDAO.insert(FakeDataSource.fakeFavoriteAirport1) + fakeFavoriteDAO.insert(FakeDataSource.fakeFavoriteAirport2) + } + + @Test + fun testDeleteFavorite_removesFavoriteSuccessfully() = runBlocking { + + addOneFavoriteToDB() + offlineSearchFlightRepo.deleteFavoriteAirport(FakeDataSource.fakeFavoriteAirport1) + + val result = offlineSearchFlightRepo.getAllFavoriteAirportsStream().first() + assertFalse(result.contains(FakeDataSource.fakeFavoriteAirport1)) + } + + @Test + fun testGetAllFavoriteAirports_returnsCompleteListOfFavoriteAirports() = runBlocking { + val result = offlineSearchFlightRepo.getAllFavoriteAirportsStream().first() + assertTrue(result.isEmpty()) + + addTwoFavoriteToDB() + + assertEquals(result.size, 2) + assertEquals(result[0], FakeDataSource.fakeFavoriteAirport1) + assertEquals(result[1], FakeDataSource.fakeFavoriteAirport2) + } +}