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

grain.social/grain-android

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

import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import social.grain.data.api.ReportSubject
import social.grain.data.model.GrainLabels
import social.grain.feature.moderation.ReportViewModel
import social.grain.support.Harness
import social.grain.support.MainDispatcherRule
import social.grain.support.awaitCall
import social.grain.support.awaitValue
import social.grain.support.settle

/**
 * The report sheet.
 *
 * The categories are the labels grain gives out, and a report names the
 * `reasonType` grain-serve files under the label picked. Reopening the sheet
 * has to start blank, or the second report on the same gallery opens onto the
 * first one's receipt.
 */
@RunWith(AndroidJUnit4::class)
class ReportViewModelTest {

    @get:Rule
    val mainDispatcher = MainDispatcherRule()

    private lateinit var harness: Harness

    private val subject = ReportSubject.Record("at://did:plc:alice/social.grain.gallery/g1", "cid1")

    @Before
    fun setUp() {
        harness = Harness()
    }

    @After
    fun tearDown() = harness.tearDown()

    private fun viewModel() = ReportViewModel(harness.client, harness.auth, harness.labelDefinitions)

    @Test
    fun openingTheSheetListsGrainsLabelsAndPreSelectsTheFirst() {
        val model = viewModel()
        model.start(subject)

        val state = model.state.value
        assertEquals(GrainLabels.definitions.map { it.identifier }, state.categories.map { it.identifier })
        assertEquals("copyright", state.selected)
        assertTrue(state.canSubmit)
    }

    @Test
    fun theSheetListsTheDefinitionsTheServerDescribes() {
        harness.server.on(
            "social.grain.moderation.describeLabels",
            """{"policies":{"labelValues":["!takedown","spam","gore"],"labelValueDefinitions":[
                {"identifier":"spam","severity":"inform","blurs":"none","defaultSetting":"warn",
                 "locales":[{"lang":"en","name":"Spam","description":"Unwanted."}]},
                {"identifier":"gore","severity":"alert","blurs":"media","defaultSetting":"warn",
                 "locales":[{"lang":"en","name":"Gore","description":"Blood."}]}]}}""",
        )
        val model = viewModel()
        model.start(subject)

        val state = model.state.awaitValue("the described categories") { it.categories.size == 2 }
        assertEquals(listOf("spam", "gore"), state.categories.map { it.identifier })
        assertEquals("spam", state.selected)
    }

    @Test
    fun aFailedDescribeLeavesTheBundledCategories() {
        harness.server.on("social.grain.moderation.describeLabels", """{"error":"InternalServerError"}""", code = 500)
        val model = viewModel()
        model.start(subject)
        harness.server.awaitCall("social.grain.moderation.describeLabels")
        Thread.sleep(200)

        assertEquals(GrainLabels.definitions, model.state.value.categories)
        assertEquals("copyright", model.state.value.selected)
    }

    @Test
    fun reopeningTheSheetClearsTheReceiptFromLastTime() {
        val model = viewModel()
        model.start(subject)
        model.submit()
        model.state.awaitValue("the receipt") { it.isSubmitted }

        model.start(subject)

        assertFalse("A fresh report doesn't open onto the last one", model.state.value.isSubmitted)
    }

    @Test
    fun submittingSendsTheChosenLabelAndTheTypedReason() {
        harness.server.on("com.atproto.moderation.createReport", """{"id":1}""")
        val model = viewModel()
        model.start(subject)

        model.select("gore")
        model.reasonState.setTextAndPlaceCursorAtEnd("  Not okay  ")
        model.submit()

        model.state.awaitValue("the receipt") { it.isSubmitted }
        val call = harness.server.awaitCall("com.atproto.moderation.createReport")
        val body = call.body
        assertEquals(
            "tools.ozone.report.defs#reasonViolenceGraphicContent",
            call.json["reasonType"]!!.jsonPrimitive.content,
        )
        assertEquals(
            "The PDS forwards the report to grain-serve",
            "did:web:stub.grain.test#grain_appview",
            call.headers["atproto-proxy"],
        )
        assertTrue("Whitespace is trimmed off the reason", body.contains("\"reason\":\"Not okay\""))
        assertTrue(body.contains("com.atproto.repo.strongRef"))
    }

    @Test
    fun anEmptyReasonIsLeftOutEntirely() {
        harness.server.on("com.atproto.moderation.createReport", """{"id":1}""")
        val model = viewModel()
        model.start(subject)

        model.submit()

        model.state.awaitValue("the receipt") { it.isSubmitted }
        assertNull(harness.server.awaitCall("com.atproto.moderation.createReport").json["reason"])
    }

    @Test
    fun reportingAnAccountSendsARepoRef() {
        harness.server.on("com.atproto.moderation.createReport", """{"id":1}""")
        val model = viewModel()
        model.start(ReportSubject.Account("did:plc:bob"))

        model.submit()

        model.state.awaitValue("the receipt") { it.isSubmitted }
        val subject = harness.server.awaitCall("com.atproto.moderation.createReport").json["subject"]!!.jsonObject
        assertEquals("com.atproto.admin.defs#repoRef", subject["\$type"]!!.jsonPrimitive.content)
        assertEquals("did:plc:bob", subject["did"]!!.jsonPrimitive.content)
    }

    @Test
    fun aSubmitThatFailsSaysSoAndLeavesTheFormUsable() {
        harness.server.on("com.atproto.moderation.createReport", """{"error":"no"}""", code = 500)
        val model = viewModel()
        model.start(subject)

        model.submit()

        val state = model.state.awaitValue("the error") { it.errorMessage != null }
        assertEquals("Couldn't submit that report. Try again.", state.errorMessage)
        assertFalse(state.isSubmitted)
        assertTrue(state.canSubmit)
    }

    @Test
    fun submittingBeforeTheSheetHasASubjectDoesNothing() {
        val model = viewModel()
        model.submit()
        settle()
        assertEquals(0, harness.server.countOf("com.atproto.moderation.createReport"))
    }
}