feat(t211): return SKU-matched image search candidates
This commit is contained in:
+149
@@ -0,0 +1,149 @@
|
||||
package com.roubao.autopilot.pinduoduo
|
||||
|
||||
import com.roubao.autopilot.readiness.PINDUODUO_PACKAGE
|
||||
import com.roubao.autopilot.workflow.SafetyStopReason
|
||||
import com.roubao.autopilot.workflow.WorkflowRunner
|
||||
import com.roubao.autopilot.workflow.WorkflowState
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class PinduoduoImageSearchAutomationTest {
|
||||
@Test
|
||||
fun `selects prepared image and verifies image results`() = runTest {
|
||||
val driver = FakeImageDriver(PinduoduoPage.SEARCH_RESULTS)
|
||||
val runner = WorkflowRunner(
|
||||
PinduoduoImageSearchAutomation(
|
||||
driver = driver,
|
||||
pollIntervalMillis = 1,
|
||||
unknownPageLimit = 3
|
||||
)
|
||||
)
|
||||
|
||||
val report = runner.run(PinduoduoImageSearchWorkflow.steps())
|
||||
|
||||
assertEquals(WorkflowState.SUCCEEDED, report.state)
|
||||
assertTrue(driver.imageSearchOpened)
|
||||
assertTrue(driver.preparedImageSelected)
|
||||
assertEquals(PinduoduoPage.IMAGE_SEARCH_RESULTS, driver.page)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selection verification failure blocks instead of using another image`() =
|
||||
runTest {
|
||||
val driver = FakeImageDriver(
|
||||
page = PinduoduoPage.SEARCH_RESULTS,
|
||||
selectionAllowed = false
|
||||
)
|
||||
val runner = WorkflowRunner(
|
||||
PinduoduoImageSearchAutomation(
|
||||
driver = driver,
|
||||
pollIntervalMillis = 1,
|
||||
unknownPageLimit = 3
|
||||
)
|
||||
)
|
||||
|
||||
val report = runner.run(PinduoduoImageSearchWorkflow.steps())
|
||||
|
||||
assertEquals(WorkflowState.BLOCKED, report.state)
|
||||
assertEquals(SafetyStopReason.UNKNOWN_PAGE, report.safetyStopReason)
|
||||
assertFalse(driver.preparedImageSelected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `recovers when camera result opens before prepared image selection`() =
|
||||
runTest {
|
||||
val driver = FakeImageDriver(
|
||||
page = PinduoduoPage.SEARCH_RESULTS,
|
||||
cameraResultRaceOnce = true
|
||||
)
|
||||
val runner = WorkflowRunner(
|
||||
PinduoduoImageSearchAutomation(
|
||||
driver = driver,
|
||||
pollIntervalMillis = 1,
|
||||
unknownPageLimit = 3
|
||||
)
|
||||
)
|
||||
|
||||
val report = runner.run(PinduoduoImageSearchWorkflow.steps())
|
||||
|
||||
assertEquals(WorkflowState.SUCCEEDED, report.state)
|
||||
assertEquals(1, driver.returnFromImageResultsCalls)
|
||||
assertTrue(driver.preparedImageSelected)
|
||||
assertEquals(PinduoduoPage.IMAGE_SEARCH_RESULTS, driver.page)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `payment marker stops before image entry`() = runTest {
|
||||
val driver = FakeImageDriver(
|
||||
page = PinduoduoPage.UNKNOWN,
|
||||
safetyStopReason = SafetyStopReason.PAYMENT_BOUNDARY
|
||||
)
|
||||
val runner = WorkflowRunner(
|
||||
PinduoduoImageSearchAutomation(
|
||||
driver = driver,
|
||||
pollIntervalMillis = 1,
|
||||
unknownPageLimit = 3
|
||||
)
|
||||
)
|
||||
|
||||
val report = runner.run(PinduoduoImageSearchWorkflow.steps())
|
||||
|
||||
assertEquals(WorkflowState.BLOCKED, report.state)
|
||||
assertFalse(driver.imageSearchOpened)
|
||||
}
|
||||
|
||||
private class FakeImageDriver(
|
||||
var page: PinduoduoPage,
|
||||
private val selectionAllowed: Boolean = true,
|
||||
private val safetyStopReason: SafetyStopReason? = null,
|
||||
private val cameraResultRaceOnce: Boolean = false
|
||||
) : PinduoduoImageSearchDriver {
|
||||
var imageSearchOpened = false
|
||||
var preparedImageSelected = false
|
||||
var returnFromImageResultsCalls = 0
|
||||
private var cameraResultRaceConsumed = false
|
||||
|
||||
override suspend fun openApp(): Boolean = true
|
||||
|
||||
override suspend fun snapshot(): PinduoduoUiSnapshot =
|
||||
PinduoduoUiSnapshot(
|
||||
foregroundPackage = PINDUODUO_PACKAGE,
|
||||
page = page,
|
||||
safetyStopReason = safetyStopReason
|
||||
)
|
||||
|
||||
override suspend fun openImageSearch(): Boolean {
|
||||
imageSearchOpened = true
|
||||
page = if (cameraResultRaceOnce && !cameraResultRaceConsumed) {
|
||||
cameraResultRaceConsumed = true
|
||||
PinduoduoPage.IMAGE_SEARCH_RESULTS
|
||||
} else {
|
||||
PinduoduoPage.IMAGE_SEARCH
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun selectPreparedImage(): Boolean {
|
||||
if (!selectionAllowed) {
|
||||
return false
|
||||
}
|
||||
preparedImageSelected = true
|
||||
page = PinduoduoPage.IMAGE_SEARCH_RESULTS
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun returnFromCandidate(): Boolean {
|
||||
page = PinduoduoPage.IMAGE_SEARCH_RESULTS
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun returnFromImageResults(): Boolean {
|
||||
returnFromImageResultsCalls += 1
|
||||
page = PinduoduoPage.IMAGE_SEARCH
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -4,6 +4,7 @@ import com.roubao.autopilot.readiness.PINDUODUO_PACKAGE
|
||||
import com.roubao.autopilot.workflow.SafetyStopReason
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class PinduoduoPageClassifierTest {
|
||||
@@ -102,6 +103,41 @@ class PinduoduoPageClassifierTest {
|
||||
assertEquals(PinduoduoPage.SEARCH_RESULTS_OTHER_QUERY, snapshot.page)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `image search camera page requires album and camera markers`() {
|
||||
val snapshot = classify(
|
||||
element(text = "我的相册"),
|
||||
element(text = "最近搜索"),
|
||||
element(text = "历史浏览"),
|
||||
element(text = "点击拍照")
|
||||
)
|
||||
|
||||
assertEquals(PinduoduoPage.IMAGE_SEARCH, snapshot.page)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `image search results require header and sort controls`() {
|
||||
val snapshot = classify(
|
||||
element(text = "搜图片同款"),
|
||||
element(text = "综合"),
|
||||
element(text = "销量"),
|
||||
element(text = "价格")
|
||||
)
|
||||
|
||||
assertEquals(PinduoduoPage.IMAGE_SEARCH_RESULTS, snapshot.page)
|
||||
assertTrue(snapshot.page.isCandidateResultsPage())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `partial image page markers remain unknown`() {
|
||||
val snapshot = classify(
|
||||
element(text = "我的相册"),
|
||||
element(text = "最近项目")
|
||||
)
|
||||
|
||||
assertEquals(PinduoduoPage.UNKNOWN, snapshot.page)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login verification and risk markers remain distinct`() {
|
||||
assertEquals(
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.roubao.autopilot.pinduoduo
|
||||
|
||||
import com.roubao.task.ProbeReferenceImage
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class PinduoduoReferenceImagePolicyTest {
|
||||
@Test
|
||||
fun `accepts only the declared jpeg bytes and hash`() {
|
||||
val bytes = byteArrayOf(
|
||||
0xff.toByte(),
|
||||
0xd8.toByte(),
|
||||
0xff.toByte(),
|
||||
0xd9.toByte()
|
||||
)
|
||||
val reference = reference(bytes)
|
||||
|
||||
assertTrue(PinduoduoReferenceImagePolicy.isValid(bytes, reference))
|
||||
assertFalse(
|
||||
PinduoduoReferenceImagePolicy.isValid(
|
||||
bytes + 0,
|
||||
reference
|
||||
)
|
||||
)
|
||||
assertFalse(
|
||||
PinduoduoReferenceImagePolicy.isValid(
|
||||
bytes,
|
||||
reference.copy(sha256 = "0".repeat(64))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun reference(bytes: ByteArray) = ProbeReferenceImage(
|
||||
relativePath = "reference.jpg",
|
||||
mediaType = "image/jpeg",
|
||||
sizeBytes = bytes.size.toLong(),
|
||||
sha256 = PinduoduoEvidenceHash.sha256(bytes)
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user