Initial QSfera import
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/* qsfera Android Library is available under MIT license
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package eu.qsfera.android.lib
|
||||
|
||||
import eu.qsfera.android.lib.common.http.CookieJarImpl
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class CookieJarImplTest {
|
||||
|
||||
private val oldCookies = listOf(COOKIE_A, COOKIE_B_OLD)
|
||||
private val newCookies = listOf(COOKIE_B_NEW)
|
||||
private val updatedCookies = listOf(COOKIE_A, COOKIE_B_NEW)
|
||||
private val cookieStore = hashMapOf(SOME_HOST to oldCookies)
|
||||
|
||||
private val cookieJarImpl = CookieJarImpl(cookieStore)
|
||||
|
||||
@Test
|
||||
fun `contains cookie with name - ok - true`() {
|
||||
assertTrue(cookieJarImpl.containsCookieWithName(oldCookies, COOKIE_B_OLD.name))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `contains cookie with name - ok - false`() {
|
||||
assertFalse(cookieJarImpl.containsCookieWithName(newCookies, COOKIE_A.name))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get updated cookies - ok`() {
|
||||
val generatedUpdatedCookies = cookieJarImpl.getUpdatedCookies(oldCookies, newCookies)
|
||||
assertEquals(2, generatedUpdatedCookies.size)
|
||||
assertEquals(updatedCookies[0], generatedUpdatedCookies[1])
|
||||
assertEquals(updatedCookies[1], generatedUpdatedCookies[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `store cookie via saveFromResponse - ok`() {
|
||||
cookieJarImpl.saveFromResponse(SOME_URL, newCookies)
|
||||
val generatedUpdatedCookies = cookieStore[SOME_HOST]
|
||||
assertEquals(2, generatedUpdatedCookies?.size)
|
||||
assertEquals(updatedCookies[0], generatedUpdatedCookies?.get(1))
|
||||
assertEquals(updatedCookies[1], generatedUpdatedCookies?.get(0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `load for request - ok`() {
|
||||
val cookies = cookieJarImpl.loadForRequest(SOME_URL)
|
||||
assertEquals(oldCookies[0], cookies[0])
|
||||
assertEquals(oldCookies[1], cookies[1])
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SOME_HOST = "some.host.com"
|
||||
val SOME_URL = "https://$SOME_HOST".toHttpUrl()
|
||||
val COOKIE_A = Cookie.parse(SOME_URL, "CookieA=CookieValueA")!!
|
||||
val COOKIE_B_OLD = Cookie.parse(SOME_URL, "CookieB=CookieOldValueB")!!
|
||||
val COOKIE_B_NEW = Cookie.parse(SOME_URL, "CookieB=CookieNewValueB")!!
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/* qsfera Android Library is available under MIT license
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package eu.qsfera.android.lib
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import eu.qsfera.android.lib.resources.status.GetRemoteStatusOperation
|
||||
import eu.qsfera.android.lib.resources.status.HttpScheme.HTTPS_PREFIX
|
||||
import eu.qsfera.android.lib.resources.status.HttpScheme.HTTP_PREFIX
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [Build.VERSION_CODES.O], manifest = Config.NONE)
|
||||
class GetRemoteStatusOperationTest {
|
||||
|
||||
@Test
|
||||
fun `uses http or https - ok - http`() {
|
||||
assertTrue(GetRemoteStatusOperation.usesHttpOrHttps(Uri.parse(HTTP_SOME_QSFERA)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `uses http or https - ok - https`() {
|
||||
assertTrue(GetRemoteStatusOperation.usesHttpOrHttps(Uri.parse(HTTPS_SOME_QSFERA)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `uses http or https - ok - no http or https`() {
|
||||
assertFalse(GetRemoteStatusOperation.usesHttpOrHttps(Uri.parse(SOME_QSFERA)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build full https url - ok - http`() {
|
||||
assertEquals(
|
||||
Uri.parse(HTTP_SOME_QSFERA),
|
||||
GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(HTTP_SOME_QSFERA))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build full https url - ok - https`() {
|
||||
assertEquals(
|
||||
Uri.parse(HTTPS_SOME_QSFERA),
|
||||
GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(HTTPS_SOME_QSFERA))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build full https url - ok - no prefix`() {
|
||||
assertEquals(
|
||||
Uri.parse(HTTPS_SOME_QSFERA),
|
||||
GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(SOME_QSFERA))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build full https url - ok - no https with subdir`() {
|
||||
assertEquals(
|
||||
Uri.parse(HTTPS_SOME_QSFERA_WITH_SUBDIR),
|
||||
GetRemoteStatusOperation.buildFullHttpsUrl(
|
||||
Uri.parse(HTTPS_SOME_QSFERA_WITH_SUBDIR)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build full https url - ok - no prefix with subdir`() {
|
||||
assertEquals(
|
||||
Uri.parse(HTTPS_SOME_QSFERA_WITH_SUBDIR),
|
||||
GetRemoteStatusOperation.buildFullHttpsUrl(
|
||||
Uri.parse(SOME_QSFERA_WITH_SUBDIR)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build full https url - ok - ip`() {
|
||||
assertEquals(Uri.parse(HTTPS_SOME_IP), GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(SOME_IP)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build full https url - ok - http ip`() {
|
||||
assertEquals(Uri.parse(HTTP_SOME_IP), GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(HTTP_SOME_IP)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build full https url - ok - ip with port`() {
|
||||
assertEquals(
|
||||
Uri.parse(HTTPS_SOME_IP_WITH_PORT),
|
||||
GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(SOME_IP_WITH_PORT))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build full https url - ok - ip with http and port`() {
|
||||
assertEquals(
|
||||
Uri.parse(HTTP_SOME_IP_WITH_PORT),
|
||||
GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(HTTP_SOME_IP_WITH_PORT))
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SOME_QSFERA = "some_qsfera.eu"
|
||||
const val HTTP_SOME_QSFERA = "$HTTP_PREFIX$SOME_QSFERA"
|
||||
const val HTTPS_SOME_QSFERA = "$HTTPS_PREFIX$SOME_QSFERA"
|
||||
|
||||
const val SOME_QSFERA_WITH_SUBDIR = "some_qsfera.eu/subdir"
|
||||
const val HTTP_SOME_QSFERA_WITH_SUBDIR = "$HTTP_PREFIX$SOME_QSFERA_WITH_SUBDIR"
|
||||
const val HTTPS_SOME_QSFERA_WITH_SUBDIR = "$HTTPS_PREFIX$SOME_QSFERA_WITH_SUBDIR"
|
||||
|
||||
const val SOME_IP = "184.123.185.12"
|
||||
const val HTTP_SOME_IP = "$HTTP_PREFIX$SOME_IP"
|
||||
const val HTTPS_SOME_IP = "$HTTPS_PREFIX$SOME_IP"
|
||||
|
||||
const val SOME_IP_WITH_PORT = "184.123.185.12:5678"
|
||||
const val HTTP_SOME_IP_WITH_PORT = "$HTTP_PREFIX$SOME_IP_WITH_PORT"
|
||||
const val HTTPS_SOME_IP_WITH_PORT = "$HTTPS_PREFIX$SOME_IP_WITH_PORT"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/* qsfera Android Library is available under MIT license
|
||||
* Copyright (C) 2023 ownCloud GmbH.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package eu.qsfera.android.lib
|
||||
|
||||
import android.os.Build
|
||||
import eu.qsfera.android.lib.resources.files.RemoteFile
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [Build.VERSION_CODES.O], manifest = Config.NONE)
|
||||
class RemoteFileTest {
|
||||
|
||||
@Test
|
||||
fun getRemotePathFromUrl_legacyWebDav() {
|
||||
val httpUrlToTest = "https://server.url/remote.php/dav/files/username/Documents/text.txt".toHttpUrl()
|
||||
val expectedRemotePath = "/Documents/text.txt"
|
||||
|
||||
val actualRemotePath = RemoteFile.Companion.getRemotePathFromUrl(httpUrlToTest, "username")
|
||||
assertEquals(expectedRemotePath, actualRemotePath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getRemotePathFromUrl_spacesWebDav() {
|
||||
val spaceWebDavUrl = "https://server.url/dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$05bca744-d89f-4e9c-a990-25a0d7f03fe9"
|
||||
|
||||
val httpUrlToTest =
|
||||
"https://server.url/dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$05bca744-d89f-4e9c-a990-25a0d7f03fe9/Documents/text.txt".toHttpUrl()
|
||||
val expectedRemotePath = "/Documents/text.txt"
|
||||
|
||||
val actualRemotePath = RemoteFile.Companion.getRemotePathFromUrl(httpUrlToTest, "username", spaceWebDavUrl)
|
||||
assertEquals(expectedRemotePath, actualRemotePath)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/* qsfera Android Library is available under MIT license
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.lib
|
||||
|
||||
import eu.qsfera.android.lib.resources.status.StatusRequester
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class StatusRequesterTest {
|
||||
private val requester = StatusRequester()
|
||||
|
||||
@Test
|
||||
fun `update location - ok - absolute path`() {
|
||||
val newLocation = requester.updateLocationWithRedirectPath(TEST_DOMAIN, "$TEST_DOMAIN$SUB_PATH")
|
||||
assertEquals("$TEST_DOMAIN$SUB_PATH", newLocation)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update location - ok - smaller absolute path`() {
|
||||
val newLocation = requester.updateLocationWithRedirectPath("$TEST_DOMAIN$SUB_PATH", TEST_DOMAIN)
|
||||
assertEquals(TEST_DOMAIN, newLocation)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update location - ok - relative path`() {
|
||||
val newLocation = requester.updateLocationWithRedirectPath(TEST_DOMAIN, SUB_PATH)
|
||||
assertEquals("$TEST_DOMAIN$SUB_PATH", newLocation)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update location - ok - replace relative path`() {
|
||||
val newLocation = requester.updateLocationWithRedirectPath("$TEST_DOMAIN/some/other/subdir", SUB_PATH)
|
||||
assertEquals("$TEST_DOMAIN$SUB_PATH", newLocation)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check redirect to unsecure connection - ok - redirect to http`() {
|
||||
assertTrue(
|
||||
requester.isRedirectedToNonSecureConnection(false, SECURE_DOMAIN, UNSECURE_DOMAIN
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check redirect to unsecure connection - ko - redirect to https from http`() {
|
||||
assertFalse(
|
||||
requester.isRedirectedToNonSecureConnection(false, UNSECURE_DOMAIN, SECURE_DOMAIN
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check redirect to unsecure connection - ko - from https to https`() {
|
||||
assertFalse(
|
||||
requester.isRedirectedToNonSecureConnection(false, SECURE_DOMAIN, SECURE_DOMAIN)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check redirect to unsecure connection - ok - from https to https with previous http`() {
|
||||
assertTrue(
|
||||
requester.isRedirectedToNonSecureConnection(true, SECURE_DOMAIN, SECURE_DOMAIN)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check redirect to unsecure connection - ok - from http to http`() {
|
||||
assertFalse(
|
||||
requester.isRedirectedToNonSecureConnection(false, UNSECURE_DOMAIN, UNSECURE_DOMAIN)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TEST_DOMAIN = "https://cloud.somewhere.com"
|
||||
const val SUB_PATH = "/subdir"
|
||||
|
||||
const val SECURE_DOMAIN = "https://cloud.somewhere.com"
|
||||
const val UNSECURE_DOMAIN = "http://somewhereelse.org"
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
package eu.qsfera.android.lib.common.http
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import eu.qsfera.android.lib.common.network.CertificateCombinedException
|
||||
import eu.qsfera.android.lib.common.network.NetworkUtils
|
||||
import eu.qsfera.android.lib.common.operations.RemoteOperationResult
|
||||
import okhttp3.Request
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import okhttp3.tls.HandshakeCertificates
|
||||
import okhttp3.tls.HeldCertificate
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import java.lang.reflect.Field
|
||||
import javax.net.ssl.SSLPeerUnverifiedException
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [Build.VERSION_CODES.O], manifest = Config.NONE)
|
||||
class HttpClientTlsTest {
|
||||
|
||||
private val context by lazy { ApplicationProvider.getApplicationContext<Context>() }
|
||||
private lateinit var server: MockWebServer
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
resetKnownServersStore()
|
||||
server = MockWebServer()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
runCatching { server.shutdown() }
|
||||
resetKnownServersStore()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accepts user-trusted certificate despite hostname mismatch`() {
|
||||
val wrongHostnameCertificate = HeldCertificate.Builder()
|
||||
.commonName(WRONG_HOSTNAME)
|
||||
.addSubjectAlternativeName(WRONG_HOSTNAME)
|
||||
.build()
|
||||
val serverCertificates = HandshakeCertificates.Builder()
|
||||
.heldCertificate(wrongHostnameCertificate)
|
||||
.build()
|
||||
|
||||
server.useHttps(serverCertificates.sslSocketFactory(), false)
|
||||
server.enqueue(MockResponse().setResponseCode(200).setBody("ok"))
|
||||
server.start()
|
||||
|
||||
NetworkUtils.addCertToKnownServersStore(wrongHostnameCertificate.certificate, context)
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(server.url("/"))
|
||||
.build()
|
||||
|
||||
TestHttpClient(context).okHttpClient.newCall(request).execute().use { response ->
|
||||
assertEquals(200, response.code)
|
||||
assertEquals("ok", response.body?.string())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejects certificate with hostname mismatch when not in known servers`() {
|
||||
val wrongHostnameCertificate = HeldCertificate.Builder()
|
||||
.commonName(WRONG_HOSTNAME)
|
||||
.addSubjectAlternativeName(WRONG_HOSTNAME)
|
||||
.build()
|
||||
val serverCertificates = HandshakeCertificates.Builder()
|
||||
.heldCertificate(wrongHostnameCertificate)
|
||||
.build()
|
||||
|
||||
server.useHttps(serverCertificates.sslSocketFactory(), false)
|
||||
server.enqueue(MockResponse().setResponseCode(200).setBody("ok"))
|
||||
server.start()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(server.url("/"))
|
||||
.build()
|
||||
|
||||
val thrown = assertThrows(Exception::class.java) {
|
||||
TestHttpClient(context).okHttpClient.newCall(request).execute().use { }
|
||||
}
|
||||
|
||||
assertNotNull(thrown)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `wraps hostname mismatch as certificate combined exception`() {
|
||||
val peerUnverifiedException = SSLPeerUnverifiedException("Hostname localhost not verified")
|
||||
|
||||
val result = RemoteOperationResult<Unit>(peerUnverifiedException)
|
||||
|
||||
assertEquals(
|
||||
RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED,
|
||||
result.code
|
||||
)
|
||||
assertTrue(result.exception is CertificateCombinedException)
|
||||
|
||||
val combinedException = result.exception as CertificateCombinedException
|
||||
assertSame(peerUnverifiedException, combinedException.sslPeerUnverifiedException)
|
||||
}
|
||||
|
||||
private fun resetKnownServersStore() {
|
||||
context.deleteFile(KNOWN_SERVERS_STORE_FILE)
|
||||
|
||||
val field: Field = NetworkUtils::class.java.getDeclaredField("mKnownServersStore")
|
||||
field.isAccessible = true
|
||||
field.set(null, null)
|
||||
}
|
||||
|
||||
private class TestHttpClient(context: Context) : HttpClient(context)
|
||||
|
||||
companion object {
|
||||
private const val WRONG_HOSTNAME = "wrong.example.test"
|
||||
private const val KNOWN_SERVERS_STORE_FILE = "knownServers.bks"
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/* qsfera Android Library is available under MIT license
|
||||
* Copyright (C) 2026 QSfera.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package eu.qsfera.android.lib.common.http.logging
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class LogInterceptorTest {
|
||||
|
||||
@Test
|
||||
fun durationStringFormatsSubSecondDuration() {
|
||||
assertEquals("duration(0h, 0min, 0s, 999ms)", formatDuration(999L))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun durationStringFormatsMinuteDuration() {
|
||||
assertEquals("duration(0h, 1min, 1s, 0ms)", formatDuration(61_000L))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun durationStringFormatsHourDuration() {
|
||||
assertEquals("duration(1h, 1min, 1s, 0ms)", formatDuration(3_661_000L))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun durationStringFormatsMixedDuration() {
|
||||
assertEquals("duration(2h, 3min, 1s, 234ms)", formatDuration(7_381_234L))
|
||||
}
|
||||
|
||||
private fun formatDuration(millis: Long): String {
|
||||
val method = LogInterceptor::class.java.getDeclaredMethod("getDurationString", Long::class.javaPrimitiveType)
|
||||
method.isAccessible = true
|
||||
return method.invoke(LogInterceptor(), millis) as String
|
||||
}
|
||||
}
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
package eu.qsfera.android.lib.resources.files.tus
|
||||
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountManager
|
||||
import android.net.Uri
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import eu.qsfera.android.lib.common.QSferaAccount
|
||||
import eu.qsfera.android.lib.common.QSferaClient
|
||||
import eu.qsfera.android.lib.common.accounts.AccountUtils
|
||||
import eu.qsfera.android.lib.common.authentication.QSferaCredentialsFactory
|
||||
import eu.qsfera.android.lib.common.operations.RemoteOperationResult
|
||||
import eu.qsfera.android.lib.resources.files.tus.CreateTusUploadRemoteOperation.Base64Encoder
|
||||
import okhttp3.mockwebserver.Dispatcher
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import okhttp3.mockwebserver.RecordedRequest
|
||||
import org.junit.After
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import java.io.File
|
||||
import java.util.Base64
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class TusIntegrationTest {
|
||||
|
||||
private lateinit var server: MockWebServer
|
||||
private val context by lazy { ApplicationProvider.getApplicationContext<android.content.Context>() }
|
||||
|
||||
private val accountType = "com.example"
|
||||
private val userId = "user-123"
|
||||
private val username = "user@example.com"
|
||||
private val token = "TEST_TOKEN"
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
server = MockWebServer()
|
||||
server.start()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
server.shutdown()
|
||||
}
|
||||
|
||||
private fun newClient(): QSferaClient {
|
||||
val base = server.url("/").toString().removeSuffix("/")
|
||||
|
||||
val am = AccountManager.get(context)
|
||||
val account = Account("$username@${Uri.parse(base).host}", accountType)
|
||||
am.addAccountExplicitly(account, null, null)
|
||||
am.setUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL, base)
|
||||
am.setUserData(account, AccountUtils.Constants.KEY_ID, userId)
|
||||
|
||||
val ocAccount = QSferaAccount(account, context)
|
||||
val client = QSferaClient(ocAccount.baseUri, /*connectionValidator*/ null, /*sync*/ true, /*singleSession*/ null, context)
|
||||
client.account = ocAccount
|
||||
client.credentials = QSferaCredentialsFactory.newBearerCredentials(username, token)
|
||||
return client
|
||||
}
|
||||
|
||||
@Test
|
||||
fun create_patch_head_delete_success() {
|
||||
val client = newClient()
|
||||
|
||||
val collectionPath = "/remote.php/dav/uploads/$userId"
|
||||
val locationPath = "$collectionPath/UPLD-123"
|
||||
val localFile = File.createTempFile("tus", ".bin").apply {
|
||||
writeBytes(byteArrayOf(1, 2, 3, 4, 5))
|
||||
}
|
||||
|
||||
// 1) POST Create -> 201 + Location
|
||||
server.enqueue(
|
||||
MockResponse()
|
||||
.setResponseCode(201)
|
||||
.addHeader("Tus-Resumable", "1.0.0")
|
||||
.addHeader("Location", locationPath)
|
||||
)
|
||||
|
||||
// 2) PATCH -> 204 + Upload-Offset
|
||||
server.enqueue(
|
||||
MockResponse()
|
||||
.setResponseCode(204)
|
||||
.addHeader("Upload-Offset", "5")
|
||||
)
|
||||
|
||||
// 3) HEAD -> 204 + Upload-Offset
|
||||
server.enqueue(
|
||||
MockResponse()
|
||||
.setResponseCode(204)
|
||||
.addHeader("Upload-Offset", "5")
|
||||
)
|
||||
|
||||
// 4) DELETE -> 204
|
||||
server.enqueue(
|
||||
MockResponse()
|
||||
.setResponseCode(204)
|
||||
)
|
||||
|
||||
// Create
|
||||
val create = CreateTusUploadRemoteOperation(
|
||||
file = localFile,
|
||||
remotePath = "/test.bin",
|
||||
mimetype = "application/octet-stream",
|
||||
metadata = mapOf("filename" to "test.bin"),
|
||||
useCreationWithUpload = false,
|
||||
firstChunkSize = null,
|
||||
tusUrl = null,
|
||||
collectionUrlOverride = server.url(collectionPath).toString(),
|
||||
base64Encoder = object : Base64Encoder {
|
||||
override fun encode(bytes: ByteArray): String =
|
||||
Base64.getEncoder().encodeToString(bytes)
|
||||
}
|
||||
)
|
||||
val createResult = create.execute(client)
|
||||
if (!createResult.isSuccess) {
|
||||
val msg = "DEBUG: Create operation failed. Code: ${createResult.code}, " +
|
||||
"HttpCode: ${createResult.httpCode}, Exception: ${createResult.exception}"
|
||||
throw RuntimeException(msg, createResult.exception)
|
||||
}
|
||||
assertTrue("Create operation failed", createResult.isSuccess)
|
||||
val creationResult = createResult.data
|
||||
assertNotNull(creationResult)
|
||||
val absoluteLocation = creationResult!!.uploadUrl
|
||||
val offset = creationResult.uploadOffset
|
||||
println("absoluteLocation: $absoluteLocation")
|
||||
println("locationPath: $locationPath")
|
||||
println("endsWith: ${absoluteLocation.endsWith(locationPath)}")
|
||||
assertTrue(absoluteLocation.endsWith(locationPath))
|
||||
assertEquals(0L, offset)
|
||||
|
||||
// Verify POST request headers
|
||||
val postReq = server.takeRequest()
|
||||
assertEquals("POST", postReq.method)
|
||||
assertEquals("Bearer $token", postReq.getHeader("Authorization"))
|
||||
assertEquals("1.0.0", postReq.getHeader("Tus-Resumable"))
|
||||
assertEquals("5", postReq.getHeader("Upload-Length"))
|
||||
assertEquals(collectionPath, postReq.path)
|
||||
|
||||
// Patch
|
||||
val patch = PatchTusUploadChunkRemoteOperation(
|
||||
localPath = localFile.absolutePath,
|
||||
uploadUrl = absoluteLocation,
|
||||
offset = 0,
|
||||
chunkSize = 5
|
||||
)
|
||||
val patchResult = patch.execute(client)
|
||||
assertTrue(patchResult.isSuccess)
|
||||
assertEquals(5L, patchResult.data)
|
||||
|
||||
// Verify PATCH request
|
||||
val patchReq = server.takeRequest()
|
||||
assertEquals("PATCH", patchReq.method)
|
||||
assertEquals("Bearer $token", patchReq.getHeader("Authorization"))
|
||||
assertEquals("1.0.0", patchReq.getHeader("Tus-Resumable"))
|
||||
assertEquals("0", patchReq.getHeader("Upload-Offset"))
|
||||
assertEquals("application/offset+octet-stream", patchReq.getHeader("Content-Type"))
|
||||
assertEquals(Uri.parse(absoluteLocation).encodedPath, patchReq.path)
|
||||
|
||||
// Head
|
||||
val head = GetTusUploadOffsetRemoteOperation(absoluteLocation)
|
||||
val headResult = head.execute(client)
|
||||
assertTrue(headResult.isSuccess)
|
||||
assertEquals(5L, headResult.data)
|
||||
|
||||
val headReq = server.takeRequest()
|
||||
assertEquals("HEAD", headReq.method)
|
||||
assertEquals("Bearer $token", headReq.getHeader("Authorization"))
|
||||
assertEquals("1.0.0", headReq.getHeader("Tus-Resumable"))
|
||||
|
||||
// Delete
|
||||
val del = DeleteTusUploadRemoteOperation(absoluteLocation)
|
||||
val delResult = del.execute(client)
|
||||
assertTrue(delResult.isSuccess)
|
||||
|
||||
val delReq = server.takeRequest()
|
||||
assertEquals("DELETE", delReq.method)
|
||||
assertEquals("Bearer $token", delReq.getHeader("Authorization"))
|
||||
assertEquals("1.0.0", delReq.getHeader("Tus-Resumable"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun creation_with_upload_returns_offset() {
|
||||
val client = newClient()
|
||||
val collectionPath = "/remote.php/dav/uploads/$userId"
|
||||
val locationPath = "$collectionPath/UPLD-WITH-DATA"
|
||||
val localFile = File.createTempFile("tus", ".bin").apply {
|
||||
writeBytes(ByteArray(100) { it.toByte() })
|
||||
}
|
||||
val firstChunkSize = 50L
|
||||
|
||||
// POST Create with Upload -> 201 + Location + Upload-Offset
|
||||
server.dispatcher = object : Dispatcher() {
|
||||
override fun dispatch(request: RecordedRequest): MockResponse {
|
||||
if (request.path == collectionPath) {
|
||||
// Verify body content
|
||||
val bodySize = request.bodySize
|
||||
assertEquals(firstChunkSize, bodySize)
|
||||
|
||||
// Verify body bytes (first 50 bytes of the file)
|
||||
val expectedBytes = ByteArray(firstChunkSize.toInt()) { it.toByte() }
|
||||
val actualBytes = request.body.readByteArray()
|
||||
assertArrayEquals(expectedBytes, actualBytes)
|
||||
|
||||
return MockResponse()
|
||||
.setResponseCode(201)
|
||||
.addHeader("Tus-Resumable", "1.0.0")
|
||||
.addHeader("Location", locationPath)
|
||||
.addHeader("Upload-Offset", bodySize.toString())
|
||||
}
|
||||
return MockResponse().setResponseCode(404)
|
||||
}
|
||||
}
|
||||
|
||||
val create = CreateTusUploadRemoteOperation(
|
||||
file = localFile,
|
||||
remotePath = "/test-with-data.bin",
|
||||
mimetype = "application/octet-stream",
|
||||
metadata = mapOf("filename" to "test-with-data.bin"),
|
||||
useCreationWithUpload = true,
|
||||
firstChunkSize = firstChunkSize,
|
||||
tusUrl = null,
|
||||
collectionUrlOverride = server.url(collectionPath).toString(),
|
||||
base64Encoder = object : Base64Encoder {
|
||||
override fun encode(bytes: ByteArray): String =
|
||||
Base64.getEncoder().encodeToString(bytes)
|
||||
}
|
||||
)
|
||||
|
||||
val createResult = create.execute(client)
|
||||
assertTrue("Create operation failed", createResult.isSuccess)
|
||||
|
||||
val creationResult = createResult.data
|
||||
assertNotNull(creationResult)
|
||||
assertEquals(firstChunkSize, creationResult!!.uploadOffset)
|
||||
assertTrue(creationResult.uploadUrl.endsWith(locationPath))
|
||||
|
||||
// Verify POST request
|
||||
val postReq = server.takeRequest()
|
||||
assertEquals("POST", postReq.method)
|
||||
assertEquals("Bearer $token", postReq.getHeader("Authorization"))
|
||||
assertEquals("1.0.0", postReq.getHeader("Tus-Resumable"))
|
||||
// creation-with-upload sends Content-Type and Content-Length for the chunk
|
||||
assertEquals("application/offset+octet-stream", postReq.getHeader("Content-Type"))
|
||||
assertEquals(firstChunkSize.toString(), postReq.getHeader("Content-Length"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun patch_wrong_offset_returns_conflict() {
|
||||
val client = newClient()
|
||||
val locationPath = "/remote.php/dav/uploads/$userId/UPLD-err"
|
||||
|
||||
// No need to POST; directly simulate an existing upload URL
|
||||
// Server responds 412 to PATCH
|
||||
server.enqueue(MockResponse().setResponseCode(412))
|
||||
|
||||
val tmp = File.createTempFile("tus", ".bin")
|
||||
tmp.writeBytes(ByteArray(10) { 1 })
|
||||
|
||||
val patch = PatchTusUploadChunkRemoteOperation(
|
||||
localPath = tmp.absolutePath,
|
||||
uploadUrl = server.url(locationPath).toString(),
|
||||
offset = 0,
|
||||
chunkSize = 10
|
||||
)
|
||||
val res = patch.execute(client)
|
||||
assertFalse(res.isSuccess)
|
||||
assertEquals(RemoteOperationResult.ResultCode.CONFLICT, res.code)
|
||||
|
||||
val req = server.takeRequest()
|
||||
assertEquals("PATCH", req.method)
|
||||
assertEquals("Bearer $token", req.getHeader("Authorization"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cancel_before_start_returns_cancelled() {
|
||||
val client = newClient()
|
||||
val locationPath = "/remote.php/dav/uploads/$userId/UPLD-cancel"
|
||||
|
||||
// No requests expected because we cancel before run
|
||||
val tmp = File.createTempFile("tus", ".bin")
|
||||
tmp.writeBytes(ByteArray(1024 * 64) { 1 })
|
||||
|
||||
val op = PatchTusUploadChunkRemoteOperation(
|
||||
localPath = tmp.absolutePath,
|
||||
uploadUrl = server.url(locationPath).toString(),
|
||||
offset = 0,
|
||||
chunkSize = 1024 * 64L
|
||||
)
|
||||
op.cancel()
|
||||
val result = op.execute(client)
|
||||
assertTrue(result.isCancelled)
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/* qsfera Android Library is available under MIT license
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.lib.resources.shares.responses
|
||||
|
||||
import eu.qsfera.android.lib.resources.CommonOcsResponse
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class ShareeResponseTest {
|
||||
|
||||
lateinit var adapter: JsonAdapter<CommonOcsResponse<ShareeOcsResponse>>
|
||||
|
||||
private fun loadResponses(fileName: String) =
|
||||
adapter.fromJson(File(fileName).readText())
|
||||
|
||||
@Before
|
||||
fun prepare() {
|
||||
val moshi = Moshi.Builder().build()
|
||||
val type: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareeOcsResponse::class.java)
|
||||
adapter = moshi.adapter(type)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check structure - ok - contains meta`() {
|
||||
val response = loadResponses(EXAMPLE_RESPONSE_JSON)!!
|
||||
assertEquals("OK", response.ocs.meta.message!!)
|
||||
assertEquals(200, response.ocs.meta.statusCode)
|
||||
assertEquals("ok", response.ocs.meta.status)
|
||||
assertTrue(response.ocs.meta.itemsPerPage?.isEmpty()!!)
|
||||
assertTrue(response.ocs.meta.totalItems?.isEmpty()!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `example response - ok - correct sturcture`() {
|
||||
val response = loadResponses(EXAMPLE_RESPONSE_JSON)!!
|
||||
assertEquals(2, response.ocs.data?.groups?.size)
|
||||
assertEquals(0, response.ocs.data?.remotes?.size)
|
||||
assertEquals(2, response.ocs.data?.users?.size)
|
||||
assertEquals(0, response.ocs.data?.exact?.groups?.size)
|
||||
assertEquals(0, response.ocs.data?.exact?.remotes?.size)
|
||||
assertEquals(1, response.ocs.data?.exact?.users?.size)
|
||||
assertEquals("user1@user1.com", response.ocs.data?.users?.get(0)?.value?.additionalInfo)
|
||||
assertNull(response.ocs.data?.users?.get(1)?.value?.additionalInfo)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check empty response - ok - parsing ok`() {
|
||||
val response = loadResponses(EMPTY_RESPONSE_JSON)!!
|
||||
assertTrue(response.ocs.data?.exact?.groups?.isEmpty()!!)
|
||||
assertTrue(response.ocs.data?.exact?.remotes?.isEmpty()!!)
|
||||
assertTrue(response.ocs.data?.exact?.users?.isEmpty()!!)
|
||||
assertTrue(response.ocs.data?.groups?.isEmpty()!!)
|
||||
assertTrue(response.ocs.data?.remotes?.isEmpty()!!)
|
||||
assertTrue(response.ocs.data?.users?.isEmpty()!!)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val RESOURCES_PATH =
|
||||
"src/test/responses/eu.qsfera.android.lib.resources.sharees.responses"
|
||||
const val EXAMPLE_RESPONSE_JSON = "$RESOURCES_PATH/example_sharee_response.json"
|
||||
const val EMPTY_RESPONSE_JSON = "$RESOURCES_PATH/empty_sharee_response.json"
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package eu.qsfera.android.lib.resources.webfinger.responses
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonDataException
|
||||
import com.squareup.moshi.Moshi
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
class WebFingerResponseTest {
|
||||
lateinit var adapter: JsonAdapter<WebFingerResponse>
|
||||
|
||||
private fun loadResponses(fileName: String) = adapter.fromJson(File(fileName).readText())
|
||||
|
||||
@Before
|
||||
fun prepare() {
|
||||
val moshi = Moshi.Builder().build()
|
||||
adapter = moshi.adapter(WebFingerResponse::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check rel in too much information - ok`() {
|
||||
val response = loadResponses(TOO_MUCH_INFORMATION_JSON)!!
|
||||
Assert.assertEquals("https://gast.somedomain.de", response.links!![0].href)
|
||||
Assert.assertEquals("http://webfinger.qsfera/rel/server-instance", response.links!![0].rel)
|
||||
}
|
||||
|
||||
@Test(expected = JsonDataException::class)
|
||||
fun `check key value pairs - ko - no href key`() {
|
||||
val response = loadResponses(BROKEN_JSON)!!
|
||||
Assert.assertEquals("https://gast.somedomain.de", response.links!![0].href)
|
||||
}
|
||||
|
||||
@Test(expected = JsonDataException::class)
|
||||
fun `check key value pairs - ko - no rel key`() {
|
||||
val response = loadResponses(BROKEN_JSON)!!
|
||||
Assert.assertEquals("https://gast.somedomain.de", response.links!![0].href)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val RESOURCES_PATH =
|
||||
"src/test/responses/eu.qsfera.android.lib.resources.webfinger.responses"
|
||||
private const val TOO_MUCH_INFORMATION_JSON = "$RESOURCES_PATH/to_much_information_response.json"
|
||||
private const val BROKEN_JSON = "$RESOURCES_PATH/broken_response.json"
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"ocs": {
|
||||
"meta": {
|
||||
"status": "ok",
|
||||
"statuscode": 100,
|
||||
"message": "OK",
|
||||
"totalitems": "",
|
||||
"itemsperpage": ""
|
||||
},
|
||||
"data": {
|
||||
"exact": {
|
||||
"users": [],
|
||||
"groups": [],
|
||||
"remotes": []
|
||||
},
|
||||
"users": [],
|
||||
"groups": [],
|
||||
"remotes": []
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"ocs": {
|
||||
"data": {
|
||||
"exact": {
|
||||
"groups": [],
|
||||
"remotes": [],
|
||||
"users": [
|
||||
{
|
||||
"label": "admin",
|
||||
"value": {
|
||||
"shareType": 0,
|
||||
"shareWith": "admin"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"groups": [
|
||||
{
|
||||
"label": "group1",
|
||||
"value": {
|
||||
"shareType": 1,
|
||||
"shareWith": "group1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "group2",
|
||||
"value": {
|
||||
"shareType": 1,
|
||||
"shareWith": "group2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"remotes": [],
|
||||
"users": [
|
||||
{
|
||||
"label": "user1",
|
||||
"value": {
|
||||
"shareType": 0,
|
||||
"shareWith": "user1",
|
||||
"shareWithAdditionalInfo": "user1@user1.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "user2",
|
||||
"value": {
|
||||
"shareType": 0,
|
||||
"shareWith": "user2"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"itemsperpage": "",
|
||||
"message": "OK",
|
||||
"status": "ok",
|
||||
"statuscode": 200,
|
||||
"totalitems": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"subject": "acct:bob@example.com",
|
||||
"aliases": [
|
||||
"https://www.example.com/~bob/"
|
||||
],
|
||||
"properties": {
|
||||
"http://example.com/ns/role": "employee"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"lel": "http://webfinger.example/rel/profile-page",
|
||||
"foo": "https://www.example.com/~bob/"
|
||||
}
|
||||
]
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"subject": "acct:peter.sine@gurken.xxx",
|
||||
"links": [
|
||||
{
|
||||
"rel": "http://webfinger.example/rel/businesscard",
|
||||
"href": "https://www.example.com/~bob/bob.vcf"
|
||||
}
|
||||
]
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"links": [
|
||||
{
|
||||
"href": "https://gast.somedomain.de",
|
||||
"rel": "http://webfinger.qsfera/rel/server-instance"
|
||||
}
|
||||
],
|
||||
"subject": "acct:peter.sine@gurken.xxx"
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"subject": "acct:peter.sine@gurken.xxx",
|
||||
"aliases": [
|
||||
"https://www.example.com/~bob/"
|
||||
],
|
||||
"properties": {
|
||||
"http://example.com/ns/role": "employee"
|
||||
},
|
||||
"gurken": {
|
||||
"whatever": 42
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"gurken": "sallat",
|
||||
"href": "https://gast.somedomain.de",
|
||||
"rel": "http://webfinger.qsfera/rel/server-instance"
|
||||
},
|
||||
{
|
||||
"gurken": "sallat",
|
||||
"rel": "http://webfinger.example/rel/businesscard",
|
||||
"href": "https://www.example.com/~bob/bob.vcf"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user