feat(t208): close candidate decision feedback loop
This commit is contained in:
+67
@@ -0,0 +1,67 @@
|
||||
package com.roubao.autopilot.procurement
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
|
||||
class CandidateHumanReviewDraftPolicyTest {
|
||||
@Test
|
||||
fun `accepted review labels selected and rejected observations`() {
|
||||
val review = CandidateHumanReviewDraftPolicy.accepted(
|
||||
candidateOrdinals = listOf(1, 2, 3),
|
||||
selectedOrdinal = 2,
|
||||
acceptReasonCode = "SKU_MATCH",
|
||||
acceptNote = "",
|
||||
rejectReasonCode = "NOT_BEST_MATCH",
|
||||
rejectNote = "",
|
||||
hasBudget = false
|
||||
)
|
||||
|
||||
assertEquals("CANDIDATE_ACCEPTED", review.outcome)
|
||||
assertEquals(2, review.selectedCandidateOrdinal)
|
||||
assertEquals(
|
||||
listOf("REJECT", "ACCEPT", "REJECT"),
|
||||
review.items.map { it.label }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no observations produce an explicit no-match review`() {
|
||||
val review = CandidateHumanReviewDraftPolicy.rejected(
|
||||
candidateOrdinals = emptyList(),
|
||||
rejectReasonCode = "",
|
||||
rejectNote = "",
|
||||
hasBudget = false
|
||||
)
|
||||
|
||||
assertEquals("NO_MATCH", review.outcome)
|
||||
assertEquals(emptyList<CandidateHumanReviewItemDraft>(), review.items)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `price reasons require a task budget`() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
CandidateHumanReviewDraftPolicy.rejected(
|
||||
candidateOrdinals = listOf(1),
|
||||
rejectReasonCode = "PRICE_TOO_HIGH",
|
||||
rejectNote = "",
|
||||
hasBudget = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `other reasons require a meaningful note`() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
CandidateHumanReviewDraftPolicy.accepted(
|
||||
candidateOrdinals = listOf(1),
|
||||
selectedOrdinal = 1,
|
||||
acceptReasonCode = "OTHER",
|
||||
acceptNote = "短",
|
||||
rejectReasonCode = "",
|
||||
rejectNote = "",
|
||||
hasBudget = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -56,6 +56,23 @@ class ExecutionCandidateIdentityPolicyTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `explicit selection keeps the original observation ordinal`() {
|
||||
val bindings = ExecutionCandidateIdentityPolicy.bind(
|
||||
identities = listOf(
|
||||
identity(sourceOrdinal = 1, rankedOrdinal = 1),
|
||||
identity(sourceOrdinal = 2, rankedOrdinal = 2),
|
||||
identity(sourceOrdinal = 3, rankedOrdinal = 3)
|
||||
),
|
||||
candidates = listOf(candidate(1), candidate(2), candidate(3))
|
||||
)
|
||||
|
||||
val selected = ExecutionCandidateIdentityPolicy.select(bindings, 3)
|
||||
|
||||
assertEquals(3, selected.ordinal)
|
||||
assertEquals("evidence-3", selected.evidenceLocalIDs.single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `missing ranked ordinal is rejected instead of falling back`() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
|
||||
+23
@@ -225,6 +225,29 @@ class ProcurementApiClientTest {
|
||||
assertEquals("/api/v1/tasks/task-id/events", eventRequest.path)
|
||||
assertEquals("event-idempotency-key", eventRequest.getHeader("Idempotency-Key"))
|
||||
assertTrue(eventRequest.body.readUtf8().contains("execution-id"))
|
||||
|
||||
server.enqueue(jsonResponse("""{"review":{"id":"review-id"},"replayed":false}"""))
|
||||
api.uploadExecutionOutboxItem(
|
||||
session = session(),
|
||||
task = task,
|
||||
execution = execution,
|
||||
claimToken = "claim-token-value",
|
||||
item = ExecutionOutboxItem(
|
||||
id = "review-id",
|
||||
type = ExecutionOutboxType.HUMAN_REVIEW,
|
||||
idempotencyKey = "review-idempotency-key",
|
||||
payload = """{"execution_id":"execution-id","items":[]}"""
|
||||
)
|
||||
)
|
||||
val reviewRequest = server.takeRequest()
|
||||
assertEquals(
|
||||
"/api/v1/tasks/task-id/human-reviews",
|
||||
reviewRequest.path
|
||||
)
|
||||
assertEquals(
|
||||
"review-idempotency-key",
|
||||
reviewRequest.getHeader("Idempotency-Key")
|
||||
)
|
||||
}
|
||||
|
||||
private fun session() = ProcurementSession(
|
||||
|
||||
Reference in New Issue
Block a user