mirror of
https://github.com/bitwarden/android.git
synced 2026-06-01 18:26:31 -05:00
BIT-725, BIT-328: Add base URL interceptors and dynamically change environments (#160)
This commit is contained in:
@@ -29,7 +29,6 @@ class IdentityServiceTest : BaseServiceTest() {
|
||||
private val identityService = IdentityServiceImpl(
|
||||
api = identityApi,
|
||||
json = Json,
|
||||
baseUrl = server.url("/").toString(),
|
||||
deviceModelProvider = deviceModelProvider,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.interceptor
|
||||
|
||||
import okhttp3.Request
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class BaseUrlInterceptorTest {
|
||||
private val baseUrlInterceptor = BaseUrlInterceptor()
|
||||
|
||||
@Test
|
||||
fun `intercept with a null base URL should proceed with the original request`() {
|
||||
val request = Request.Builder().url("http://www.fake.com/").build()
|
||||
val chain = FakeInterceptorChain(request)
|
||||
|
||||
val response = baseUrlInterceptor.intercept(chain)
|
||||
|
||||
assertEquals(request, response.request)
|
||||
assertEquals("http", response.request.url.scheme)
|
||||
assertEquals("www.fake.com", response.request.url.host)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `intercept with a non-null base URL should update the base URL used by the request`() {
|
||||
baseUrlInterceptor.baseUrl = "https://api.bitwarden.com"
|
||||
|
||||
val request = Request.Builder().url("http://www.fake.com/").build()
|
||||
val chain = FakeInterceptorChain(request)
|
||||
|
||||
val response = baseUrlInterceptor.intercept(chain)
|
||||
|
||||
assertNotEquals(request, response.request)
|
||||
assertEquals("https", response.request.url.scheme)
|
||||
assertEquals("api.bitwarden.com", response.request.url.host)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.interceptor
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class BaseUrlInterceptorsTest {
|
||||
private val baseUrlInterceptors = BaseUrlInterceptors()
|
||||
|
||||
@Test
|
||||
fun `the default environment should be US and all interceptors should have the correct URLs`() {
|
||||
assertEquals(
|
||||
Environment.Us,
|
||||
baseUrlInterceptors.environment,
|
||||
)
|
||||
assertEquals(
|
||||
"https://vault.bitwarden.com/api",
|
||||
baseUrlInterceptors.apiInterceptor.baseUrl,
|
||||
)
|
||||
assertEquals(
|
||||
"https://vault.bitwarden.com/identity",
|
||||
baseUrlInterceptors.identityInterceptor.baseUrl,
|
||||
)
|
||||
assertEquals(
|
||||
"https://vault.bitwarden.com/events",
|
||||
baseUrlInterceptors.eventsInterceptor.baseUrl,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `setting the environment should update all the interceptors correctly for a non-blank base URL`() {
|
||||
baseUrlInterceptors.environment = Environment.Eu
|
||||
|
||||
assertEquals(
|
||||
"https://vault.bitwarden.eu/api",
|
||||
baseUrlInterceptors.apiInterceptor.baseUrl,
|
||||
)
|
||||
assertEquals(
|
||||
"https://vault.bitwarden.eu/identity",
|
||||
baseUrlInterceptors.identityInterceptor.baseUrl,
|
||||
)
|
||||
assertEquals(
|
||||
"https://vault.bitwarden.eu/events",
|
||||
baseUrlInterceptors.eventsInterceptor.baseUrl,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `setting the environment should update all the interceptors correctly for a blank base URL and all URLs filled`() {
|
||||
baseUrlInterceptors.environment = Environment.SelfHosted(
|
||||
environmentUrlData = EnvironmentUrlDataJson(
|
||||
base = " ",
|
||||
api = "https://api.com",
|
||||
identity = "https://identity.com",
|
||||
events = "https://events.com",
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"https://api.com",
|
||||
baseUrlInterceptors.apiInterceptor.baseUrl,
|
||||
)
|
||||
assertEquals(
|
||||
"https://identity.com",
|
||||
baseUrlInterceptors.identityInterceptor.baseUrl,
|
||||
)
|
||||
assertEquals(
|
||||
"https://events.com",
|
||||
baseUrlInterceptors.eventsInterceptor.baseUrl,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `setting the environment should update all the interceptors correctly for a blank base URL and some or all URLs absent`() {
|
||||
baseUrlInterceptors.environment = Environment.SelfHosted(
|
||||
environmentUrlData = EnvironmentUrlDataJson(
|
||||
base = " ",
|
||||
api = "",
|
||||
identity = "",
|
||||
icon = " ",
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"https://api.bitwarden.com",
|
||||
baseUrlInterceptors.apiInterceptor.baseUrl,
|
||||
)
|
||||
assertEquals(
|
||||
"https://identity.bitwarden.com",
|
||||
baseUrlInterceptors.identityInterceptor.baseUrl,
|
||||
)
|
||||
assertEquals(
|
||||
"https://events.bitwarden.com",
|
||||
baseUrlInterceptors.eventsInterceptor.baseUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.retrofit
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.create
|
||||
import retrofit2.http.GET
|
||||
|
||||
class RetrofitsTest {
|
||||
private val authTokenInterceptor = mockk<AuthTokenInterceptor> {
|
||||
mockIntercept { isAuthInterceptorCalled = true }
|
||||
}
|
||||
private val baseUrlInterceptors = mockk<BaseUrlInterceptors> {
|
||||
every { apiInterceptor } returns mockk {
|
||||
mockIntercept { isApiInterceptorCalled = true }
|
||||
}
|
||||
every { identityInterceptor } returns mockk {
|
||||
mockIntercept { isIdentityInterceptorCalled = true }
|
||||
}
|
||||
every { eventsInterceptor } returns mockk {
|
||||
mockIntercept { isEventsInterceptorCalled = true }
|
||||
}
|
||||
}
|
||||
private val json = Json
|
||||
private val server = MockWebServer()
|
||||
|
||||
private val retrofits = RetrofitsImpl(
|
||||
authTokenInterceptor = authTokenInterceptor,
|
||||
baseUrlInterceptors = baseUrlInterceptors,
|
||||
json = json,
|
||||
)
|
||||
|
||||
private var isAuthInterceptorCalled = false
|
||||
private var isApiInterceptorCalled = false
|
||||
private var isIdentityInterceptorCalled = false
|
||||
private var isEventsInterceptorCalled = false
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
server.start()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
server.shutdown()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `authenticatedApiRetrofit should invoke the correct interceptors`() = runBlocking {
|
||||
val testApi = retrofits
|
||||
.authenticatedApiRetrofit
|
||||
.createMockRetrofit()
|
||||
.create<TestApi>()
|
||||
|
||||
server.enqueue(MockResponse().setBody("""{}"""))
|
||||
|
||||
testApi.test()
|
||||
|
||||
assertTrue(isAuthInterceptorCalled)
|
||||
assertTrue(isApiInterceptorCalled)
|
||||
assertFalse(isIdentityInterceptorCalled)
|
||||
assertFalse(isEventsInterceptorCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unauthenticatedApiRetrofit should invoke the correct interceptors`() = runBlocking {
|
||||
val testApi = retrofits
|
||||
.unauthenticatedApiRetrofit
|
||||
.createMockRetrofit()
|
||||
.create<TestApi>()
|
||||
|
||||
server.enqueue(MockResponse().setBody("""{}"""))
|
||||
|
||||
testApi.test()
|
||||
|
||||
assertFalse(isAuthInterceptorCalled)
|
||||
assertTrue(isApiInterceptorCalled)
|
||||
assertFalse(isIdentityInterceptorCalled)
|
||||
assertFalse(isEventsInterceptorCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unauthenticatedIdentityRetrofit should invoke the correct interceptors`() = runBlocking {
|
||||
val testApi = retrofits
|
||||
.unauthenticatedIdentityRetrofit
|
||||
.createMockRetrofit()
|
||||
.create<TestApi>()
|
||||
|
||||
server.enqueue(MockResponse().setBody("""{}"""))
|
||||
|
||||
testApi.test()
|
||||
|
||||
assertFalse(isAuthInterceptorCalled)
|
||||
assertFalse(isApiInterceptorCalled)
|
||||
assertTrue(isIdentityInterceptorCalled)
|
||||
assertFalse(isEventsInterceptorCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `staticRetrofitBuilder should invoke the correct interceptors`() = runBlocking {
|
||||
val testApi = retrofits
|
||||
.staticRetrofitBuilder
|
||||
.baseUrl(server.url("/").toString())
|
||||
.build()
|
||||
.createMockRetrofit()
|
||||
.create<TestApi>()
|
||||
|
||||
server.enqueue(MockResponse().setBody("""{}"""))
|
||||
|
||||
testApi.test()
|
||||
|
||||
assertFalse(isAuthInterceptorCalled)
|
||||
assertFalse(isApiInterceptorCalled)
|
||||
assertFalse(isIdentityInterceptorCalled)
|
||||
assertFalse(isEventsInterceptorCalled)
|
||||
}
|
||||
|
||||
private fun Retrofit.createMockRetrofit(): Retrofit =
|
||||
this
|
||||
.newBuilder()
|
||||
.baseUrl(server.url("/").toString())
|
||||
.build()
|
||||
}
|
||||
|
||||
interface TestApi {
|
||||
@GET("/test")
|
||||
suspend fun test(): JsonObject
|
||||
}
|
||||
|
||||
/**
|
||||
* Mocks the given [Interceptor] such that the [Interceptor.intercept] is a no-op but triggers the
|
||||
* [isCalledCallback].
|
||||
*/
|
||||
private fun Interceptor.mockIntercept(isCalledCallback: () -> Unit) {
|
||||
val chainSlot = slot<Interceptor.Chain>()
|
||||
every { intercept(capture(chainSlot)) } answers {
|
||||
isCalledCallback()
|
||||
val chain = chainSlot.captured
|
||||
chain.proceed(chain.request())
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.platform.repository
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
@@ -28,6 +29,7 @@ class NetworkConfigRepositoryTest {
|
||||
}
|
||||
|
||||
private val authTokenInterceptor = AuthTokenInterceptor()
|
||||
private val baseUrlInterceptors = BaseUrlInterceptors()
|
||||
|
||||
private lateinit var networkConfigRepository: NetworkConfigRepository
|
||||
|
||||
@@ -37,6 +39,7 @@ class NetworkConfigRepositoryTest {
|
||||
authRepository = authRepository,
|
||||
authTokenInterceptor = authTokenInterceptor,
|
||||
environmentRepository = environmentRepository,
|
||||
baseUrlInterceptors = baseUrlInterceptors,
|
||||
dispatcher = UnconfinedTestDispatcher(),
|
||||
)
|
||||
}
|
||||
@@ -55,4 +58,19 @@ class NetworkConfigRepositoryTest {
|
||||
mutableAuthStateFlow.value = AuthState.Unauthenticated
|
||||
assertNull(authTokenInterceptor.authToken)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `changes in the Environment should update the BaseUrlInterceptors`() {
|
||||
mutableEnvironmentStateFlow.value = Environment.Us
|
||||
assertEquals(
|
||||
Environment.Us,
|
||||
baseUrlInterceptors.environment,
|
||||
)
|
||||
|
||||
mutableEnvironmentStateFlow.value = Environment.Eu
|
||||
assertEquals(
|
||||
Environment.Eu,
|
||||
baseUrlInterceptors.environment,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.x8bit.bitwarden.data.platform.util
|
||||
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class StringExtensionsTest {
|
||||
@Test
|
||||
fun `orNullIfBlank returns null for a null String`() {
|
||||
assertNull((null as String?).orNullIfBlank())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `orNullIfBlank returns null for an empty String`() {
|
||||
assertNull("".orNullIfBlank())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `orNullIfBlank returns null for a blank String`() {
|
||||
assertNull(" ".orNullIfBlank())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `orNullIfBlank returns the original value for a non-blank String`() {
|
||||
assertEquals(
|
||||
"test",
|
||||
"test".orNullIfBlank(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user