mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
fix: adapt Blossom interceptor test to OkHttp 5 Interceptor.Chain
OkHttp 5.4.0 expanded Interceptor.Chain from ~10 to ~40 members, breaking the test's hand-rolled `object : Interceptor.Chain` fake. Replace it with a java.lang.reflect.Proxy that implements only request()/proceed() and throws for the rest, so it survives future interface growth without dozens of empty stubs. A real OkHttpClient can't be used here because Platform init fails outside Robolectric (android.util.Log.isLoggable is unavailable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ece7194456
commit
a75b60135a
+57
-63
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.okhttp
|
||||
|
||||
import okhttp3.Connection
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Protocol
|
||||
@@ -29,6 +28,7 @@ import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import java.lang.reflect.Proxy
|
||||
|
||||
class LocalBlossomCacheRedirectInterceptorTest {
|
||||
private val sha = "b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553"
|
||||
@@ -37,7 +37,7 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
fun bridgeOffPassesThrough() {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { false }
|
||||
val captured = mutableListOf<String>()
|
||||
val response = interceptor.intercept(fakeChain("https://blossom.example.com/$sha.jpg", captured))
|
||||
val response = run(interceptor, "https://blossom.example.com/$sha.jpg", captured)
|
||||
assertEquals("https://blossom.example.com/$sha.jpg", captured.single())
|
||||
response.close()
|
||||
}
|
||||
@@ -46,7 +46,7 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
fun bridgeOnRewritesShaInPath() {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val response = interceptor.intercept(fakeChain("https://blossom.example.com/$sha.jpg", captured))
|
||||
val response = run(interceptor, "https://blossom.example.com/$sha.jpg", captured)
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha.jpg?xs=https%3A%2F%2Fblossom.example.com",
|
||||
captured.single(),
|
||||
@@ -58,7 +58,7 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
fun bridgeOnPreservesNonHexUrls() {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val response = interceptor.intercept(fakeChain("https://example.com/avatar.jpg", captured))
|
||||
val response = run(interceptor, "https://example.com/avatar.jpg", captured)
|
||||
assertEquals("https://example.com/avatar.jpg", captured.single())
|
||||
response.close()
|
||||
}
|
||||
@@ -67,7 +67,7 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
fun bridgeOnSkipsLocalhost() {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val response = interceptor.intercept(fakeChain("http://127.0.0.1:24242/$sha.jpg", captured))
|
||||
val response = run(interceptor, "http://127.0.0.1:24242/$sha.jpg", captured)
|
||||
assertEquals("http://127.0.0.1:24242/$sha.jpg", captured.single())
|
||||
response.close()
|
||||
}
|
||||
@@ -76,7 +76,7 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
fun bridgeOnHandlesNonStandardPort() {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val response = interceptor.intercept(fakeChain("https://blossom.example.com:8443/$sha.png", captured))
|
||||
val response = run(interceptor, "https://blossom.example.com:8443/$sha.png", captured)
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha.png?xs=https%3A%2F%2Fblossom.example.com%3A8443",
|
||||
captured.single(),
|
||||
@@ -88,7 +88,7 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
fun bridgeOnHandlesUppercaseSha() {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val response = interceptor.intercept(fakeChain("https://blossom.example.com/${sha.uppercase()}.jpg", captured))
|
||||
val response = run(interceptor, "https://blossom.example.com/${sha.uppercase()}.jpg", captured)
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha.jpg?xs=https%3A%2F%2Fblossom.example.com",
|
||||
captured.single(),
|
||||
@@ -100,7 +100,7 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
fun bridgeOnFallsBackToBinExtension() {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val response = interceptor.intercept(fakeChain("https://blossom.example.com/$sha", captured))
|
||||
val response = run(interceptor, "https://blossom.example.com/$sha", captured)
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha.bin?xs=https%3A%2F%2Fblossom.example.com",
|
||||
captured.single(),
|
||||
@@ -112,7 +112,7 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
fun bridgeOnHandlesShaInDeeperPathSegment() {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val response = interceptor.intercept(fakeChain("https://nostr.build/i/cache/$sha.webp", captured))
|
||||
val response = run(interceptor, "https://nostr.build/i/cache/$sha.webp", captured)
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha.webp?xs=https%3A%2F%2Fnostr.build%2Fi%2Fcache",
|
||||
captured.single(),
|
||||
@@ -124,7 +124,7 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
fun bridgeOnPreservesNostrBuildPathPrefix() {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val response = interceptor.intercept(fakeChain("https://cdn.nostr.build/i/$sha.jpg", captured))
|
||||
val response = run(interceptor, "https://cdn.nostr.build/i/$sha.jpg", captured)
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha.jpg?xs=https%3A%2F%2Fcdn.nostr.build%2Fi",
|
||||
captured.single(),
|
||||
@@ -138,11 +138,10 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val response =
|
||||
interceptor.intercept(
|
||||
fakeChain(
|
||||
"https://share.yabu.me/84b0c46ab699ac35eb2ca286470b85e081db2087cdef63932236c397417782f5/28fa4d999af6ae3e4e11bfc2727130ef1b3a13cc0f981e5a93c3996cb2f524e5.webp",
|
||||
captured,
|
||||
),
|
||||
run(
|
||||
interceptor,
|
||||
"https://share.yabu.me/84b0c46ab699ac35eb2ca286470b85e081db2087cdef63932236c397417782f5/28fa4d999af6ae3e4e11bfc2727130ef1b3a13cc0f981e5a93c3996cb2f524e5.webp",
|
||||
captured,
|
||||
)
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/28fa4d999af6ae3e4e11bfc2727130ef1b3a13cc0f981e5a93c3996cb2f524e5.webp?xs=https%3A%2F%2Fshare.yabu.me%2F84b0c46ab699ac35eb2ca286470b85e081db2087cdef63932236c397417782f5",
|
||||
@@ -158,21 +157,19 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val url = "https://example.com/$sha/avatar.jpg"
|
||||
val response = interceptor.intercept(fakeChain(url, captured))
|
||||
val response = run(interceptor, url, captured)
|
||||
assertEquals(url, captured.single())
|
||||
response.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeOnSkipsWhenLastSegmentHasNonHexPrefixBeforeSha() {
|
||||
// nostr.build /i/ layout: <prefix>_<sha>.<ext>. The hex inside the
|
||||
// filename isn't a Blossom blob — rewriting to the local cache and
|
||||
fun bridgeOnSkipsNostrBuildResizedVariants() {
|
||||
// sending xs=https://nostr.build/i would 404 because the real blob
|
||||
// is at /i/nostr.build_<sha>.jpg, not /i/<sha>.
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val url = "https://nostr.build/i/nostr.build_$sha.jpg"
|
||||
val response = interceptor.intercept(fakeChain(url, captured))
|
||||
val response = run(interceptor, url, captured)
|
||||
assertEquals(url, captured.single())
|
||||
response.close()
|
||||
}
|
||||
@@ -183,55 +180,52 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val url = "https://example.com/${sha}_thumb.jpg"
|
||||
val response = interceptor.intercept(fakeChain(url, captured))
|
||||
val response = run(interceptor, url, captured)
|
||||
assertEquals(url, captured.single())
|
||||
response.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs [interceptor] against [url] and returns its response. The chain is a
|
||||
* dynamic proxy that only implements [Interceptor.Chain.request] and
|
||||
* [Interceptor.Chain.proceed]; `proceed` records the URL it is asked to
|
||||
* fetch — i.e. the URL after any rewrite [interceptor] applied — and returns
|
||||
* a synthetic 200 without touching the network. A proxy is used instead of
|
||||
* an `object :` literal because OkHttp's `Interceptor.Chain` declares ~40
|
||||
* members (client-config getters and `withX` reconfigurers) that this test
|
||||
* never exercises and that would otherwise need empty stubs.
|
||||
*/
|
||||
private fun run(
|
||||
interceptor: Interceptor,
|
||||
url: String,
|
||||
captured: MutableList<String>,
|
||||
): Response = interceptor.intercept(fakeChain(url, captured))
|
||||
|
||||
private fun fakeChain(
|
||||
url: String,
|
||||
captured: MutableList<String>,
|
||||
): Interceptor.Chain =
|
||||
object : Interceptor.Chain {
|
||||
private val request = Request.Builder().url(url.toHttpUrl()).build()
|
||||
|
||||
override fun request(): Request = request
|
||||
|
||||
override fun proceed(request: Request): Response {
|
||||
captured.add(request.url.toString())
|
||||
return Response
|
||||
.Builder()
|
||||
.request(request)
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.code(200)
|
||||
.message("OK")
|
||||
.body("".toResponseBody(null))
|
||||
.build()
|
||||
): Interceptor.Chain {
|
||||
val request = Request.Builder().url(url.toHttpUrl()).build()
|
||||
return Proxy.newProxyInstance(
|
||||
Interceptor.Chain::class.java.classLoader,
|
||||
arrayOf(Interceptor.Chain::class.java),
|
||||
) { _, method, args ->
|
||||
when (method.name) {
|
||||
"request" -> request
|
||||
"proceed" -> {
|
||||
val proceeded = args[0] as Request
|
||||
captured.add(proceeded.url.toString())
|
||||
Response
|
||||
.Builder()
|
||||
.request(proceeded)
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.code(200)
|
||||
.message("OK")
|
||||
.body("".toResponseBody(null))
|
||||
.build()
|
||||
}
|
||||
else -> throw UnsupportedOperationException(method.name)
|
||||
}
|
||||
|
||||
override fun connection(): Connection? = null
|
||||
|
||||
override fun call() = throw UnsupportedOperationException()
|
||||
|
||||
override fun connectTimeoutMillis(): Int = 0
|
||||
|
||||
override fun readTimeoutMillis(): Int = 0
|
||||
|
||||
override fun writeTimeoutMillis(): Int = 0
|
||||
|
||||
override fun withConnectTimeout(
|
||||
timeout: Int,
|
||||
unit: java.util.concurrent.TimeUnit,
|
||||
): Interceptor.Chain = this
|
||||
|
||||
override fun withReadTimeout(
|
||||
timeout: Int,
|
||||
unit: java.util.concurrent.TimeUnit,
|
||||
): Interceptor.Chain = this
|
||||
|
||||
override fun withWriteTimeout(
|
||||
timeout: Int,
|
||||
unit: java.util.concurrent.TimeUnit,
|
||||
): Interceptor.Chain = this
|
||||
}
|
||||
} as Interceptor.Chain
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user