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

grain.social/grain-android

1 collaborator
0
1
main
141 lines · 6.4 KB Raw History
package social.grain

import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import social.grain.data.api.ProfileSeed
import social.grain.data.api.ProfileSeed.Seeded
import social.grain.support.StubServer
import social.grain.support.testAuth
import social.grain.support.xrpc

/**
 * A grain profile made for the account at sign-in: left alone once it has a
 * name, made or put over from the Bluesky profile, made bare with neither, and
 * an unnamed one left with no Bluesky profile.
 */
@RunWith(AndroidJUnit4::class)
class ProfileSeedTest {

    private val server = StubServer()
    private val client = server.xrpc()
    private val auth = testAuth()

    private val avatar =
        """{"${'$'}type":"blob","ref":{"${'$'}link":"bafk"},"mimeType":"image/png","size":9}"""
    private val notFound = """{"error":"RecordNotFound","message":"Could not locate record"}"""

    @Test
    fun aNamedGrainProfileIsLeftAlone() = runTest {
        profileReads(grain = """{"uri":"at://did:plc:me/social.grain.actor.profile/self","cid":"c1","value":{"displayName":"Me"}}""", bsky = BSKY)

        assertEquals(Seeded.Named, ProfileSeed.seed(client, auth))
        assertEquals(1, server.countOf("com.atproto.repo.getRecord"))
        assertEquals(0, server.countOf("com.atproto.repo.putRecord"))
        assertEquals(0, server.countOf("com.atproto.repo.createRecord"))
    }

    @Test
    fun withNeitherProfileABareOneIsMadeAtSelfAndIndexed() = runTest {
        profileReads(grain = null, bsky = null)
        server.on("com.atproto.repo.createRecord", """{"uri":"at://did:plc:me/social.grain.actor.profile/self","cid":"c2"}""")

        val seeded = ProfileSeed.seed(client, auth)

        assertEquals(Seeded.Made("at://did:plc:me/social.grain.actor.profile/self"), seeded)
        val sent = server.lastCall("com.atproto.repo.createRecord").json
        assertEquals("self", sent["rkey"]!!.jsonPrimitive.content)
        assertEquals(ProfileSeed.GRAIN, sent["collection"]!!.jsonPrimitive.content)
        val record = sent["record"]!!.jsonObject
        assertEquals(setOf("\$type", "createdAt"), record.keys)
        assertEquals(ProfileSeed.GRAIN, record["\$type"]!!.jsonPrimitive.content)
        assertTrue(record["createdAt"]!!.jsonPrimitive.content.endsWith("Z"))
        assertEquals(0, server.countOf("com.atproto.repo.putRecord"))
        assertTrue(server.lastCall("social.grain.sync.indexRecords").body.contains("social.grain.actor.profile/self"))
    }

    @Test
    fun withNoBlueskyProfileAnUnnamedGrainProfileIsLeftAlone() = runTest {
        profileReads(
            grain = """{"uri":"at://did:plc:me/social.grain.actor.profile/self","cid":"c1",""" +
                """"value":{"createdAt":"2025-01-01T00:00:00.000Z"}}""",
            bsky = null,
        )

        assertEquals(Seeded.NoBluesky, ProfileSeed.seed(client, auth))
        assertEquals(0, server.countOf("com.atproto.repo.putRecord"))
        assertEquals(0, server.countOf("com.atproto.repo.createRecord"))
    }

    @Test
    fun withNoGrainProfileOneIsMadeAtSelfAndIndexed() = runTest {
        profileReads(grain = null, bsky = BSKY)
        server.on("com.atproto.repo.createRecord", """{"uri":"at://did:plc:me/social.grain.actor.profile/self","cid":"c2"}""")

        val seeded = ProfileSeed.seed(client, auth)

        assertEquals(Seeded.Made("at://did:plc:me/social.grain.actor.profile/self"), seeded)
        val sent = server.lastCall("com.atproto.repo.createRecord").json
        assertEquals("self", sent["rkey"]!!.jsonPrimitive.content)
        assertEquals(ProfileSeed.GRAIN, sent["collection"]!!.jsonPrimitive.content)
        val record = sent["record"]!!.jsonObject
        assertEquals(ProfileSeed.GRAIN, record["\$type"]!!.jsonPrimitive.content)
        assertEquals("Alice", record["displayName"]!!.jsonPrimitive.content)
        assertNull("An empty description is left out", record["description"])
        assertNull("Only the name, description and avatar are taken", record["banner"])
        assertTrue(record["createdAt"]!!.jsonPrimitive.content.endsWith("Z"))
        assertEquals(0, server.countOf("com.atproto.repo.putRecord"))
        assertTrue(server.lastCall("social.grain.sync.indexRecords").body.contains("social.grain.actor.profile/self"))
    }

    @Test
    fun anUnnamedGrainProfileIsPutOverOnItsCidKeepingWhenItWasMade() = runTest {
        profileReads(
            grain = """{"uri":"at://did:plc:me/social.grain.actor.profile/self","cid":"c1",""" +
                """"value":{"displayName":"","createdAt":"2025-01-01T00:00:00.000Z","description":"old"}}""",
            bsky = BSKY,
        )

        val seeded = ProfileSeed.seed(client, auth)

        assertTrue(seeded is Seeded.Put)
        val sent = server.lastCall("com.atproto.repo.putRecord").json
        assertEquals("c1", sent["swapRecord"]!!.jsonPrimitive.content)
        assertEquals("self", sent["rkey"]!!.jsonPrimitive.content)
        val record = sent["record"]!!.jsonObject
        assertEquals("2025-01-01T00:00:00.000Z", record["createdAt"]!!.jsonPrimitive.content)
        assertNull("The Bluesky profile's empty description does not keep the old one", record["description"])
        assertEquals(0, server.countOf("com.atproto.repo.createRecord"))
    }

    @Test
    fun theAvatarIsTheBlueskyBlobAsItIs() = runTest {
        profileReads(grain = null, bsky = BSKY)

        ProfileSeed.seed(client, auth)

        val record = server.lastCall("com.atproto.repo.createRecord").json["record"]!!.jsonObject
        assertEquals(
            kotlinx.serialization.json.Json.parseToJsonElement(avatar),
            record["avatar"],
        )
    }

    private val BSKY: String
        get() = """{"uri":"at://did:plc:me/app.bsky.actor.profile/self","cid":"b1",""" +
            """"value":{"displayName":"Alice","description":"","avatar":$avatar,"banner":$avatar}}"""

    /** Answer the PDS's profile reads: [grain] and [bsky] as getRecord answers, or not found. */
    private fun profileReads(grain: String?, bsky: String?) {
        server.answer("com.atproto.repo.getRecord") { call ->
            val record = if (call.params["collection"] == ProfileSeed.GRAIN) grain else bsky
            if (record != null) 200 to record else 400 to notFound
        }
    }
}