diff --git a/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/FirebaseEmulatorConnection.kt b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/FirebaseEmulatorConnection.kt new file mode 100644 index 00000000..306b09b1 --- /dev/null +++ b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/FirebaseEmulatorConnection.kt @@ -0,0 +1,12 @@ +package com.omricat.maplibrarian.firebase + +object FirebaseEmulatorConnection { + + // Connects to host computer from Android emulator + const val HOST = "10.0.2.2" + + const val AUTH_PORT = 9099 + + const val FIRESTORE_PORT = 8080 + +} diff --git a/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/FirebaseEndToEndTest.kt b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/FirebaseEndToEndTest.kt new file mode 100644 index 00000000..c30a695c --- /dev/null +++ b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/FirebaseEndToEndTest.kt @@ -0,0 +1,97 @@ +package com.omricat.maplibrarian.firebase + +import com.github.michaelbull.result.Ok +import com.github.michaelbull.result.getOrThrow +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FirebaseFirestore +import com.omricat.maplibrarian.auth.EmailPasswordCredential +import com.omricat.maplibrarian.chartlist.FirebaseChartsService +import com.omricat.maplibrarian.firebase.auth.FirebaseAuthEmulatorRestApi +import com.omricat.maplibrarian.firebase.auth.FirebaseAuthService +import com.omricat.maplibrarian.firebase.charts.FirebaseFirestoreRestApi +import com.omricat.maplibrarian.model.DbChartModel +import com.omricat.maplibrarian.model.UnsavedChartModel +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class FirebaseEndToEndTest { + + @Before + fun clearData() { + authApi.deleteAllUsers() + firestoreApi.deleteAllData() + } + + private val testCredential = EmailPasswordCredential("test@example.com", "password") + + @Test + fun addUser_addCharts_queryCharts() = runTest { + val testDispatcherProvider = TestDispatcherProvider(testScheduler) + val userRepository = FirebaseAuthService(firebaseAuthInstance, testDispatcherProvider) + + val chartsRepository = FirebaseChartsService(firestoreInstance, testDispatcherProvider) + + val createUserResult = userRepository.createUser(testCredential) + + val user = createUserResult.getOrThrow { AssertionError(it) } + + val addChartResult = + chartsRepository.addNewChart(user, UnsavedChartModel(user.id, "New map")) + + assert(addChartResult is Ok) + + val queryChartResult = + chartsRepository.chartsListForUser(user) + + queryChartResult.getOrThrow { AssertionError(it) }.run { + assert(size == 1) + assert(first().title == "New map") + } + } + + companion object Fixtures { + + @JvmStatic lateinit var firestoreInstance: FirebaseFirestore + + @JvmStatic lateinit var firestoreApi: FirebaseFirestoreRestApi + + @JvmStatic lateinit var firebaseAuthInstance: FirebaseAuth + + @JvmStatic lateinit var authApi: FirebaseAuthEmulatorRestApi + + @JvmStatic + @BeforeClass + fun setup() { + firestoreInstance = + FirebaseFirestore.getInstance(TestFixtures.app).apply { + useEmulator( + FirebaseEmulatorConnection.HOST, + FirebaseEmulatorConnection.FIRESTORE_PORT + ) + } + + firestoreApi = + FirebaseFirestoreRestApi( + TestFixtures.projectId, + TestFixtures.emulatorBaseUrl(FirebaseEmulatorConnection.FIRESTORE_PORT) + ) + + firebaseAuthInstance = + FirebaseAuth.getInstance(TestFixtures.app).apply { + useEmulator( + FirebaseEmulatorConnection.HOST, + FirebaseEmulatorConnection.AUTH_PORT + ) + } + authApi = + FirebaseAuthEmulatorRestApi( + TestFixtures.projectId, + TestFixtures.emulatorBaseUrl(FirebaseEmulatorConnection.AUTH_PORT) + ) + } + } +} diff --git a/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/TestDispatcherProvider.kt b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/TestDispatcherProvider.kt new file mode 100644 index 00000000..78f1b949 --- /dev/null +++ b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/TestDispatcherProvider.kt @@ -0,0 +1,20 @@ +package com.omricat.maplibrarian.firebase + +import com.omricat.maplibrarian.utils.DispatcherProvider +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler + +@OptIn(ExperimentalCoroutinesApi::class) +class TestDispatcherProvider(scheduler: TestCoroutineScheduler) : DispatcherProvider { + private val dispatcher = StandardTestDispatcher(scheduler) + override val default: CoroutineDispatcher + get() = dispatcher + override val io: CoroutineDispatcher + get() = dispatcher + override val main: CoroutineDispatcher + get() = dispatcher + override val unconfined: CoroutineDispatcher + get() = dispatcher +} diff --git a/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/TestFixtures.kt b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/TestFixtures.kt new file mode 100644 index 00000000..dcdf902f --- /dev/null +++ b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/TestFixtures.kt @@ -0,0 +1,19 @@ +package com.omricat.maplibrarian.firebase + +import androidx.test.core.app.ApplicationProvider +import com.google.firebase.FirebaseApp +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Builder + +object TestFixtures { + val app: FirebaseApp by lazy { + FirebaseApp.initializeApp(ApplicationProvider.getApplicationContext()) + ?: error("Failed to initialize FirebaseApp") + } + + val projectId: String + get() = app.options.projectId ?: error("Can't get projectId from FirebaseApp options") + + fun emulatorBaseUrl(port: Int): HttpUrl = + Builder().host(FirebaseEmulatorConnection.HOST).port(port).scheme("http").build() +} diff --git a/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/auth/FirebaseAuthEmulatorRestApi.kt b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/auth/FirebaseAuthEmulatorRestApi.kt index 0e548cc9..c8c078fa 100644 --- a/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/auth/FirebaseAuthEmulatorRestApi.kt +++ b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/auth/FirebaseAuthEmulatorRestApi.kt @@ -2,6 +2,7 @@ package com.omricat.maplibrarian.firebase.auth import okhttp3.HttpUrl import retrofit2.Call +import retrofit2.Response import retrofit2.Retrofit import retrofit2.create import retrofit2.http.DELETE @@ -9,15 +10,13 @@ import retrofit2.http.Path class FirebaseAuthEmulatorRestApi(private val projectId: String, baseUrl: HttpUrl) { - private interface RetrofitEmulatorApi { + private interface AuthEmulatorApi { @DELETE("/emulator/v1/projects/{project-id}/accounts") fun deleteAllUsers(@Path("project-id") projectId: String): Call } - private val wrappedApi: RetrofitEmulatorApi = + private val wrappedApi: AuthEmulatorApi = Retrofit.Builder().baseUrl(baseUrl).build().create() - fun deleteAllUsers() { - wrappedApi.deleteAllUsers(projectId).execute() - } + fun deleteAllUsers(): Response = wrappedApi.deleteAllUsers(projectId).execute() } diff --git a/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/auth/FirebaseAuthServiceTest.kt b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/auth/FirebaseAuthServiceTest.kt index 3f7707f6..d4dde648 100644 --- a/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/auth/FirebaseAuthServiceTest.kt +++ b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/auth/FirebaseAuthServiceTest.kt @@ -1,85 +1,57 @@ package com.omricat.maplibrarian.firebase.auth -import androidx.test.core.app.ApplicationProvider import com.github.michaelbull.result.Ok -import com.google.firebase.FirebaseApp import com.google.firebase.auth.FirebaseAuth import com.omricat.maplibrarian.auth.EmailPasswordCredential +import com.omricat.maplibrarian.firebase.FirebaseEmulatorConnection +import com.omricat.maplibrarian.firebase.TestDispatcherProvider +import com.omricat.maplibrarian.firebase.TestFixtures import com.omricat.maplibrarian.model.User -import com.omricat.maplibrarian.utils.DispatcherProvider -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.runTest -import okhttp3.HttpUrl import org.junit.Before import org.junit.BeforeClass import org.junit.Test -// Connects to host computer from Android emulator -private const val FIREBASE_EMULATOR_HOST = "10.0.2.2" - -private const val FIREBASE_EMULATOR_AUTH_PORT = 9099 - @OptIn(ExperimentalCoroutinesApi::class) class FirebaseAuthServiceTest { @Before fun clearUsers() { - emulatorAPI.deleteAllUsers() + authApi.deleteAllUsers() } - @Test - fun addUserSucceeds() { - val credential = EmailPasswordCredential("test@example.com", "password") + private val testCredential = EmailPasswordCredential("test@example.com", "password") - runTest { - val repository = - FirebaseAuthService(firebaseAuthInstance, TestDispatcherProvider(testScheduler)) - val createUserResult = repository.createUser(credential) - assert(createUserResult is Ok) - } + @Test + fun addUserSucceeds() = runTest { + val repository = + FirebaseAuthService(firebaseAuthInstance, TestDispatcherProvider(testScheduler)) + val createUserResult = repository.createUser(testCredential) + assert(createUserResult is Ok) } companion object Fixtures { @JvmStatic lateinit var firebaseAuthInstance: FirebaseAuth - @JvmStatic lateinit var emulatorAPI: FirebaseAuthEmulatorRestApi + @JvmStatic lateinit var authApi: FirebaseAuthEmulatorRestApi @JvmStatic @BeforeClass fun setUp() { - val app: FirebaseApp = - FirebaseApp.initializeApp(ApplicationProvider.getApplicationContext()) - ?: error("Failed to initialize FirebaseApp") firebaseAuthInstance = - FirebaseAuth.getInstance(app).apply { - useEmulator(FIREBASE_EMULATOR_HOST, FIREBASE_EMULATOR_AUTH_PORT) + FirebaseAuth.getInstance(TestFixtures.app).apply { + useEmulator( + FirebaseEmulatorConnection.HOST, + FirebaseEmulatorConnection.AUTH_PORT + ) } - emulatorAPI = + authApi = FirebaseAuthEmulatorRestApi( - app.options.projectId!!, - HttpUrl.Builder() - .host(FIREBASE_EMULATOR_HOST) - .port(FIREBASE_EMULATOR_AUTH_PORT) - .scheme("http") - .build() + TestFixtures.projectId, + TestFixtures.emulatorBaseUrl(FirebaseEmulatorConnection.AUTH_PORT) ) } } } - -@OptIn(ExperimentalCoroutinesApi::class) -class TestDispatcherProvider(scheduler: TestCoroutineScheduler) : DispatcherProvider { - private val dispatcher = StandardTestDispatcher(scheduler) - override val default: CoroutineDispatcher - get() = dispatcher - override val io: CoroutineDispatcher - get() = dispatcher - override val main: CoroutineDispatcher - get() = dispatcher - override val unconfined: CoroutineDispatcher - get() = dispatcher -} diff --git a/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/charts/FirebaseFirestoreRestApi.kt b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/charts/FirebaseFirestoreRestApi.kt new file mode 100644 index 00000000..569bf20e --- /dev/null +++ b/integration-tests/firebase/src/main/kotlin/com/omricat/maplibrarian/firebase/charts/FirebaseFirestoreRestApi.kt @@ -0,0 +1,23 @@ +package com.omricat.maplibrarian.firebase.charts + +import okhttp3.HttpUrl +import retrofit2.Call +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.create +import retrofit2.http.DELETE +import retrofit2.http.Path + +class FirebaseFirestoreRestApi(private val projectId: String, baseUrl: HttpUrl) { + + private interface FirestoreEmulatorApi { + + @DELETE("/emulator/v1/projects/{project-id}/databases/(default)/documents") + fun deleteDefaultDatabase(@Path("project-id") projectId: String): Call + } + + private val wrappedApi: FirestoreEmulatorApi = + Retrofit.Builder().baseUrl(baseUrl).build().create() + + fun deleteAllData(): Response = wrappedApi.deleteDefaultDatabase(projectId).execute() +}