~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain-android

1 collaborator
0
1
main
104 lines · 3.9 KB Raw History
package social.grain

import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import social.grain.data.api.GetFollowersResponse
import social.grain.data.api.GetGalleryFavoritesResponse
import social.grain.data.api.GetKnownFollowersResponse

/**
 * The four actor lists share one model, so a field typed wrong breaks all of
 * them at once — and only at runtime, as an empty list with a decode error in
 * the log. The follower fixture is a real response captured from the appview.
 */
class ActorListDecodeTest {

    private val json = Json {
        ignoreUnknownKeys = true
        explicitNulls = false
        coerceInputValues = true
    }

    private val followersFixture = """
        {
          "totalCount": 5,
          "items": [
            {
              "did": "did:plc:bcgltzqazw5tb6k2g3ttenbj",
              "handle": "chadtmiller.com",
              "displayName": "chad",
              "description": "building @grain.social, suuuup",
              "avatar": "https://cdn.grain.social/x/avatar/plain/did:plc:bcgltzqazw5tb6k2g3ttenbj/y"
            },
            {
              "did": "did:plc:z6sonfx6nicq2ndrixghcolb",
              "handle": "jj.mcdonough.rodeo",
              "displayName": "JJ",
              "description": "Data guy. Tryhard. VT, US. Born at 341ppm.",
              "avatar": "https://cdn.grain.social/z/avatar/plain/did:plc:z6sonfx6nicq2ndrixghcolb/w"
            }
          ],
          "cursor": "Mjo6YmFmeXJlaWJjNWVhdHIzcnJrNXVjdGhmbDZkZHB6ZHF2cng3bHRsZWRnc3p3N2gzeXlwam9scGpjeWE"
        }
    """.trimIndent()

    @Test
    fun `a real followers response decodes`() {
        val response = json.decodeFromString<GetFollowersResponse>(followersFixture)

        assertEquals(5, response.totalCount)
        assertEquals(2, response.items?.size)
        assertNotNull("cursor drives pagination", response.cursor)

        val first = response.items!!.first()
        assertEquals("did:plc:bcgltzqazw5tb6k2g3ttenbj", first.did)
        assertEquals("chadtmiller.com", first.handle)
        assertEquals("chad", first.displayName)
        assertTrue(first.avatar!!.startsWith("https://"))
    }

    @Test
    fun `a member with no display name or bio still decodes`() {
        // The appview omits these rather than sending null, which is the case
        // an over-strict model would choke on.
        val sparse = """{"items":[{"did":"did:plc:sparse","handle":"a.test"}]}"""
        val response = json.decodeFromString<GetFollowersResponse>(sparse)

        val member = response.items!!.single()
        assertEquals("did:plc:sparse", member.did)
        assertNull(member.displayName)
        assertNull(member.description)
        assertNull(member.avatar)
        assertNull("no cursor means no further pages", response.cursor)
    }

    @Test
    fun `an empty favorites list decodes rather than failing`() {
        val response = json.decodeFromString<GetGalleryFavoritesResponse>(
            """{"totalCount":0,"items":[]}""",
        )
        assertEquals(0, response.totalCount)
        assertTrue(response.items!!.isEmpty())
    }

    @Test
    fun `known followers has no cursor envelope`() {
        val response = json.decodeFromString<GetKnownFollowersResponse>(
            """{"items":[{"did":"did:plc:known","handle":"b.test","displayName":"Known"}]}""",
        )
        assertEquals("Known", response.items!!.single().displayName)
    }

    @Test
    fun `a response with unknown fields still decodes`() {
        // The appview adds fields ahead of clients; a strict decoder would fail
        // the whole list on one new key.
        val future = """{"items":[{"did":"did:plc:x","handle":"c.test","somethingNew":42}],"newTopLevel":true}"""
        val response = json.decodeFromString<GetFollowersResponse>(future)
        assertEquals("c.test", response.items!!.single().handle)
    }
}