跳转至

apps\online\src\test\java\cn\nfarms\online\DemoTicketApiTest.kt

源码路径:apps\online\src\test\java\cn\nfarms\online\DemoTicketApiTest.kt · 98 行

package cn.nfarms.online

import cn.nfarms.core.store.AppFiles
import cn.nfarms.online.data.ConflictException
import cn.nfarms.online.data.DemoTicketApi
import cn.nfarms.online.data.Merge
import cn.nfarms.online.data.MergeResult
import java.io.File
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

/**
 * 演示后端 + 三路合并的联动。
 *
 * 这里测的是**真后端行为**:乐观锁(rev)拒绝旧版本提交、同一单被"别人"改过之后
 * 必须判冲突。它们是「冲突合并」能不能正常工作的前提 ——
 * 只有合并函数对、但后端接受旧版本覆盖,这套逻辑等于不存在。
 */
class DemoTicketApiTest {

    private lateinit var api: DemoTicketApi
    private lateinit var tmp: File

    @Before
    fun setUp() {
        api = DemoTicketApi()
        tmp = File(System.getProperty("java.io.tmpdir"), "nfarms-online-${System.nanoTime()}")
        tmp.mkdirs()
    }

    @After
    fun tearDown() { tmp.deleteRecursively() }

    @Test
    fun push_with_stale_rev_is_rejected() = runBlocking {
        val remote = api.list().first { it.id == "T-1001" }
        val stale = remote.copy(rev = 1, title = "我的旧版本")
        val failed = runCatching { api.push(stale, baseRev = 1) }
        assertTrue("用旧 rev 提交必须被拒(乐观锁)", failed.exceptionOrNull() is ConflictException)
        // 拒绝时要把**当前**远端值抛回去,否则客户端没法做三路合并。
        // 这里不写死 rev 数字:种子数据的 rev 会随演示数据调整,写死只会让测试变脆。
        assertEquals(
            remote.rev,
            (failed.exceptionOrNull() as ConflictException).remote.rev,
        )
    }

    @Test
    fun push_with_current_rev_succeeds_and_bumps_rev() = runBlocking {
        val remote = api.list().first { it.id == "T-1001" }
        val ok = api.push(remote.copy(title = "改好了"), baseRev = remote.rev)
        assertEquals(remote.rev + 1, ok.rev)
        assertEquals("改好了", ok.title)
    }

    @Test
    fun remote_edit_creates_a_real_conflict_against_local_edit() = runBlocking {
        val base = api.list().first { it.id == "T-1001" }
        // 本地改
        val local = base.copy(title = "本地改的")
        // 别人也改
        api.simulateRemoteEdit(base.id, "远端改的")
        val remote = api.list().first { it.id == base.id }

        val merged = Merge.merge(local, remote, base)
        assertTrue("两边都改 → 必须判冲突", merged is MergeResult.Conflict)
    }

    @Test
    fun pushing_a_new_ticket_creates_it_with_rev_one() = runBlocking {
        val draft = cn.nfarms.online.data.Ticket(
            id = "D-1", title = "新工单", body = "描述", status = "open",
            updatedAt = System.currentTimeMillis(), rev = 0,
        )
        val created = api.push(draft, baseRev = 0)
        assertEquals(1, created.rev)
        assertNotNull(api.revOf("D-1"))
    }

    /** 附件校验只依赖文件本身,不依赖 Android;这里顺带验证大小边界。 */
    @Test
    fun attachment_size_bounds() {
        fun ok(size: Long) = cn.nfarms.online.data.Attachment("A", "f", size, "x").sizeOk
        assertTrue(ok(1))
        assertTrue(ok(10L * 1024 * 1024))
        assertTrue(!ok(0))
        assertTrue(!ok(10L * 1024 * 1024 + 1))
    }

    @Suppress("unused")
    private fun filesForTest(): AppFiles = AppFiles.at(tmp, tmp, "online")
}