Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix malformed fallback streaming URL #534

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ import javax.net.ssl.X509TrustManager
*/
class MastodonClient private constructor(
private val instanceName: String,
private val streamingUrl: String,
internal val streamingUrl: String,
private val client: OkHttpClient,
private val accessToken: String? = null,
private val debug: Boolean = false,
Expand Down Expand Up @@ -939,11 +939,11 @@ class MastodonClient private constructor(
this.debug = true
}

private fun getStreamingApiUrl(fallbackUrl: String): String {
private fun getStreamingApiUrl(fallbackUrl: () -> String): String {
executeInstanceRequest().use { response: Response ->
if (!response.isSuccessful) return fallbackUrl
if (!response.isSuccessful) return fallbackUrl()

val streamingUrl: String? = response.body?.string()?.let { responseBody: String ->
val streamingUrl: String? = response.body.string().let { responseBody: String ->
val rawJsonObject = JSON_SERIALIZER
.parseToJsonElement(responseBody)
.jsonObject
Expand All @@ -956,7 +956,7 @@ class MastodonClient private constructor(
?.replace("wss:", "https:")
}

return streamingUrl ?: fallbackUrl
return streamingUrl ?: fallbackUrl()
}
}

Expand Down Expand Up @@ -1038,7 +1038,11 @@ class MastodonClient private constructor(
instanceVersion = getInstanceVersion(),
scheme = scheme,
port = port,
streamingUrl = getStreamingApiUrl(fallbackUrl = scheme + instanceName)
streamingUrl = getStreamingApiUrl(
fallbackUrl = {
HttpUrl.Builder().scheme(scheme).host(instanceName).toString()
}
)
)
}
}
Expand Down
109 changes: 108 additions & 1 deletion bigbone/src/test/kotlin/social/bigbone/MastodonClientTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import org.amshove.kluent.invoking
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldThrow
import org.amshove.kluent.withMessage
import org.junit.jupiter.api.Test
Expand All @@ -27,7 +28,13 @@ class MastodonClientTest {
val clientBuilder = spyk(MastodonClient.Builder(serverUrl)) {
// Mock internal NodeInfoClient so that we don't open the site in unit testing
mockkObject(NodeInfoClient)
every { NodeInfoClient.retrieveServerInfo(host = serverUrl, scheme = scheme, port = port) } throws ServerInfoRetrievalException(
every {
NodeInfoClient.retrieveServerInfo(
host = serverUrl,
scheme = scheme,
port = port
)
} throws ServerInfoRetrievalException(
"just for testing",
null
)
Expand Down Expand Up @@ -131,4 +138,104 @@ class MastodonClientTest {

invoking(clientBuilder::build) shouldThrow UnknownHostException::class
}

@Test
fun `Given streaming URL in instance response, when building MastodonClient, then use that streaming URL`() {
val serverUrl = "mastodon.example"
val expectedStreamingUrl = "wss://streaming.example.com"
val clientBuilder = spyk(MastodonClient.Builder(serverUrl)) {
// Mock internal NodeInfoClient so that we don't open the site in unit testing
mockkObject(NodeInfoClient)
every { NodeInfoClient.retrieveServerInfo(host = serverUrl) } returns Server(
schemaVersion = "2.0",
software = Server.Software(name = "mastodon", version = "4.0.0")
)

val jsonResponse = """
{
"domain": "mastodon.example",
"title": "Mastodon Example",
"version": "4.3.5",
"source_url": "https://github.com/mastodon/mastodon",
"description": "A Mastodon instance for testing",
"configuration": {
"urls": {
"streaming": "$expectedStreamingUrl"
}
}
}
""".trimIndent()

val responseMock = mockk<Response> {
every { body } answers { jsonResponse.toResponseBody("application/json".toMediaType()) }
every { isSuccessful } answers { true }
every { close() } returns Unit
}
every { executeInstanceRequest() } answers { responseMock }
}

val mastodonClient = clientBuilder.build()
mastodonClient.streamingUrl shouldBeEqualTo expectedStreamingUrl.replace("wss", "https")
}

@Test
fun `Given no streaming URL in instance response, when building MastodonClient, then use fallback streaming url`() {
val serverUrl = "mastodon.example"
val clientBuilder = spyk(MastodonClient.Builder(serverUrl)) {
// Mock internal NodeInfoClient so that we don't open the site in unit testing
mockkObject(NodeInfoClient)
every { NodeInfoClient.retrieveServerInfo(host = serverUrl) } returns Server(
schemaVersion = "2.0",
software = Server.Software(name = "mastodon", version = "4.0.0")
)

val jsonResponse = """
{
"domain": "mastodon.example",
"title": "Mastodon Example",
"version": "4.3.5",
"source_url": "https://github.com/mastodon/mastodon",
"description": "A Mastodon instance for testing",
"configuration": {
"urls": { }
}
}
""".trimIndent()

val responseMock = mockk<Response> {
every { body } answers { jsonResponse.toResponseBody("application/json".toMediaType()) }
every { isSuccessful } answers { true }
every { close() } returns Unit
}
every { executeInstanceRequest() } answers { responseMock }
}

val mastodonClient = clientBuilder.build()
mastodonClient.streamingUrl shouldBeEqualTo "https://$serverUrl/"
}

@Test
fun `Given valid server response and no response to instance request, when building server, then use fallback streaming url`() {
val serverUrl = "mastodon.example"
val clientBuilder = spyk(MastodonClient.Builder(serverUrl)) {
// Mock internal NodeInfoClient so that we don't open the site in unit testing
mockkObject(NodeInfoClient)
every { NodeInfoClient.retrieveServerInfo(host = serverUrl) } returns Server(
schemaVersion = "2.0",
software = Server.Software(name = "mastodon", version = "4.3.5")
)

val responseMock = mockk<Response> {
every { body } answers {
"{\"error\": \"Instance info not found\"}".toResponseBody("application/json".toMediaType())
}
every { isSuccessful } answers { false }
every { close() } returns Unit
}
every { executeInstanceRequest() } answers { responseMock }
}

val mastodonClient = clientBuilder.build()
mastodonClient.streamingUrl shouldBeEqualTo "https://$serverUrl/"
}
}