feat(t213): verify selected SKU combination price
This commit is contained in:
+270
-22
@@ -157,7 +157,7 @@ class PinduoduoCandidateAutomationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `missing safe specification entry blocks without purchase fallback`() =
|
||||
fun `unsupported specification entry skips candidate safely`() =
|
||||
runTest {
|
||||
val driver = FakeCandidateDriver(
|
||||
cardPages = listOf(listOf(card("a"))),
|
||||
@@ -174,13 +174,175 @@ class PinduoduoCandidateAutomationTest {
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
AutomationResult.Blocked(SafetyStopReason.UNKNOWN_PAGE),
|
||||
AutomationResult.FatalFailure(
|
||||
WorkflowFailureCode.TARGET_NOT_READY
|
||||
),
|
||||
result
|
||||
)
|
||||
assertEquals(1, driver.openSpecificationCalls)
|
||||
assertEquals(1, driver.returnToResultsCalls)
|
||||
assertTrue(driver.savedEvidence.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selects target color then size and captures verified price`() =
|
||||
runTest {
|
||||
val driver = FakeCandidateDriver(
|
||||
cardPages = listOf(listOf(card("a")))
|
||||
)
|
||||
val automation = PinduoduoCandidateAutomation(
|
||||
driver = driver,
|
||||
specificationTarget = PinduoduoSpecificationTarget(
|
||||
color = "BLACK",
|
||||
size = "L"
|
||||
),
|
||||
maxCandidates = 1,
|
||||
pagePollIntervalMillis = 1
|
||||
)
|
||||
automation.reset()
|
||||
|
||||
val result = automation.execute(
|
||||
PinduoduoCandidateWorkflow.steps().last()
|
||||
)
|
||||
|
||||
assertEquals(AutomationResult.Success, result)
|
||||
assertEquals(
|
||||
listOf(
|
||||
PinduoduoSpecificationGroupKind.COLOR to "黑色",
|
||||
PinduoduoSpecificationGroupKind.SIZE to "L"
|
||||
),
|
||||
driver.selectedOptions
|
||||
)
|
||||
val specification = driver.savedEvidence.single().specification
|
||||
assertEquals("已选择:黑色 L", specification.selectedSummary)
|
||||
assertEquals(1090L, specification.price?.cents)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `direct order confirmation is exited without candidate save`() =
|
||||
runTest {
|
||||
val driver = FakeCandidateDriver(
|
||||
cardPages = listOf(listOf(card("a"))),
|
||||
openOrderConfirmation = true
|
||||
)
|
||||
val automation = PinduoduoCandidateAutomation(
|
||||
driver = driver,
|
||||
maxCandidates = 1,
|
||||
pagePollIntervalMillis = 1
|
||||
)
|
||||
automation.reset()
|
||||
|
||||
val result = automation.execute(
|
||||
PinduoduoCandidateWorkflow.steps().last()
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
AutomationResult.FatalFailure(
|
||||
WorkflowFailureCode.TARGET_NOT_READY
|
||||
),
|
||||
result
|
||||
)
|
||||
assertEquals(1, driver.returnFromOrderConfirmationCalls)
|
||||
assertTrue(driver.savedEvidence.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bounded specification scroll exposes target size`() = runTest {
|
||||
val driver = FakeCandidateDriver(
|
||||
cardPages = listOf(listOf(card("a"))),
|
||||
sizeVisibleAfterSpecificationScroll = true
|
||||
)
|
||||
val automation = PinduoduoCandidateAutomation(
|
||||
driver = driver,
|
||||
specificationTarget = PinduoduoSpecificationTarget(
|
||||
color = "BLACK",
|
||||
size = "L"
|
||||
),
|
||||
maxCandidates = 1,
|
||||
pagePollIntervalMillis = 1
|
||||
)
|
||||
automation.reset()
|
||||
|
||||
val result = automation.execute(
|
||||
PinduoduoCandidateWorkflow.steps().last()
|
||||
)
|
||||
|
||||
assertEquals(AutomationResult.Success, result)
|
||||
assertEquals(1, driver.specificationScrollCalls)
|
||||
assertEquals(
|
||||
PinduoduoSpecificationGroupKind.SIZE to "L",
|
||||
driver.selectedOptions.last()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unchanged selection state is captured but not continued`() = runTest {
|
||||
val driver = FakeCandidateDriver(
|
||||
cardPages = listOf(listOf(card("a"))),
|
||||
ignoreSpecificationSelection = true
|
||||
)
|
||||
val automation = PinduoduoCandidateAutomation(
|
||||
driver = driver,
|
||||
specificationTarget = PinduoduoSpecificationTarget(
|
||||
color = "BLACK",
|
||||
size = "L"
|
||||
),
|
||||
maxCandidates = 1,
|
||||
pagePollIntervalMillis = 1
|
||||
)
|
||||
automation.reset()
|
||||
|
||||
val result = automation.execute(
|
||||
PinduoduoCandidateWorkflow.steps().last()
|
||||
)
|
||||
|
||||
assertEquals(AutomationResult.Success, result)
|
||||
assertEquals(
|
||||
listOf(PinduoduoSpecificationGroupKind.COLOR to "黑色"),
|
||||
driver.selectedOptions
|
||||
)
|
||||
assertTrue(
|
||||
driver.savedEvidence.single().specification.groups
|
||||
.single {
|
||||
it.kind == PinduoduoSpecificationGroupKind.COLOR
|
||||
}
|
||||
.options
|
||||
.none { it.selected }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `missing target never exceeds specification scroll budget`() =
|
||||
runTest {
|
||||
val driver = FakeCandidateDriver(
|
||||
cardPages = listOf(listOf(card("a"))),
|
||||
hideSizeAlways = true
|
||||
)
|
||||
val automation = PinduoduoCandidateAutomation(
|
||||
driver = driver,
|
||||
specificationTarget = PinduoduoSpecificationTarget(
|
||||
color = "BLACK",
|
||||
size = "L"
|
||||
),
|
||||
maxCandidates = 1,
|
||||
pagePollIntervalMillis = 1
|
||||
)
|
||||
automation.reset()
|
||||
|
||||
val result = automation.execute(
|
||||
PinduoduoCandidateWorkflow.steps().last()
|
||||
)
|
||||
|
||||
assertEquals(AutomationResult.Success, result)
|
||||
assertEquals(2, driver.specificationScrollCalls)
|
||||
assertEquals(
|
||||
setOf(PinduoduoSpecificationGroupKind.COLOR),
|
||||
driver.savedEvidence.single().specification.groups
|
||||
.map { it.kind }
|
||||
.toSet()
|
||||
)
|
||||
}
|
||||
|
||||
private fun card(signature: String) = PinduoduoCandidateCard(
|
||||
signature = signature,
|
||||
semanticTextCount = 3,
|
||||
@@ -192,6 +354,10 @@ class PinduoduoCandidateAutomationTest {
|
||||
private val safetyStopReason: SafetyStopReason? = null,
|
||||
private val failCapture: Boolean = false,
|
||||
private val failOpenSpecifications: Boolean = false,
|
||||
private val openOrderConfirmation: Boolean = false,
|
||||
private val sizeVisibleAfterSpecificationScroll: Boolean = false,
|
||||
private val hideSizeAlways: Boolean = false,
|
||||
private val ignoreSpecificationSelection: Boolean = false,
|
||||
private val transitionDelaySnapshots: Int = 0
|
||||
) : PinduoduoCandidateDriver {
|
||||
private var cardPageIndex = 0
|
||||
@@ -203,6 +369,13 @@ class PinduoduoCandidateAutomationTest {
|
||||
val savedEvidence = mutableListOf<PinduoduoCandidateEvidence>()
|
||||
var scrollCalls = 0
|
||||
var openSpecificationCalls = 0
|
||||
var returnToResultsCalls = 0
|
||||
var returnFromOrderConfirmationCalls = 0
|
||||
var specificationScrollCalls = 0
|
||||
val selectedOptions =
|
||||
mutableListOf<Pair<PinduoduoSpecificationGroupKind, String>>()
|
||||
private val effectiveSelections =
|
||||
mutableListOf<Pair<PinduoduoSpecificationGroupKind, String>>()
|
||||
|
||||
override suspend fun snapshot(): PinduoduoUiSnapshot {
|
||||
if (pendingPage != null) {
|
||||
@@ -217,6 +390,9 @@ class PinduoduoCandidateAutomationTest {
|
||||
foregroundPackage = PINDUODUO_PACKAGE,
|
||||
page = page,
|
||||
safetyStopReason = safetyStopReason
|
||||
?: SafetyStopReason.PAYMENT_BOUNDARY.takeIf {
|
||||
page == PinduoduoPage.ORDER_CONFIRMATION
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -250,14 +426,41 @@ class PinduoduoCandidateAutomationTest {
|
||||
if (failOpenSpecifications) {
|
||||
return false
|
||||
}
|
||||
transitionTo(PinduoduoPage.SPECIFICATION_PANEL)
|
||||
transitionTo(
|
||||
if (openOrderConfirmation) {
|
||||
PinduoduoPage.ORDER_CONFIRMATION
|
||||
} else {
|
||||
PinduoduoPage.SPECIFICATION_PANEL
|
||||
}
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun captureSpecifications():
|
||||
override suspend fun readSpecifications():
|
||||
PinduoduoSpecificationEvidence = specification()
|
||||
|
||||
override suspend fun selectSpecificationOption(
|
||||
kind: PinduoduoSpecificationGroupKind,
|
||||
optionText: String
|
||||
): Boolean {
|
||||
selectedOptions += kind to optionText
|
||||
if (!ignoreSpecificationSelection) {
|
||||
effectiveSelections += kind to optionText
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun scrollSpecifications(): Boolean {
|
||||
specificationScrollCalls += 1
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun captureSpecifications(
|
||||
evidence: PinduoduoSpecificationEvidence
|
||||
):
|
||||
PinduoduoSpecificationCapture =
|
||||
PinduoduoSpecificationCapture(
|
||||
evidence = specification(),
|
||||
evidence = evidence,
|
||||
screenshot = PinduoduoScreenshotCapture(
|
||||
pngBytes = byteArrayOf(2),
|
||||
width = 1080,
|
||||
@@ -270,6 +473,12 @@ class PinduoduoCandidateAutomationTest {
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun returnFromOrderConfirmation(): Boolean {
|
||||
returnFromOrderConfirmationCalls += 1
|
||||
transitionTo(PinduoduoPage.PRODUCT_DETAIL)
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun saveCandidate(
|
||||
ordinal: Int,
|
||||
card: PinduoduoCandidateCard,
|
||||
@@ -289,6 +498,7 @@ class PinduoduoCandidateAutomationTest {
|
||||
}
|
||||
|
||||
override suspend fun returnToResults(): Boolean {
|
||||
returnToResultsCalls += 1
|
||||
transitionTo(PinduoduoPage.SEARCH_RESULTS)
|
||||
return true
|
||||
}
|
||||
@@ -306,6 +516,11 @@ class PinduoduoCandidateAutomationTest {
|
||||
savedEvidence.clear()
|
||||
scrollCalls = 0
|
||||
openSpecificationCalls = 0
|
||||
returnToResultsCalls = 0
|
||||
returnFromOrderConfirmationCalls = 0
|
||||
specificationScrollCalls = 0
|
||||
selectedOptions.clear()
|
||||
effectiveSelections.clear()
|
||||
cardPageIndex = 0
|
||||
page = PinduoduoPage.SEARCH_RESULTS
|
||||
pendingPage = null
|
||||
@@ -335,24 +550,57 @@ class PinduoduoCandidateAutomationTest {
|
||||
private fun specification() = PinduoduoSpecificationEvidence(
|
||||
signature = "specification",
|
||||
semanticTextCount = 6,
|
||||
groups = listOf(
|
||||
PinduoduoSpecificationGroup(
|
||||
kind = PinduoduoSpecificationGroupKind.COLOR,
|
||||
title = "颜色分类",
|
||||
options = listOf(
|
||||
PinduoduoSpecificationOption("黑色", false, true)
|
||||
),
|
||||
complete = true
|
||||
),
|
||||
PinduoduoSpecificationGroup(
|
||||
kind = PinduoduoSpecificationGroupKind.SIZE,
|
||||
title = "尺码",
|
||||
options = listOf(
|
||||
PinduoduoSpecificationOption("L", false, true)
|
||||
),
|
||||
complete = true
|
||||
groups = buildList {
|
||||
add(
|
||||
PinduoduoSpecificationGroup(
|
||||
kind = PinduoduoSpecificationGroupKind.COLOR,
|
||||
title = "颜色分类",
|
||||
options = listOf(
|
||||
PinduoduoSpecificationOption(
|
||||
"黑色",
|
||||
effectiveSelections.any {
|
||||
it.first ==
|
||||
PinduoduoSpecificationGroupKind.COLOR
|
||||
},
|
||||
true
|
||||
)
|
||||
),
|
||||
complete = true
|
||||
)
|
||||
)
|
||||
)
|
||||
if (
|
||||
!hideSizeAlways &&
|
||||
(
|
||||
!sizeVisibleAfterSpecificationScroll ||
|
||||
specificationScrollCalls > 0
|
||||
)
|
||||
) {
|
||||
add(
|
||||
PinduoduoSpecificationGroup(
|
||||
kind = PinduoduoSpecificationGroupKind.SIZE,
|
||||
title = "尺码",
|
||||
options = listOf(
|
||||
PinduoduoSpecificationOption(
|
||||
"L",
|
||||
selected = effectiveSelections.any {
|
||||
it.first ==
|
||||
PinduoduoSpecificationGroupKind.SIZE
|
||||
},
|
||||
enabled = true
|
||||
)
|
||||
),
|
||||
complete = true
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
selectedSummary = effectiveSelections
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.joinToString(
|
||||
prefix = "已选择:",
|
||||
separator = " "
|
||||
) { it.second },
|
||||
price = PinduoduoSpecificationPrice("¥10.9", 1090)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+43
-1
@@ -156,7 +156,7 @@ class PinduoduoPageClassifierTest {
|
||||
|
||||
@Test
|
||||
fun `payment boundary blocks even on otherwise unknown page`() {
|
||||
val snapshot = classify(element(text = "确认订单"))
|
||||
val snapshot = classify(element(text = "立即支付"))
|
||||
|
||||
assertEquals(PinduoduoPage.UNKNOWN, snapshot.page)
|
||||
assertEquals(SafetyStopReason.PAYMENT_BOUNDARY, snapshot.safetyStopReason)
|
||||
@@ -215,6 +215,48 @@ class PinduoduoPageClassifierTest {
|
||||
assertNull(snapshot.safetyStopReason)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `stable specification controls survive scrolled option groups`() {
|
||||
val snapshot = classify(
|
||||
element(text = "确认款式"),
|
||||
element(text = "已选择:白色 2XL"),
|
||||
element(contentDescription = "关闭", clickable = true),
|
||||
element(text = "确定", clickable = true),
|
||||
element(text = "尺码"),
|
||||
element(text = "2XL", clickable = true)
|
||||
)
|
||||
|
||||
assertEquals(PinduoduoPage.SPECIFICATION_PANEL, snapshot.page)
|
||||
assertNull(snapshot.safetyStopReason)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `order confirmation is classified and remains payment blocked`() {
|
||||
val snapshot = classify(
|
||||
element(text = "确认订单"),
|
||||
element(text = "提交订单", clickable = true)
|
||||
)
|
||||
|
||||
assertEquals(PinduoduoPage.ORDER_CONFIRMATION, snapshot.page)
|
||||
assertEquals(
|
||||
SafetyStopReason.PAYMENT_BOUNDARY,
|
||||
snapshot.safetyStopReason
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `order list uses stable status tabs`() {
|
||||
val snapshot = classify(
|
||||
element(text = "我的订单"),
|
||||
element(text = "待付款"),
|
||||
element(text = "待发货"),
|
||||
element(text = "待收货")
|
||||
)
|
||||
|
||||
assertEquals(PinduoduoPage.ORDER_LIST, snapshot.page)
|
||||
assertNull(snapshot.safetyStopReason)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cart and order pages are never classified as product detail`() {
|
||||
val cart = classify(
|
||||
|
||||
+164
-3
@@ -36,7 +36,7 @@ class PinduoduoSpecificationParserTest {
|
||||
listOf(
|
||||
element("颜色分类", top = 100),
|
||||
element("黑色", top = 140, clickable = true),
|
||||
element("", top = 150, scrollable = true),
|
||||
element("", top = 50, bottom = 400, scrollable = true),
|
||||
element("尺码", top = 240),
|
||||
element("L", top = 280, clickable = true)
|
||||
)
|
||||
@@ -44,7 +44,7 @@ class PinduoduoSpecificationParserTest {
|
||||
|
||||
requireNotNull(evidence)
|
||||
assertFalse(evidence.groups.first().complete)
|
||||
assertTrue(evidence.groups.last().complete)
|
||||
assertFalse(evidence.groups.last().complete)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -75,9 +75,169 @@ class PinduoduoSpecificationParserTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reads selected combination and strict cny price`() {
|
||||
val evidence = PinduoduoSpecificationParser.parse(
|
||||
listOf(
|
||||
element("确认款式", top = 10, clickable = true),
|
||||
element("¥ 10.9", top = 20),
|
||||
element("2件9.5折", top = 30),
|
||||
element("已选择: 白色 2XL 建议145-160斤", top = 40),
|
||||
element("颜色分类", top = 100),
|
||||
element("白色", top = 140, clickable = true, selected = true),
|
||||
element("尺码", top = 240),
|
||||
element(
|
||||
"2XL 建议145-160斤",
|
||||
top = 280,
|
||||
clickable = true,
|
||||
selected = true
|
||||
),
|
||||
element("确定", top = 500, clickable = true)
|
||||
)
|
||||
)
|
||||
|
||||
requireNotNull(evidence)
|
||||
assertEquals(
|
||||
"已选择: 白色 2XL 建议145-160斤",
|
||||
evidence.selectedSummary
|
||||
)
|
||||
assertEquals(1090L, evidence.price?.cents)
|
||||
assertEquals("¥ 10.9", evidence.price?.rawText)
|
||||
assertFalse(evidence.priceAmbiguous)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple explicit prices are ambiguous`() {
|
||||
val evidence = PinduoduoSpecificationParser.parse(
|
||||
listOf(
|
||||
element("¥10.9", top = 20),
|
||||
element("¥12", top = 30),
|
||||
element("颜色分类", top = 100),
|
||||
element("黑色", top = 140, clickable = true)
|
||||
)
|
||||
)
|
||||
|
||||
requireNotNull(evidence)
|
||||
assertNull(evidence.price)
|
||||
assertTrue(evidence.priceAmbiguous)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `range and conditional prices do not become combination price`() {
|
||||
val evidence = PinduoduoSpecificationParser.parse(
|
||||
listOf(
|
||||
element("¥10.9-¥12.9", top = 20),
|
||||
element("券后¥9.9", top = 30),
|
||||
element("2件9.5折", top = 40),
|
||||
element("颜色分类", top = 100),
|
||||
element("黑色", top = 140, clickable = true)
|
||||
)
|
||||
)
|
||||
|
||||
requireNotNull(evidence)
|
||||
assertNull(evidence.price)
|
||||
assertFalse(evidence.priceAmbiguous)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `merges partial observations after bounded scroll`() {
|
||||
val color = requireNotNull(
|
||||
PinduoduoSpecificationParser.parse(
|
||||
listOf(
|
||||
element("¥10.9", top = 20),
|
||||
element("已选择:白色", top = 40),
|
||||
element("颜色分类", top = 100),
|
||||
element(
|
||||
"白色",
|
||||
top = 140,
|
||||
clickable = true,
|
||||
selected = true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val size = requireNotNull(
|
||||
PinduoduoSpecificationParser.parse(
|
||||
listOf(
|
||||
element("¥10.9", top = 20),
|
||||
element("已选择:白色 2XL", top = 40),
|
||||
element("尺码", top = 100),
|
||||
element(
|
||||
"2XL 建议145-160斤",
|
||||
top = 140,
|
||||
clickable = true,
|
||||
selected = true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val merged = requireNotNull(
|
||||
PinduoduoSpecificationParser.merge(listOf(color, size))
|
||||
)
|
||||
assertEquals(
|
||||
PinduoduoSpecificationGroupKind.entries.toSet(),
|
||||
merged.groups.map { it.kind }.toSet()
|
||||
)
|
||||
assertEquals("已选择:白色 2XL", merged.selectedSummary)
|
||||
assertEquals(1090L, merged.price?.cents)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selection policy rejects disabled and accepts selected alias`() {
|
||||
val evidence = requireNotNull(
|
||||
PinduoduoSpecificationParser.parse(
|
||||
listOf(
|
||||
element("已选择:黑色 XXL", top = 40),
|
||||
element("颜色分类", top = 100),
|
||||
element(
|
||||
"经典黑色",
|
||||
top = 140,
|
||||
clickable = true,
|
||||
selected = true
|
||||
),
|
||||
element("尺码", top = 240),
|
||||
element(
|
||||
"XXL",
|
||||
top = 280,
|
||||
clickable = true,
|
||||
selected = true
|
||||
),
|
||||
element(
|
||||
"3XL",
|
||||
top = 320,
|
||||
clickable = true,
|
||||
enabled = false
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
PinduoduoSpecificationOptionResolution.Ready(
|
||||
optionText = "XXL",
|
||||
alreadySelected = true
|
||||
),
|
||||
PinduoduoSpecificationSelectionPolicy.resolve(
|
||||
evidence,
|
||||
PinduoduoSpecificationGroupKind.SIZE,
|
||||
"2XL"
|
||||
)
|
||||
)
|
||||
assertEquals(
|
||||
PinduoduoSpecificationOptionResolution.Disabled("3XL"),
|
||||
PinduoduoSpecificationSelectionPolicy.resolve(
|
||||
evidence,
|
||||
PinduoduoSpecificationGroupKind.SIZE,
|
||||
"3XL"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun element(
|
||||
text: String,
|
||||
top: Int,
|
||||
bottom: Int = top + 1,
|
||||
clickable: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
selected: Boolean = false,
|
||||
@@ -93,6 +253,7 @@ class PinduoduoSpecificationParserTest {
|
||||
visibleToUser = true,
|
||||
selected = selected,
|
||||
scrollable = scrollable,
|
||||
boundsTop = top
|
||||
boundsTop = top,
|
||||
boundsBottom = bottom
|
||||
)
|
||||
}
|
||||
|
||||
+40
-3
@@ -4,6 +4,7 @@ import com.roubao.autopilot.pinduoduo.PinduoduoSpecificationEvidence
|
||||
import com.roubao.autopilot.pinduoduo.PinduoduoSpecificationGroup
|
||||
import com.roubao.autopilot.pinduoduo.PinduoduoSpecificationGroupKind
|
||||
import com.roubao.autopilot.pinduoduo.PinduoduoSpecificationOption
|
||||
import com.roubao.autopilot.pinduoduo.PinduoduoSpecificationPrice
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
@@ -54,6 +55,39 @@ class CandidateSpecificationHardConstraintPolicyTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `visible but unselected target is unknown`() {
|
||||
assertEquals(
|
||||
HardConstraintMatchStatus.UNKNOWN,
|
||||
evaluate(
|
||||
colorOptions = listOf(
|
||||
option("黑色", selected = false)
|
||||
),
|
||||
sizeOptions = listOf(option("2XL"))
|
||||
).first().status
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `missing combination price makes selected targets unknown`() {
|
||||
val evidence = specification(
|
||||
colorOptions = listOf(option("黑色")),
|
||||
sizeOptions = listOf(option("2XL"))
|
||||
).copy(price = null)
|
||||
|
||||
val results = CandidateSpecificationHardConstraintPolicy.evaluate(
|
||||
constraints = constraints(),
|
||||
evidence = evidence,
|
||||
evidenceSha256 = "b".repeat(64)
|
||||
)
|
||||
|
||||
assertTrue(
|
||||
results.all {
|
||||
it.status == HardConstraintMatchStatus.UNKNOWN
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `truncated group and duplicated matching option are unknown`() {
|
||||
assertEquals(
|
||||
@@ -134,15 +168,18 @@ class CandidateSpecificationHardConstraintPolicyTest {
|
||||
sizeOptions,
|
||||
true
|
||||
)
|
||||
)
|
||||
),
|
||||
selectedSummary = "已选择:经典黑色 XXL",
|
||||
price = PinduoduoSpecificationPrice("¥10.9", 1090)
|
||||
)
|
||||
|
||||
private fun option(
|
||||
text: String,
|
||||
enabled: Boolean = true
|
||||
enabled: Boolean = true,
|
||||
selected: Boolean = true
|
||||
) = PinduoduoSpecificationOption(
|
||||
text = text,
|
||||
selected = false,
|
||||
selected = selected,
|
||||
enabled = enabled
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user