Skip to content

Commit 1b424ce

Browse files
committed
Migrate to ktor
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
1 parent d359a15 commit 1b424ce

7 files changed

Lines changed: 220 additions & 146 deletions

File tree

app/src/androidTest/java/at/bitfire/icsdroid/CalendarFetcherTest.kt

Lines changed: 59 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,21 @@ import android.content.ContentResolver
88
import android.content.Context
99
import android.net.Uri
1010
import androidx.test.platform.app.InstrumentationRegistry
11-
import at.bitfire.icsdroid.HttpUtils.toAndroidUri
11+
import at.bitfire.icsdroid.MockEngineWrapper.engine
1212
import at.bitfire.icsdroid.test.BuildConfig
1313
import at.bitfire.icsdroid.test.R
14-
import kotlinx.coroutines.Dispatchers
14+
import io.ktor.client.utils.buildHeaders
15+
import io.ktor.http.ContentType
16+
import io.ktor.http.HttpHeaders
17+
import io.ktor.http.HttpStatusCode
18+
import io.ktor.http.headers
1519
import kotlinx.coroutines.runBlocking
16-
import kotlinx.coroutines.withContext
17-
import okhttp3.MediaType
18-
import okhttp3.mockwebserver.MockResponse
19-
import okhttp3.mockwebserver.MockWebServer
20-
import org.junit.AfterClass
2120
import org.junit.Assert.assertArrayEquals
2221
import org.junit.Assert.assertEquals
23-
import org.junit.BeforeClass
22+
import org.junit.Before
2423
import org.junit.Test
2524
import java.io.IOException
2625
import java.io.InputStream
27-
import java.net.HttpURLConnection
2826
import java.util.LinkedList
2927

3028
class CalendarFetcherTest {
@@ -34,29 +32,21 @@ class CalendarFetcherTest {
3432
val appContext: Context by lazy { InstrumentationRegistry.getInstrumentation().targetContext }
3533
val testContext: Context by lazy { InstrumentationRegistry.getInstrumentation().context }
3634

37-
val server = MockWebServer()
38-
39-
@BeforeClass
40-
@JvmStatic
41-
fun setUp() {
42-
server.start()
43-
}
44-
45-
@AfterClass
46-
@JvmStatic
47-
fun tearDown() {
48-
server.shutdown()
49-
}
35+
}
5036

37+
@Before
38+
fun setUp() {
39+
MockEngineWrapper.clear()
5140
}
5241

5342
@Test
5443
fun testFetchLocal_readsCorrectly() {
44+
val client = HttpClient(appContext, engine)
5545
val uri = Uri.parse("${ContentResolver.SCHEME_ANDROID_RESOURCE}://${BuildConfig.APPLICATION_ID}/${R.raw.vienna_evolution}")
5646

5747
var ical: String? = null
58-
val fetcher = object: CalendarFetcher(appContext, uri) {
59-
override suspend fun onSuccess(data: InputStream, contentType: MediaType?, eTag: String?, lastModified: Long?, displayName: String?) {
48+
val fetcher = object: CalendarFetcher(appContext, uri, client) {
49+
override suspend fun onSuccess(data: InputStream, contentType: ContentType?, eTag: String?, lastModified: Long?, displayName: String?) {
6050
ical = data.bufferedReader().use { it.readText() }
6151
}
6252
}
@@ -72,25 +62,29 @@ class CalendarFetcherTest {
7262

7363
@Test
7464
fun testFetchNetwork_success() {
65+
val client = HttpClient(appContext, engine)
7566
val etagCorrect = "33a64df551425fcc55e4d42a148795d9f25f89d4"
7667
val lastModifiedCorrect = "Wed, 21 Oct 2015 07:28:00 GMT" // UNIX timestamp 1445405280
7768
val icalCorrect = testContext.resources.openRawResource(R.raw.vienna_evolution).use { streamCorrect ->
7869
streamCorrect.bufferedReader().use { it.readText() }
7970
}
8071

8172
// create mock response
82-
server.enqueue(MockResponse()
83-
.setResponseCode(HttpURLConnection.HTTP_OK)
84-
.addHeader("ETag", etagCorrect)
85-
.addHeader("Last-Modified", lastModifiedCorrect)
86-
.setBody(icalCorrect))
73+
MockEngineWrapper.enqueue(
74+
content = icalCorrect,
75+
status = HttpStatusCode.OK,
76+
headers = buildHeaders {
77+
append(HttpHeaders.ETag, etagCorrect)
78+
append(HttpHeaders.LastModified, lastModifiedCorrect)
79+
}
80+
)
8781

8882
// make request to local mock server
8983
var ical: String? = null
9084
var etag: String? = null
9185
var lastmod: Long? = null
92-
val fetcher = object: CalendarFetcher(appContext, server.url("/").toAndroidUri()) {
93-
override suspend fun onSuccess(data: InputStream, contentType: MediaType?, eTag: String?, lastModified: Long?, displayName: String?) {
86+
val fetcher = object: CalendarFetcher(appContext, MockEngineWrapper.uri(), client) {
87+
override suspend fun onSuccess(data: InputStream, contentType: ContentType?, eTag: String?, lastModified: Long?, displayName: String?) {
9488
ical = data.bufferedReader().use { it.readText() }
9589
etag = eTag
9690
lastmod = lastModified
@@ -108,30 +102,41 @@ class CalendarFetcherTest {
108102

109103
@Test
110104
fun testFetchNetwork_onRedirectWithLocation() {
105+
val client = HttpClient(appContext, engine)
106+
111107
// create mock responses:
112108
// 1. redirect with absolute target URL
113-
server.enqueue(MockResponse()
114-
.setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
115-
.addHeader("Location", server.url("new-location/vienna-evolution.ics")))
109+
MockEngineWrapper.enqueue(
110+
status = HttpStatusCode.TemporaryRedirect,
111+
headers = headers {
112+
append(
113+
HttpHeaders.Location, MockEngineWrapper.uri("new-location", "vienna-evolution.ics").toString()
114+
)
115+
}
116+
)
116117
// 2. redirect with relative target URL
117-
server.enqueue(MockResponse()
118-
.setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
119-
.addHeader("Location", "the-file-is-here"))
118+
MockEngineWrapper.enqueue(
119+
status = HttpStatusCode.TemporaryRedirect,
120+
headers = headers {
121+
append(HttpHeaders.Location, "the-file-is-here")
122+
}
123+
)
120124
// 3. finally the real resource
121-
server.enqueue(MockResponse()
122-
.setResponseCode(HttpURLConnection.HTTP_OK)
123-
.setBody("icalCorrect"))
125+
MockEngineWrapper.enqueue(
126+
content = "icalCorrect",
127+
status = HttpStatusCode.OK
128+
)
124129

125130
// make initial request to local mock server
126-
val baseUrl = server.url("/").toAndroidUri()
131+
val baseUrl = MockEngineWrapper.uri()
127132
var ical: String? = null
128133
val redirects = LinkedList<Uri>()
129-
val fetcher = object: CalendarFetcher(appContext, baseUrl) {
130-
override suspend fun onRedirect(httpCode: Int, target: Uri) {
134+
val fetcher = object: CalendarFetcher(appContext, baseUrl, client) {
135+
override suspend fun onRedirect(httpCode: HttpStatusCode, target: Uri) {
131136
redirects += target
132137
super.onRedirect(httpCode, target)
133138
}
134-
override suspend fun onSuccess(data: InputStream, contentType: MediaType?, eTag: String?, lastModified: Long?, displayName: String?) {
139+
override suspend fun onSuccess(data: InputStream, contentType: ContentType?, eTag: String?, lastModified: Long?, displayName: String?) {
135140
ical = data.bufferedReader().use { it.readText() }
136141
}
137142
}
@@ -157,12 +162,13 @@ class CalendarFetcherTest {
157162

158163
@Test
159164
fun testFetchNetwork_onRedirectWithoutLocation() {
160-
server.enqueue(MockResponse()
161-
.setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP))
165+
val client = HttpClient(appContext, engine)
166+
167+
MockEngineWrapper.enqueue(status = HttpStatusCode.TemporaryRedirect)
162168

163169
var e: Exception? = null
164170
runBlocking {
165-
object : CalendarFetcher(appContext, server.url("/").toAndroidUri()) {
171+
object : CalendarFetcher(appContext, MockEngineWrapper.uri(), client) {
166172
override suspend fun onError(error: Exception) {
167173
e = error
168174
}
@@ -174,12 +180,12 @@ class CalendarFetcherTest {
174180

175181
@Test
176182
fun testFetchNetwork_onNotModified() {
177-
server.enqueue(MockResponse()
178-
.setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED))
183+
val client = HttpClient(appContext, engine)
184+
MockEngineWrapper.enqueue(status = HttpStatusCode.NotModified)
179185

180186
var notModified = false
181187
runBlocking {
182-
object : CalendarFetcher(appContext, server.url("/").toAndroidUri()) {
188+
object : CalendarFetcher(appContext, MockEngineWrapper.uri(), client) {
183189
override suspend fun onNotModified() {
184190
notModified = true
185191
}
@@ -191,12 +197,12 @@ class CalendarFetcherTest {
191197

192198
@Test
193199
fun testFetchNetwork_onError() {
194-
server.enqueue(MockResponse()
195-
.setResponseCode(HttpURLConnection.HTTP_NOT_FOUND))
200+
val client = HttpClient(appContext, engine)
201+
MockEngineWrapper.enqueue(status = HttpStatusCode.NotFound)
196202

197203
var e: Exception? = null
198204
runBlocking {
199-
object : CalendarFetcher(appContext, server.url("/").toAndroidUri()) {
205+
object : CalendarFetcher(appContext, MockEngineWrapper.uri(), client) {
200206
override suspend fun onError(error: Exception) {
201207
e = error
202208
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package at.bitfire.icsdroid
2+
3+
import android.content.Context
4+
import at.bitfire.icsdroid.HttpUtils.toUri
5+
import io.ktor.client.engine.mock.MockEngine
6+
import io.ktor.client.engine.mock.respond
7+
import io.ktor.http.Headers
8+
import io.ktor.http.HttpStatusCode
9+
import io.ktor.http.URLBuilder
10+
import io.ktor.http.appendPathSegments
11+
import io.ktor.http.headersOf
12+
import io.ktor.http.toURI
13+
import java.util.concurrent.locks.ReentrantLock
14+
import kotlin.concurrent.withLock
15+
16+
object MockEngineWrapper {
17+
class Response(val content: String, val status: HttpStatusCode, val headers: Headers)
18+
19+
private val lock = ReentrantLock()
20+
21+
private val queue = mutableListOf<Response>()
22+
23+
val engine = MockEngine {
24+
val response = lock.withLock { queue.removeFirst() }
25+
respond(response.content, response.status, response.headers)
26+
}
27+
28+
fun clear() {
29+
queue.clear()
30+
}
31+
32+
fun enqueue(response: Response) {
33+
lock.withLock {
34+
queue.add(response)
35+
}
36+
}
37+
38+
fun enqueue(
39+
content: String = "",
40+
status: HttpStatusCode = HttpStatusCode.OK,
41+
headers: Headers = headersOf()
42+
) = enqueue(Response(content, status, headers))
43+
44+
fun uri(vararg segments: String) = URLBuilder("http://localhost")
45+
.appendPathSegments(*segments)
46+
.build()
47+
.toURI()
48+
.toUri()
49+
50+
fun httpClient(context: Context) = HttpClient(context, engine)
51+
}

app/src/androidTest/java/at/bitfire/icsdroid/model/ValidationModelTest.kt

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,12 @@ import android.app.Application
88
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
99
import androidx.test.platform.app.InstrumentationRegistry
1010
import at.bitfire.ical4android.Css3Color
11-
import at.bitfire.icsdroid.HttpUtils.toAndroidUri
11+
import at.bitfire.icsdroid.MockEngineWrapper
1212
import at.bitfire.icsdroid.ui.ResourceInfo
1313
import kotlinx.coroutines.runBlocking
14-
import okhttp3.mockwebserver.MockResponse
15-
import okhttp3.mockwebserver.MockWebServer
16-
import org.junit.AfterClass
1714
import org.junit.Assert.assertEquals
1815
import org.junit.Assert.assertNull
19-
import org.junit.BeforeClass
16+
import org.junit.Before
2017
import org.junit.Rule
2118
import org.junit.Test
2219

@@ -26,26 +23,15 @@ class ValidationModelTest {
2623

2724
val app = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
2825

29-
lateinit var server: MockWebServer
30-
31-
@BeforeClass
32-
@JvmStatic
33-
fun setUp() {
34-
server = MockWebServer()
35-
server.start()
36-
}
37-
38-
@AfterClass
39-
@JvmStatic
40-
fun tearDown() {
41-
server.shutdown()
42-
}
43-
4426
}
4527

4628
@get:Rule
4729
val instantTaskExecutor = InstantTaskExecutorRule()
4830

31+
@Before
32+
fun setUp() {
33+
MockEngineWrapper.clear()
34+
}
4935

5036
@Test
5137
fun testModelInitialize_CalendarProperties_None() {
@@ -96,12 +82,12 @@ class ValidationModelTest {
9682
}
9783

9884
private fun validate(iCal: String): ResourceInfo {
99-
server.enqueue(MockResponse().setBody(iCal))
85+
MockEngineWrapper.enqueue(content = iCal)
10086

10187
val model = ValidationModel(app)
10288
runBlocking {
10389
// Wait until the validation completed
104-
model.validate(server.url("/").toAndroidUri(), null, null).join()
90+
model.validate(MockEngineWrapper.uri(), null, null).join()
10591
}
10692

10793
return model.uiState.result!!

0 commit comments

Comments
 (0)