Merge branch 'refresh_tokens' into 'master'

Store refresh tokens and use them if request fails with 401

Closes #169

See merge request pixeldroid/PixelDroid!265
This commit is contained in:
Matthieu 2020-12-08 18:48:15 +01:00
commit 271b9f310d
19 changed files with 177 additions and 122 deletions

View File

@ -72,14 +72,14 @@ dependencies {
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1' implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
implementation "androidx.browser:browser:1.2.0" implementation "androidx.browser:browser:1.3.0"
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1' implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
implementation 'androidx.paging:paging-runtime-ktx:3.0.0-alpha09' implementation 'androidx.paging:paging-runtime-ktx:3.0.0-alpha10'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
@ -110,23 +110,23 @@ dependencies {
implementation 'com.google.android.material:material:1.2.1' implementation 'com.google.android.material:material:1.2.1'
//Dagger (dependency injection) //Dagger (dependency injection)
implementation 'com.google.dagger:dagger-android:2.29.1' implementation 'com.google.dagger:dagger-android:2.30.1'
implementation 'com.google.dagger:dagger-android-support:2.29.1' implementation 'com.google.dagger:dagger-android-support:2.30.1'
// if you use the support libraries // if you use the support libraries
kapt 'com.google.dagger:dagger-android-processor:2.29.1' kapt 'com.google.dagger:dagger-android-processor:2.30.1'
kapt 'com.google.dagger:dagger-compiler:2.29.1' kapt 'com.google.dagger:dagger-compiler:2.30.1'
implementation 'com.squareup.okhttp3:okhttp:4.9.0' implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.19' implementation 'io.reactivex.rxjava2:rxjava:2.2.20'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'com.github.connyduck:sparkbutton:4.0.0' implementation 'com.github.connyduck:sparkbutton:4.0.0'
implementation 'info.androidhive:imagefilters:1.0.7' implementation 'info.androidhive:imagefilters:1.0.7'
implementation 'com.github.yalantis:ucrop:2.2.5-native' implementation 'com.github.yalantis:ucrop:2.2.6-native'
implementation("com.github.bumptech.glide:glide:4.11.0") { implementation("com.github.bumptech.glide:glide:4.11.0") {
exclude group: "com.android.support" exclude group: "com.android.support"
@ -141,13 +141,13 @@ dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "com.mikepenz:materialdrawer:8.1.5" implementation "com.mikepenz:materialdrawer:8.1.8"
// Add for NavController support // Add for NavController support
implementation "com.mikepenz:materialdrawer-nav:8.1.5" implementation "com.mikepenz:materialdrawer-nav:8.1.5"
//iconics //iconics
implementation "com.mikepenz:iconics-core:5.0.3" implementation "com.mikepenz:iconics-core:5.0.3"
implementation "com.mikepenz:materialdrawer-iconics:8.1.5" implementation "com.mikepenz:materialdrawer-iconics:8.1.8"
implementation "com.mikepenz:iconics-views:5.0.3" implementation "com.mikepenz:iconics-views:5.0.3"
implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar' implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar'

View File

@ -49,7 +49,10 @@ class CameraTest {
display_name = "Testi Testo", display_name = "Testi Testo",
avatar_static = "some_avatar_url", avatar_static = "some_avatar_url",
isActive = true, isActive = true,
accessToken = "token" accessToken = "token",
refreshToken = refreshToken,
clientId = clientId,
clientSecret = clientSecret
) )
) )
db.close() db.close()

View File

@ -60,7 +60,10 @@ class DrawerMenuTest {
display_name = "Testi Testo", display_name = "Testi Testo",
avatar_static = "some_avatar_url", avatar_static = "some_avatar_url",
isActive = true, isActive = true,
accessToken = "token" accessToken = "token",
refreshToken = refreshToken,
clientId = clientId,
clientSecret = clientSecret
) )
) )
db.close() db.close()

View File

@ -67,7 +67,10 @@ class HomeFeedTest {
display_name = "Testi Testo", display_name = "Testi Testo",
avatar_static = "some_avatar_url", avatar_static = "some_avatar_url",
isActive = true, isActive = true,
accessToken = "token" accessToken = "token",
refreshToken = refreshToken,
clientId = clientId,
clientSecret = clientSecret
) )
) )
db.close() db.close()

View File

@ -83,7 +83,10 @@ class IntentTest {
display_name = "Testi Testo", display_name = "Testi Testo",
avatar_static = "some_avatar_url", avatar_static = "some_avatar_url",
isActive = true, isActive = true,
accessToken = "token" accessToken = "token",
refreshToken = refreshToken,
clientId = clientId,
clientSecret = clientSecret
) )
) )
db.close() db.close()

View File

@ -123,7 +123,10 @@ class LoginActivityOnlineTest {
display_name = "Testi Testo", display_name = "Testi Testo",
avatar_static = "some_avatar_url", avatar_static = "some_avatar_url",
isActive = true, isActive = true,
accessToken = "token" accessToken = "token",
refreshToken = refreshToken,
clientId = clientId,
clientSecret = clientSecret
) )
) )
db.close() db.close()

View File

@ -95,7 +95,10 @@ class PostFragmentUITests {
display_name = "Testi Testo", display_name = "Testi Testo",
avatar_static = "some_avatar_url", avatar_static = "some_avatar_url",
isActive = true, isActive = true,
accessToken = "token" accessToken = "token",
refreshToken = refreshToken,
clientId = clientId,
clientSecret = clientSecret
) )
) )
db.close() db.close()

View File

@ -61,7 +61,10 @@ class PostTest {
display_name = "Testi Testo", display_name = "Testi Testo",
avatar_static = "some_avatar_url", avatar_static = "some_avatar_url",
isActive = true, isActive = true,
accessToken = "token" accessToken = "token",
refreshToken = refreshToken,
clientId = clientId,
clientSecret = clientSecret
) )
) )
db.close() db.close()

View File

@ -24,7 +24,6 @@ import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
import io.reactivex.functions.Function3
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_login.* import kotlinx.android.synthetic.main.activity_login.*
import okhttp3.HttpUrl import okhttp3.HttpUrl
@ -32,6 +31,7 @@ import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import javax.inject.Inject import javax.inject.Inject
/** /**
Overview of the flow of the login process: (boxes are requests done in parallel, Overview of the flow of the login process: (boxes are requests done in parallel,
since they do not depend on each other) since they do not depend on each other)
@ -138,7 +138,7 @@ class LoginActivity : AppCompatActivity() {
hideKeyboard() hideKeyboard()
loadingAnimation(true) loadingAnimation(true)
pixelfedAPI = apiHolder.setDomain(normalizedDomain) pixelfedAPI = PixelfedAPI.createFromUrl(normalizedDomain)
Single.zip( Single.zip(
pixelfedAPI.registerApplication( pixelfedAPI.registerApplication(
@ -251,12 +251,12 @@ class LoginActivity : AppCompatActivity() {
} }
//Successful authorization //Successful authorization
pixelfedAPI = apiHolder.setDomain(domain) pixelfedAPI = PixelfedAPI.createFromUrl(domain)
//TODO check why we can't do onErrorReturn { null } which would make more sense ¯\_(ツ)_/¯ //TODO check why we can't do onErrorReturn { null } which would make more sense ¯\_(ツ)_/¯
//Also, maybe find a nicer way to do this, this feels hacky (although it can work fine) //Also, maybe find a nicer way to do this, this feels hacky (although it can work fine)
val nullInstance = Instance(null, null, null, null, null, null, null, null) val nullInstance = Instance(null, null, null, null, null, null, null, null)
val nullToken = Token(null, null, null, null) val nullToken = Token(null, null, null, null, null)
Single.zip( Single.zip(
pixelfedAPI.instance().onErrorReturn { nullInstance }, pixelfedAPI.instance().onErrorReturn { nullInstance },
@ -280,7 +280,7 @@ class LoginActivity : AppCompatActivity() {
} }
DBUtils.storeInstance(db, instance) DBUtils.storeInstance(db, instance)
storeUser(token.access_token, instance.uri) storeUser(token.access_token, token.refresh_token, clientId, clientSecret, instance.uri)
wipeSharedSettings() wipeSharedSettings()
} }
@ -314,7 +314,7 @@ class LoginActivity : AppCompatActivity() {
} }
} }
private fun storeUser(accessToken: String, instance: String) { private fun storeUser(accessToken: String, refreshToken: String?, clientId: String, clientSecret: String, instance: String) {
pixelfedAPI.verifyCredentials("Bearer $accessToken") pixelfedAPI.verifyCredentials("Bearer $accessToken")
.enqueue(object : Callback<Account> { .enqueue(object : Callback<Account> {
override fun onResponse(call: Call<Account>, response: Response<Account>) { override fun onResponse(call: Call<Account>, response: Response<Account>) {
@ -326,7 +326,10 @@ class LoginActivity : AppCompatActivity() {
user, user,
instance, instance,
activeUser = true, activeUser = true,
accessToken = accessToken accessToken = accessToken,
refreshToken = refreshToken,
clientId = clientId,
clientSecret = clientSecret
) )
apiHolder.setDomainToCurrentUser(db) apiHolder.setDomainToCurrentUser(db)
val intent = Intent(this@LoginActivity, MainActivity::class.java) val intent = Intent(this@LoginActivity, MainActivity::class.java)

View File

@ -8,7 +8,6 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
@ -190,6 +189,9 @@ class MainActivity : AppCompatActivity() {
if (hasInternet(applicationContext)) { if (hasInternet(applicationContext)) {
val domain = user?.instance_uri.orEmpty() val domain = user?.instance_uri.orEmpty()
val accessToken = user?.accessToken.orEmpty() val accessToken = user?.accessToken.orEmpty()
val refreshToken = user?.refreshToken
val clientId = user?.clientId.orEmpty()
val clientSecret = user?.clientSecret.orEmpty()
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db) val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
api.verifyCredentials("Bearer $accessToken") api.verifyCredentials("Bearer $accessToken")
.enqueue(object : Callback<Account> { .enqueue(object : Callback<Account> {
@ -199,7 +201,7 @@ class MainActivity : AppCompatActivity() {
) { ) {
if (response.body() != null && response.isSuccessful) { if (response.body() != null && response.isSuccessful) {
val account = response.body() as Account val account = response.body() as Account
DBUtils.addUser(db, account, domain, accessToken = accessToken) DBUtils.addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken, clientId = clientId, clientSecret = clientSecret)
fillDrawerAccountInfo(account.id!!) fillDrawerAccountInfo(account.id!!)
} }
} }

View File

@ -23,11 +23,7 @@ interface PixelfedAPI {
companion object { companion object {
@Deprecated( fun createFromUrl(baseUrl: String): PixelfedAPI {
"Use the DI-d PixelfedAPIHolder instead",
ReplaceWith("apiHolder.api")
)
fun create(baseUrl: String): PixelfedAPI {
return Retrofit.Builder() return Retrofit.Builder()
.baseUrl(baseUrl) .baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
@ -52,10 +48,11 @@ interface PixelfedAPI {
fun obtainToken( fun obtainToken(
@Field("client_id") client_id: String, @Field("client_id") client_id: String,
@Field("client_secret") client_secret: String, @Field("client_secret") client_secret: String,
@Field("redirect_uri") redirect_uri: String, @Field("redirect_uri") redirect_uri: String? = null,
@Field("scope") scope: String? = "read", @Field("scope") scope: String? = "read",
@Field("code") code: String? = null, @Field("code") code: String? = null,
@Field("grant_type") grant_type: String? = null @Field("grant_type") grant_type: String? = null,
@Field("refresh_token") refresh_token: String? = null
): Single<Token> ): Single<Token>
// get instance configuration // get instance configuration

View File

@ -3,7 +3,6 @@ package com.h.pixeldroid.db.entities
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.Index import androidx.room.Index
import com.h.pixeldroid.db.entities.InstanceDatabaseEntity
@Entity( @Entity(
tableName = "users", tableName = "users",
@ -24,5 +23,8 @@ data class UserDatabaseEntity (
var display_name: String, var display_name: String,
var avatar_static: String, var avatar_static: String,
var isActive: Boolean, var isActive: Boolean,
var accessToken: String var accessToken: String,
val refreshToken: String?,
val clientId: String,
val clientSecret: String
) )

View File

@ -2,11 +2,14 @@ package com.h.pixeldroid.di
import com.h.pixeldroid.api.PixelfedAPI import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.db.AppDatabase import com.h.pixeldroid.db.AppDatabase
import com.h.pixeldroid.db.entities.UserDatabaseEntity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import okhttp3.*
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.lang.Exception
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@ -15,25 +18,50 @@ class APIModule{
@Provides @Provides
@Singleton @Singleton
fun providesAPIHolder(db: AppDatabase): PixelfedAPIHolder { fun providesAPIHolder(db: AppDatabase): PixelfedAPIHolder {
return PixelfedAPIHolder(db.userDao().getActiveUser()?.instance_uri) return PixelfedAPIHolder(db.userDao().getActiveUser())
}
} }
class TokenAuthenticator(val user: UserDatabaseEntity) : Authenticator {
val pixelfedAPI = PixelfedAPI.createFromUrl(user.instance_uri)
override fun authenticate(route: Route?, response: Response): Request? {
if (response.request.header("Authorization") != null) {
return null // Give up, we've already failed to authenticate.
}
// Refresh the access_token using a synchronous api request
val newAccessToken: String = try {
pixelfedAPI.obtainToken(
scope = "", grant_type = "refresh_token",
refresh_token = user.refreshToken, client_id = user.clientId, client_secret = user.clientSecret
).blockingGet().access_token
}catch (e: Exception){
null
}.orEmpty()
// Add new header to rejected request and retry it
return response.request.newBuilder()
.header("Authorization", "Bearer $newAccessToken")
.build()
}
} }
class PixelfedAPIHolder(user: UserDatabaseEntity?){
class PixelfedAPIHolder(domain: String?){
private val intermediate: Retrofit.Builder = Retrofit.Builder() private val intermediate: Retrofit.Builder = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
var api: PixelfedAPI? = if (domain != null) setDomain(domain) else null var api: PixelfedAPI? = if (user != null) setDomain(user) else null
fun setDomainToCurrentUser(db: AppDatabase): PixelfedAPI { fun setDomainToCurrentUser(db: AppDatabase): PixelfedAPI {
return setDomain(db.userDao().getActiveUser()!!.instance_uri) return setDomain(db.userDao().getActiveUser()!!)
} }
fun setDomain(domain: String): PixelfedAPI { fun setDomain(user: UserDatabaseEntity): PixelfedAPI {
val newAPI = intermediate val newAPI = intermediate
.baseUrl(domain) .baseUrl(user.instance_uri)
.client(OkHttpClient().newBuilder().authenticator(TokenAuthenticator(user)).build())
.build().create(PixelfedAPI::class.java) .build().create(PixelfedAPI::class.java)
api = newAPI api = newAPI
return newAPI return newAPI

View File

@ -32,7 +32,7 @@ class PostFragment : BaseFragment() {
val user = db.userDao().getActiveUser()!! val user = db.userDao().getActiveUser()!!
val api = apiHolder.api ?: apiHolder.setDomain(user.instance_uri) val api = apiHolder.api ?: apiHolder.setDomain(user)
val holder = StatusViewHolder(root) val holder = StatusViewHolder(root)

View File

@ -89,8 +89,8 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val uiModel = getItem(position) as Status val uiModel = getItem(position) as Status
uiModel.let { uiModel.let {
val instanceUri = db.userDao().getActiveUser()!!.instance_uri val user = db.userDao().getActiveUser()!!
(holder as StatusViewHolder).bind(it, apiHolder.setDomain(instanceUri), db, lifecycleScope) (holder as StatusViewHolder).bind(it, apiHolder.setDomain(user), db, lifecycleScope)
} }
} }
} }

View File

@ -79,8 +79,8 @@ class SearchPostsFragment : UncachedFeedFragment<Status>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val uiModel = getItem(position) as Status val uiModel = getItem(position) as Status
uiModel.let { uiModel.let {
val instanceUri = db.userDao().getActiveUser()!!.instance_uri val user = db.userDao().getActiveUser()!!
(holder as StatusViewHolder).bind(it, apiHolder.setDomain(instanceUri), db, lifecycleScope) (holder as StatusViewHolder).bind(it, apiHolder.setDomain(user), db, lifecycleScope)
} }
} }
} }

View File

@ -2,6 +2,7 @@ package com.h.pixeldroid.objects
data class Token( data class Token(
val access_token: String?, val access_token: String?,
val refresh_token: String?,
val token_type: String?, val token_type: String?,
val scope: String?, val scope: String?,
val created_at: Int? val created_at: Int?

View File

@ -19,7 +19,8 @@ class DBUtils {
} }
} }
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true, accessToken: String) { fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String) {
db.userDao().insertUser( db.userDao().insertUser(
UserDatabaseEntity( UserDatabaseEntity(
user_id = account.id!!, user_id = account.id!!,
@ -29,7 +30,10 @@ class DBUtils {
display_name = account.getDisplayName(), display_name = account.getDisplayName(),
avatar_static = account.avatar_static.orEmpty(), avatar_static = account.avatar_static.orEmpty(),
isActive = activeUser, isActive = activeUser,
accessToken = accessToken accessToken = accessToken,
refreshToken = refreshToken,
clientId = clientId,
clientSecret = clientSecret
) )
) )
} }

View File

@ -5,15 +5,11 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule
import com.h.pixeldroid.api.PixelfedAPI import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.objects.* import com.h.pixeldroid.objects.*
import io.reactivex.Single import io.reactivex.Single
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import retrofit2.Call
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.*
/** /**
@ -92,9 +88,9 @@ class APIUnitTest {
val statusesHome: List<Status> val statusesHome: List<Status>
runBlocking { runBlocking {
statuses = PixelfedAPI.create("http://localhost:8089") statuses = PixelfedAPI.createFromUrl("http://localhost:8089")
.timelinePublic(null, null, null, null, null) .timelinePublic(null, null, null, null, null)
statusesHome = PixelfedAPI.create("http://localhost:8089") statusesHome = PixelfedAPI.createFromUrl("http://localhost:8089")
.timelineHome("abc", null, null, null,null, null) .timelineHome("abc", null, null, null,null, null)
} }
@ -116,7 +112,7 @@ class APIUnitTest {
.withHeader("Content-Type", "application/json") .withHeader("Content-Type", "application/json")
.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}""" .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 call: Single<Application> = PixelfedAPI.create("http://localhost:8089") val call: Single<Application> = PixelfedAPI.createFromUrl("http://localhost:8089")
.registerApplication("Pixeldroid", "urn:ietf:wg:oauth:2.0:oob", "read write follow") .registerApplication("Pixeldroid", "urn:ietf:wg:oauth:2.0:oob", "read write follow")
val application: Application = call.toFuture().get() val application: Application = call.toFuture().get()
@ -136,6 +132,7 @@ class APIUnitTest {
.withHeader("Content-Type", "application/json") .withHeader("Content-Type", "application/json")
.withBody("""{ .withBody("""{
"access_token": "ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0", "access_token": "ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0",
"refresh_token": "ZA-Yj3aBD8U8Cm7lKUp-sqfdsqfdqfsdfqds",
"token_type": "Bearer", "token_type": "Bearer",
"scope": "read write follow push", "scope": "read write follow push",
"created_at": 1573979017 "created_at": 1573979017
@ -144,7 +141,7 @@ class APIUnitTest {
val OAUTH_SCHEME = "oauth2redirect" val OAUTH_SCHEME = "oauth2redirect"
val SCOPE = "read write follow" val SCOPE = "read write follow"
val PACKAGE_ID = "com.h.pixeldroid" val PACKAGE_ID = "com.h.pixeldroid"
val call: Single<Token> = PixelfedAPI.create("http://localhost:8089") val call: Single<Token> = PixelfedAPI.createFromUrl("http://localhost:8089")
.obtainToken("123", "ssqdfqsdfqds", "$OAUTH_SCHEME://$PACKAGE_ID", SCOPE, "abc", .obtainToken("123", "ssqdfqsdfqds", "$OAUTH_SCHEME://$PACKAGE_ID", SCOPE, "abc",
"authorization_code") "authorization_code")
val token: Token = call.toFuture().get() val token: Token = call.toFuture().get()
@ -152,7 +149,7 @@ class APIUnitTest {
assertEquals("Bearer", token.token_type) assertEquals("Bearer", token.token_type)
assertEquals("read write follow push", token.scope) assertEquals("read write follow push", token.scope)
assertEquals(1573979017, token.created_at) assertEquals(1573979017, token.created_at)
assertEquals(Token("ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0", "Bearer", "read write follow push",1573979017), token) assertEquals(Token("ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0", "ZA-Yj3aBD8U8Cm7lKUp-sqfdsqfdqfsdfqds","Bearer", "read write follow push",1573979017), token)
} }