Skip to content

Commit d3072ab

Browse files
Merge branch 'main' into tts-coverage
2 parents 9da7db8 + 9718069 commit d3072ab

File tree

5 files changed

+847
-105
lines changed

5 files changed

+847
-105
lines changed

app/src/androidTest/java/org/kiwix/kiwixmobile/NetworkTest.kt

Lines changed: 133 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -17,136 +17,164 @@
1717
*/
1818
package org.kiwix.kiwixmobile
1919

20-
import android.Manifest
21-
import androidx.test.core.app.ActivityScenario
22-
import androidx.test.espresso.Espresso
23-
import androidx.test.espresso.IdlingPolicies
24-
import androidx.test.espresso.IdlingRegistry
25-
import androidx.test.espresso.action.ViewActions
26-
import androidx.test.espresso.matcher.ViewMatchers
2720
import androidx.test.ext.junit.runners.AndroidJUnit4
28-
import androidx.test.platform.app.InstrumentationRegistry
29-
import androidx.test.rule.GrantPermissionRule
30-
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
31-
import com.adevinta.android.barista.interaction.BaristaMenuClickInteractions.clickMenu
32-
import com.adevinta.android.barista.interaction.BaristaSleepInteractions
21+
import androidx.test.filters.SmallTest
22+
import kotlinx.coroutines.runBlocking
23+
import kotlinx.coroutines.test.runTest
24+
import kotlinx.coroutines.withTimeout
25+
import okhttp3.OkHttpClient
26+
import okhttp3.logging.HttpLoggingInterceptor
27+
import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC
28+
import okhttp3.mockwebserver.MockResponse
29+
import okhttp3.mockwebserver.MockWebServer
30+
import okhttp3.mockwebserver.SocketPolicy
3331
import org.junit.After
32+
import org.junit.Assert.assertEquals
33+
import org.junit.Assert.assertFalse
34+
import org.junit.Assert.assertNotNull
35+
import org.junit.Assert.assertNull
36+
import org.junit.Assert.assertTrue
3437
import org.junit.Before
35-
import org.junit.BeforeClass
36-
import org.junit.Ignore
3738
import org.junit.Rule
3839
import org.junit.Test
3940
import org.junit.runner.RunWith
40-
import org.kiwix.kiwixmobile.core.CoreApp.Companion.coreComponent
41-
import org.kiwix.kiwixmobile.core.R.string
42-
import org.kiwix.kiwixmobile.core.di.components.DaggerTestComponent
41+
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
4342
import org.kiwix.kiwixmobile.core.utils.files.Log
44-
import org.kiwix.kiwixmobile.main.KiwixMainActivity
45-
import org.kiwix.kiwixmobile.testutils.TestUtils
46-
import org.kiwix.kiwixmobile.utils.KiwixIdlingResource.Companion.getInstance
47-
import java.util.concurrent.TimeUnit
43+
import org.kiwix.kiwixmobile.testutils.RetryRule
44+
import org.kiwix.sharedFunctions.TEST_PORT
45+
import java.net.InetAddress
46+
import java.util.concurrent.TimeUnit.SECONDS
4847

4948
/**
5049
* Created by mhutti1 on 14/04/17.
5150
*/
51+
@SmallTest
5252
@RunWith(AndroidJUnit4::class)
5353
class NetworkTest {
54-
// @Inject
55-
// MockWebServer mockWebServer
54+
@Rule
55+
@JvmField
56+
val retryRule = RetryRule()
57+
58+
private lateinit var mockWebServer: MockWebServer
59+
private lateinit var kiwixService: KiwixService
5660

57-
private val permissions =
58-
arrayOf(
59-
Manifest.permission.READ_EXTERNAL_STORAGE,
60-
Manifest.permission.WRITE_EXTERNAL_STORAGE
61+
@Before
62+
fun setUp() {
63+
mockWebServer = MockWebServer()
64+
mockWebServer.start(InetAddress.getByName("127.0.0.1"), TEST_PORT)
65+
kiwixService = KiwixService.ServiceCreator.newHackListService(
66+
OkHttpClient().newBuilder()
67+
.connectTimeout(TEST_TIMEOUT, SECONDS)
68+
.readTimeout(TEST_TIMEOUT, SECONDS)
69+
.callTimeout(TEST_TIMEOUT, SECONDS)
70+
.addNetworkInterceptor(HttpLoggingInterceptor().apply { level = BASIC })
71+
.build(),
72+
mockWebServer.url("/").toString()
6173
)
74+
Log.d(TAG, "MockWebServer started on port $TEST_PORT")
75+
}
6276

63-
@Rule
64-
@JvmField
65-
var permissionRules: GrantPermissionRule =
66-
GrantPermissionRule.grant(*permissions)
67-
68-
@Before fun setUp() {
69-
val component =
70-
DaggerTestComponent.builder().context(
71-
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
72-
).build()
73-
coreComponent = component
74-
component.inject(this)
75-
val library = NetworkTest::class.java.classLoader.getResourceAsStream("library.xml")
76-
val metalinks = NetworkTest::class.java.classLoader.getResourceAsStream("test.zim.meta4")
77-
val testzim = NetworkTest::class.java.classLoader.getResourceAsStream("testzim.zim")
78-
// try {
79-
// byte[] libraryBytes = IOUtils.toByteArray(library);
80-
// mockWebServer.enqueue(new MockResponse().setBody(new String(libraryBytes)));
81-
// byte[] metalinkBytes = IOUtils.toByteArray(metalinks);
82-
// mockWebServer.enqueue(new MockResponse().setBody(new String(metalinkBytes)));
83-
// mockWebServer.enqueue(new MockResponse().setHeader("Content-Length", 357269));
84-
// Buffer buffer = new Buffer();
85-
// buffer.write(IOUtils.toByteArray(testzim));
86-
// buffer.close();
87-
// mockWebServer.enqueue(new MockResponse().setBody(buffer));
88-
// } catch (IOException e) {
89-
// e.printStackTrace();
90-
// }
77+
@After
78+
fun tearDown() {
79+
mockWebServer.shutdown()
80+
Log.d(TAG, "MockWebServer shut down")
9181
}
9282

83+
private fun getResourceAsString(name: String): String =
84+
javaClass.classLoader!!.getResourceAsStream(name)!!.bufferedReader().readText()
85+
9386
@Test
94-
@Ignore("Broken in 2.5") // TODO Fix in 3.0
95-
fun networkTest() {
96-
ActivityScenario.launch(KiwixMainActivity::class.java)
97-
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS.toLong())
98-
clickMenu(TestUtils.getResourceString(string.library))
99-
TestUtils.allowStoragePermissionsIfNeeded()
100-
// Espresso.onData(TestUtils.withContent("wikipedia_ab_all_2017-03"))
101-
// .inAdapterView(ViewMatchers.withId(R.id.libraryList))
102-
// .perform(ViewActions.click())
103-
try {
104-
Espresso.onView(ViewMatchers.withId(android.R.id.button1)).perform(ViewActions.click())
105-
} catch (e: RuntimeException) {
106-
Log.w(NETWORK_TEST_TAG, "failed to perform click action on the view : ${e.localizedMessage} ")
107-
}
108-
clickOn(string.local_zims)
109-
try {
110-
// Espresso.onData(CoreMatchers.allOf(ViewMatchers.withId(R.id.zim_swiperefresh)))
111-
// refresh(R.id.zim_swiperefresh)
112-
Thread.sleep(500)
113-
} catch (e: InterruptedException) {
114-
e.printStackTrace()
115-
}
87+
fun testNetworkSuccess() = runTest {
88+
val libraryXml = getResourceAsString("library.xml")
89+
mockWebServer.enqueue(
90+
MockResponse()
91+
.setResponseCode(200)
92+
.setBody(libraryXml)
93+
)
94+
95+
val response = kiwixService.getLibraryPage(mockWebServer.url("/v2/entries").toString())
96+
val recordedRequest = mockWebServer.takeRequest()
97+
Log.d(TAG, "testNetworkSuccess: code=${response.code()} bodyLength=${response.body()?.length}")
98+
Log.d(TAG, "testNetworkSuccess: method=${recordedRequest.method} path=${recordedRequest.path}")
99+
Log.d(
100+
TAG,
101+
"testNetworkSuccess: code=${response.code()} bodyLength=${response.body()?.length} bodyPreview=${
102+
response.body()?.take(100)
103+
}"
104+
)
116105

117-
// Commented out the following which assumes only 1 match - not always safe to assume as there may
118-
// already be a similar file on the device.
119-
// onData(withContent("wikipedia_ab_all_2017-03")).inAdapterView(withId(R.id.zimfilelist)).perform(click());
120-
121-
// Find matching zim files on the device
122-
// try {
123-
// val dataInteraction =
124-
// Espresso.onData(TestUtils.withContent("wikipedia_ab_all_2017-03"))
125-
// .inAdapterView(ViewMatchers.withId(R.id.zimfilelist))
126-
// // TODO how can we get a count of the items matching the dataInteraction?
127-
// dataInteraction.atPosition(0).perform(ViewActions.click())
128-
// clickMenu(string.library)
129-
// val dataInteraction1 =
130-
// Espresso.onData(TestUtils.withContent("wikipedia_ab_all_2017-03"))
131-
// .inAdapterView(ViewMatchers.withId(R.id.zimfilelist))
132-
// dataInteraction1.atPosition(0).perform(ViewActions.longClick()) // to delete the zim file
133-
// BaristaDialogInteractions.clickDialogPositiveButton()
134-
// } catch (e: Exception) {
135-
// Log.w(NETWORK_TEST_TAG, "failed to interact with local ZIM file: " + e.localizedMessage)
136-
// }
106+
assertEquals("GET", recordedRequest.method)
107+
assertTrue(recordedRequest.path!!.startsWith("/v2/entries"))
108+
assertTrue(response.isSuccessful)
109+
assertEquals(200, response.code())
110+
assertEquals(libraryXml, response.body())
137111
}
138112

139-
@After fun finish() {
140-
IdlingRegistry.getInstance().unregister(getInstance())
113+
@Test
114+
fun testNetworkError() = runTest {
115+
mockWebServer.enqueue(MockResponse().setResponseCode(500))
116+
117+
val response = kiwixService.getLibraryPage(mockWebServer.url("/v2/entries").toString())
118+
Log.d(TAG, "testNetworkError: code=${response.code()} body=${response.body()}")
119+
120+
assertFalse(response.isSuccessful)
121+
assertEquals(500, response.code())
122+
assertNull(response.body())
123+
assertNotNull(response.errorBody())
141124
}
142125

143-
companion object {
144-
private const val NETWORK_TEST_TAG = "KiwixNetworkTest"
126+
@Test
127+
fun testNetworkNotFound() = runTest {
128+
mockWebServer.enqueue(MockResponse().setResponseCode(404))
129+
130+
val response = kiwixService.getLibraryPage(mockWebServer.url("/v2/entries").toString())
131+
Log.d(TAG, "testNetworkNotFound: code=${response.code()} body=${response.body()}")
132+
133+
assertFalse(response.isSuccessful)
134+
assertEquals(404, response.code())
135+
assertNull(response.body())
136+
assertNotNull(response.errorBody())
137+
}
138+
139+
@Test
140+
fun testNetworkEmptyResponse() = runTest {
141+
mockWebServer.enqueue(
142+
MockResponse()
143+
.setResponseCode(200)
144+
.setBody("")
145+
)
146+
147+
val response = kiwixService.getLibraryPage(mockWebServer.url("/v2/entries").toString())
148+
Log.d(TAG, "testNetworkEmptyResponse: code=${response.code()} body='${response.body()}'")
149+
150+
assertTrue(response.isSuccessful)
151+
assertEquals(200, response.code())
152+
assertEquals("", response.body())
153+
}
145154

146-
@BeforeClass fun beforeClass() {
147-
IdlingPolicies.setMasterPolicyTimeout(180, TimeUnit.SECONDS)
148-
IdlingPolicies.setIdlingResourceTimeout(180, TimeUnit.SECONDS)
149-
IdlingRegistry.getInstance().register(getInstance())
155+
@Test
156+
fun testNetworkTimeout() {
157+
mockWebServer.enqueue(
158+
MockResponse().apply { socketPolicy = SocketPolicy.NO_RESPONSE }
159+
)
160+
161+
var exceptionThrown = false
162+
try {
163+
runBlocking {
164+
withTimeout((TEST_TIMEOUT + 2L) * 1000L) {
165+
kiwixService.getLibraryPage(mockWebServer.url("/v2/entries").toString())
166+
}
167+
}
168+
} catch (e: Exception) {
169+
Log.e(TAG, "testNetworkTimeout: caught expected ${e::class.java.simpleName}: ${e.message}")
170+
exceptionThrown = true
150171
}
172+
173+
assertTrue("Expected a timeout exception to be thrown", exceptionThrown)
174+
}
175+
176+
companion object {
177+
private const val TAG = "NetworkTest"
178+
private const val TEST_TIMEOUT = 5L
151179
}
152180
}

app/src/debug/AndroidManifest.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
<application
5+
android:networkSecurityConfig="@xml/network_security_config"
6+
tools:ignore="MissingApplicationIcon" />
7+
</manifest>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="utf-8"?><!--
2+
~ Kiwix Android
3+
~ Copyright (c) 2026 Kiwix <android.kiwix.org>
4+
~ This program is free software: you can redistribute it and/or modify
5+
~ it under the terms of the GNU General Public License as published by
6+
~ the Free Software Foundation, either version 3 of the License, or
7+
~ (at your option) any later version.
8+
~
9+
~ This program is distributed in the hope that it will be useful,
10+
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
~ GNU General Public License for more details.
13+
~
14+
~ You should have received a copy of the GNU General Public License
15+
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
~
17+
-->
18+
<network-security-config>
19+
<domain-config cleartextTrafficPermitted="true">
20+
<domain includeSubdomains="true">localhost</domain>
21+
<domain includeSubdomains="true">127.0.0.1</domain>
22+
</domain-config>
23+
</network-security-config>

buildSrc/src/main/kotlin/plugin/AllProjectConfigurer.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ class AllProjectConfigurer {
255255
// Compose UI test implementation
256256
androidTestImplementation(Libs.COMPOSE_UI_TEST_JUNIT)
257257
androidTestImplementation(Libs.COMPOSE_UI_TEST_JUNIT_ACCESSIBILITY)
258+
testImplementation(Libs.COMPOSE_UI_TEST_JUNIT)
258259
debugImplementation(Libs.COMPOSE_UI_MANIFEST)
259260
debugImplementation(Libs.COMPOSE_TOOLING)
260261

0 commit comments

Comments
 (0)