diff --git a/app/src/main/java/org/pixeldroid/app/utils/Utils.kt b/app/src/main/java/org/pixeldroid/app/utils/Utils.kt index 9e235cad..639cf2d0 100644 --- a/app/src/main/java/org/pixeldroid/app/utils/Utils.kt +++ b/app/src/main/java/org/pixeldroid/app/utils/Utils.kt @@ -27,8 +27,14 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.arthenica.ffmpegkit.FFmpegKitConfig import com.google.android.material.color.MaterialColors +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializer import okhttp3.HttpUrl import org.pixeldroid.app.R +import java.time.Instant +import java.time.format.DateTimeFormatter import java.util.* import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -227,6 +233,17 @@ fun Context.themeActionBar(): Int { @ColorInt fun Context.getColorFromAttr(@AttrRes attrColor: Int): Int = MaterialColors.getColor(this, attrColor, Color.BLACK) + +val typeAdapterInstantDeserializer: JsonDeserializer = JsonDeserializer { json: JsonElement, _, _ -> + DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse( + json.asString, Instant::from + ) +} + +val typeAdapterInstantSerializer: JsonSerializer = JsonSerializer { src: Instant, _, _ -> + JsonPrimitive(DateTimeFormatter.ISO_INSTANT.format(src)) +} + /** * Delegated property to use in fragments to prevent memory leaks of bindings. * This makes it unnecessary to set binding to null in onDestroyView. diff --git a/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt b/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt index 152d0081..0779ce02 100644 --- a/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt +++ b/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt @@ -11,6 +11,8 @@ import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity import org.pixeldroid.app.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.TokenAuthenticator +import org.pixeldroid.app.utils.typeAdapterInstantDeserializer +import org.pixeldroid.app.utils.typeAdapterInstantSerializer import retrofit2.Response import retrofit2.Retrofit import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory @@ -40,42 +42,31 @@ interface PixelfedAPI { } fun createFromUrl(baseUrl: String): PixelfedAPI { - return Retrofit.Builder().client( - OkHttpClient().newBuilder().addNetworkInterceptor(headerInterceptor) - // Only do secure-ish TLS connections (no HTTP or very old SSL/TLS) - .connectionSpecs(listOf(ConnectionSpec.MODERN_TLS)).build() - ) + return Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create(gSonInstance)) .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) + .client( + OkHttpClient().newBuilder().addNetworkInterceptor(headerInterceptor) + // Only do secure-ish TLS connections (no HTTP or very old SSL/TLS) + .connectionSpecs(listOf(ConnectionSpec.MODERN_TLS)).build() + ) .build().create(PixelfedAPI::class.java) } - private var gSonInstance: Gson = GsonBuilder() - .registerTypeAdapter( - Instant::class.java, - JsonDeserializer { json: JsonElement, _, _ -> - DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse( - json.asString, Instant::from - ) - } as JsonDeserializer).registerTypeAdapter( - Instant::class.java, - JsonSerializer { src: Instant, _, _ -> - JsonPrimitive(DateTimeFormatter.ISO_INSTANT.format(src)) - }) + private val gSonInstance: Gson = GsonBuilder() + .registerTypeAdapter(Instant::class.java, typeAdapterInstantDeserializer) + .registerTypeAdapter(Instant::class.java, typeAdapterInstantSerializer) .create() - private val intermediate: Retrofit.Builder = Retrofit.Builder() - .addConverterFactory(GsonConverterFactory.create(gSonInstance)) - .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) - - fun apiForUser( user: UserDatabaseEntity, db: AppDatabase, pixelfedAPIHolder: PixelfedAPIHolder ): PixelfedAPI = - intermediate + Retrofit.Builder() + .addConverterFactory(GsonConverterFactory.create(gSonInstance)) + .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) .baseUrl(user.instance_uri) .client( OkHttpClient().newBuilder().addNetworkInterceptor(headerInterceptor) diff --git a/app/src/test/java/org/pixeldroid/app/APIUnitTest.kt b/app/src/test/java/org/pixeldroid/app/APIUnitTest.kt index b7c7d955..347e92b7 100644 --- a/app/src/test/java/org/pixeldroid/app/APIUnitTest.kt +++ b/app/src/test/java/org/pixeldroid/app/APIUnitTest.kt @@ -2,13 +2,26 @@ package org.pixeldroid.app import com.github.tomakehurst.wiremock.client.WireMock.* import com.github.tomakehurst.wiremock.junit.WireMockRule +import com.google.gson.GsonBuilder +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializer import org.pixeldroid.app.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.objects.* import kotlinx.coroutines.runBlocking +import okhttp3.ConnectionSpec +import okhttp3.OkHttpClient import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test +import org.pixeldroid.app.utils.typeAdapterInstantDeserializer +import org.pixeldroid.app.utils.typeAdapterInstantSerializer +import retrofit2.Retrofit +import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory import java.time.Instant +import java.time.format.DateTimeFormatter /** @@ -20,6 +33,23 @@ class APIUnitTest { @get:Rule var wireMockRule = WireMockRule(8089) + + // Same as in PixelfedAPI but allow cleartext + private val api: PixelfedAPI = Retrofit.Builder() + .baseUrl("http://localhost:8089") + .addConverterFactory(GsonConverterFactory.create(GsonBuilder() + .registerTypeAdapter(Instant::class.java, typeAdapterInstantDeserializer) + .registerTypeAdapter(Instant::class.java, typeAdapterInstantSerializer) + .create()) + ) + .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) + .client( + OkHttpClient().newBuilder().addNetworkInterceptor(PixelfedAPI.headerInterceptor) + // Allow cleartext + .connectionSpecs(listOf(ConnectionSpec.CLEARTEXT)).build() + ) + .build().create(PixelfedAPI::class.java) + @Test fun api_correctly_translated_data_class() { stubFor( @@ -44,9 +74,10 @@ class APIUnitTest { val statusesHome: List runBlocking { - statuses = PixelfedAPI.createFromUrl("http://localhost:8089") + + statuses = api .timelinePublic(null, null, null, null, null) - statusesHome = PixelfedAPI.createFromUrl("http://localhost:8089") + statusesHome = api .timelineHome(null, null, null, null, null) } @@ -69,8 +100,7 @@ class APIUnitTest { .withBody(""" {"id":3197,"name":"Pixeldroid","website":null,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":3197,"client_secret":"hhRwLupqUJPghKsZzpZtxNV67g5DBdPYCqW6XE3m","vapid_key":null}""" ))) val application: Application = runBlocking { - PixelfedAPI.createFromUrl("http://localhost:8089") - .registerApplication("Pixeldroid", "urn:ietf:wg:oauth:2.0:oob", "read write follow") + api.registerApplication("Pixeldroid", "urn:ietf:wg:oauth:2.0:oob", "read write follow") } assertEquals("3197", application.client_id) @@ -100,8 +130,7 @@ class APIUnitTest { val PACKAGE_ID = "org.pixeldroid.app" val token: Token = runBlocking { - PixelfedAPI.createFromUrl("http://localhost:8089") - .obtainToken( + api.obtainToken( "123", "ssqdfqsdfqds", "$OAUTH_SCHEME://$PACKAGE_ID", SCOPE, "abc", "authorization_code" )