feat(t213): complete live SKU price verification

This commit is contained in:
QiuSW
2026-07-28 11:20:12 +08:00
parent 73b00b4063
commit 4c3b962dde
15 changed files with 251 additions and 46 deletions
@@ -202,6 +202,18 @@ class PinduoduoPageClassifierTest {
assertNull(snapshot.safetyStopReason)
}
@Test
fun `modern detail footer accepts short customer service label`() {
val snapshot = classify(
element(contentDescription = "返回", clickable = true),
element(contentDescription = "客服", clickable = true),
element(contentDescription = "收藏", clickable = true)
)
assertEquals(PinduoduoPage.PRODUCT_DETAIL, snapshot.page)
assertNull(snapshot.safetyStopReason)
}
@Test
fun `specification panel requires color size and options`() {
val snapshot = classify(
@@ -106,6 +106,31 @@ class PinduoduoSpecificationParserTest {
assertFalse(evidence.priceAmbiguous)
}
@Test
fun `reads allowlisted first unit price and ignores comparison price`() {
val evidence = PinduoduoSpecificationParser.parse(
listOf(
element("首件¥16.7", top = 20),
element("大促价¥20.7", top = 30),
element("已选择:XL码/裙长90胸围89 红色", top = 40),
element("颜色分类", top = 100),
element("红色", top = 140, clickable = true, selected = true),
element("尺码", top = 240),
element(
"XL码/裙长90胸围89",
top = 280,
clickable = true,
selected = true
)
)
)
requireNotNull(evidence)
assertEquals(1670L, evidence.price?.cents)
assertEquals("首件¥16.7", evidence.price?.rawText)
assertFalse(evidence.priceAmbiguous)
}
@Test
fun `multiple explicit prices are ambiguous`() {
val evidence = PinduoduoSpecificationParser.parse(
@@ -0,0 +1,38 @@
package com.roubao.autopilot.procurement
import org.junit.Assert.assertEquals
import org.junit.Test
class ClaimReconciliationPolicyTest {
@Test
fun `restores only when local and server task identities match`() {
assertEquals(
ClaimReconciliationDecision.RESTORE,
reconcileClaim("task-1", "task-1")
)
}
@Test
fun `discards local task when backend has ended or replaced it`() {
assertEquals(
ClaimReconciliationDecision.DISCARD_LOCAL,
reconcileClaim("task-1", null)
)
assertEquals(
ClaimReconciliationDecision.DISCARD_LOCAL,
reconcileClaim("task-1", "task-2")
)
}
@Test
fun `continues claim or replay when no local task is materialized`() {
assertEquals(
ClaimReconciliationDecision.CLAIM_OR_REPLAY,
reconcileClaim(null, null)
)
assertEquals(
ClaimReconciliationDecision.CLAIM_OR_REPLAY,
reconcileClaim(null, "task-1")
)
}
}