package social.grain import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json import okhttp3.OkHttpClient import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith import social.grain.data.api.XrpcClient import social.grain.data.api.getCommentThread import social.grain.data.api.getFeed import social.grain.data.api.getGallery import social.grain.data.auth.AuthManager import java.util.concurrent.TimeUnit /** * Decodes live gallery-detail and comment responses into the ported models. * * Same class of risk the feed models carried: a field typed wrong here shows up * only as an empty comment sheet with a decode error buried in the log. */ @RunWith(AndroidJUnit4::class) class CommentDecodeTest { private val client = XrpcClient( baseUrl = AuthManager.SERVER_URL, http = OkHttpClient.Builder() .connectTimeout(20, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .build(), json = Json { ignoreUnknownKeys = true explicitNulls = false coerceInputValues = true }, ) @Test fun galleryDetailDecodesForARealGallery() = runBlocking { val uri = client.getFeed(feed = "recent", limit = 1).items.orEmpty().first().uri val gallery = client.getGallery(uri).gallery assertEquals(uri, gallery.uri) assertTrue("Creator DID missing", gallery.creator.did.startsWith("did:")) assertTrue("Detail returned no photos", gallery.items.orEmpty().isNotEmpty()) } @Test fun commentThreadDecodesAndRepliesNameTheirRoot() = runBlocking { // Walk the feed for a gallery that actually has comments rather than // asserting against whatever happens to be newest. val withComments = client.getFeed(feed = "recent", limit = 60).items.orEmpty() .firstOrNull { (it.commentCount ?: 0) > 0 } if (withComments == null) { // Nothing to assert against; still exercise the decode path so a // broken response type fails rather than silently skipping. client.getCommentThread(client.getFeed(feed = "recent", limit = 1).items!!.first().uri) return@runBlocking } val thread = client.getCommentThread(withComments.uri) assertTrue("Gallery reported comments but the thread was empty", thread.comments.isNotEmpty()) val uris = thread.comments.map { it.uri }.toSet() for (comment in thread.comments) { assertTrue("Comment URI should be an at:// URI", comment.uri.startsWith("at://")) assertTrue("Comment author DID missing", comment.author.did.startsWith("did:")) comment.replyTo?.let { assertTrue("replyTo should be an at:// URI, was '$it'", it.startsWith("at://")) } // Exercise the facet path against whatever real records contain. comment.facets?.forEach { it.parsedFeatures } } assertEquals("Comment URIs should be unique", thread.comments.size, uris.size) } }