2026-07-27 18:35:59 +08:00
|
|
|
package usecase
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestValidateCandidateCommandAcceptsEmptyMatchedBatch(t *testing.T) {
|
|
|
|
|
command := validAIExecutionCandidateCommand()
|
|
|
|
|
command.Candidates = nil
|
|
|
|
|
command.Recommendation = nil
|
|
|
|
|
|
|
|
|
|
if err := validateCandidateCommand(command); err != nil {
|
|
|
|
|
t.Fatalf("validate empty matched batch: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestValidateCandidateCommandAcceptsMatchedColorAndSize(t *testing.T) {
|
|
|
|
|
command := validAIExecutionCandidateCommand()
|
|
|
|
|
|
|
|
|
|
if err := validateCandidateCommand(command); err != nil {
|
|
|
|
|
t.Fatalf("validate matched candidate: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestValidateCandidateCommandRejectsUnknownHardConstraint(t *testing.T) {
|
|
|
|
|
command := validAIExecutionCandidateCommand()
|
|
|
|
|
command.Candidates[0].Evaluation.HardConstraints[1].Status = "UNKNOWN"
|
2026-07-28 11:57:25 +08:00
|
|
|
command.Candidates[0].Evaluation.Decision = "REJECT"
|
|
|
|
|
command.Candidates[0].Evaluation.RejectionReasons = []string{"尺码无法确认"}
|
|
|
|
|
command.Recommendation = nil
|
2026-07-27 18:35:59 +08:00
|
|
|
|
2026-07-28 11:57:25 +08:00
|
|
|
if err := validateCandidateCommand(command); err != nil {
|
|
|
|
|
t.Fatalf("validate observed unknown hard constraint: %v", err)
|
2026-07-27 18:35:59 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestValidateCandidateCommandRejectsV2CandidateWithoutHardConstraints(t *testing.T) {
|
|
|
|
|
command := validAIExecutionCandidateCommand()
|
|
|
|
|
command.Candidates[0].Evaluation.HardConstraints = nil
|
|
|
|
|
|
|
|
|
|
if err := validateCandidateCommand(command); err == nil {
|
|
|
|
|
t.Fatal("expected missing v2 hard constraints to be rejected")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestValidateCandidateCommandRejectsWeakV2Candidate(t *testing.T) {
|
|
|
|
|
command := validAIExecutionCandidateCommand()
|
|
|
|
|
command.Candidates[0].Evaluation.Score = 0.74
|
|
|
|
|
|
|
|
|
|
if err := validateCandidateCommand(command); err == nil {
|
|
|
|
|
t.Fatal("expected weak v2 candidate to be rejected")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 11:57:25 +08:00
|
|
|
func TestValidateCandidateCommandAcceptsV2RecommendationUsingOriginalOrdinal(t *testing.T) {
|
2026-07-27 18:35:59 +08:00
|
|
|
command := validAIExecutionCandidateCommand()
|
|
|
|
|
second := command.Candidates[0]
|
|
|
|
|
second.Ordinal = 2
|
|
|
|
|
second.Title = "拼多多图片候选 2"
|
|
|
|
|
command.Candidates = append(command.Candidates, second)
|
|
|
|
|
command.Recommendation.CandidateOrdinal = 2
|
|
|
|
|
|
2026-07-28 11:57:25 +08:00
|
|
|
if err := validateCandidateCommand(command); err != nil {
|
|
|
|
|
t.Fatalf("validate recommendation using original ordinal: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestValidateCandidateCommandRejectsV2RecommendationForRejectedCandidate(t *testing.T) {
|
|
|
|
|
command := validAIExecutionCandidateCommand()
|
|
|
|
|
second := command.Candidates[0]
|
|
|
|
|
second.Ordinal = 2
|
|
|
|
|
second.Title = "拼多多图片候选 2"
|
|
|
|
|
second.Evaluation = &CandidateEvaluation{
|
|
|
|
|
Decision: "REJECT",
|
|
|
|
|
Score: 0.2,
|
|
|
|
|
Confidence: 0.9,
|
|
|
|
|
RejectionReasons: []string{"颜色不匹配"},
|
|
|
|
|
}
|
|
|
|
|
command.Candidates = append(command.Candidates, second)
|
|
|
|
|
command.Recommendation.CandidateOrdinal = 2
|
|
|
|
|
|
2026-07-27 18:35:59 +08:00
|
|
|
if err := validateCandidateCommand(command); err == nil {
|
2026-07-28 11:57:25 +08:00
|
|
|
t.Fatal("expected recommendation for rejected candidate to be rejected")
|
2026-07-27 18:35:59 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func validAIExecutionCandidateCommand() StoreExecutionCandidatesCommand {
|
|
|
|
|
return StoreExecutionCandidatesCommand{
|
|
|
|
|
TaskContentSHA256: strings.Repeat("a", 64),
|
|
|
|
|
ExecutionMode: aiAssistedMode,
|
|
|
|
|
SearchQuery: "PDD_IMAGE_SEARCH",
|
|
|
|
|
Provenance: &ExecutionProvenance{
|
|
|
|
|
ProviderID: "openai-compatible",
|
|
|
|
|
Model: "test-model",
|
|
|
|
|
PromptVersion: "candidate-evaluation-v2",
|
|
|
|
|
SchemaVersion: 2,
|
|
|
|
|
},
|
|
|
|
|
Candidates: []ExecutionCandidate{
|
|
|
|
|
{
|
2026-07-28 12:21:55 +08:00
|
|
|
Ordinal: 1,
|
|
|
|
|
Title: "拼多多图片候选 1",
|
|
|
|
|
CardSignature: strings.Repeat("b", 64),
|
|
|
|
|
DetailSignature: strings.Repeat("c", 64),
|
|
|
|
|
DetailEvidenceSHA256: strings.Repeat("d", 64),
|
|
|
|
|
SpecificationEvidenceSHA256: strings.Repeat("e", 64),
|
|
|
|
|
EvidenceAssetIDs: []string{
|
|
|
|
|
"00000000-0000-4000-8000-000000000001",
|
|
|
|
|
"00000000-0000-4000-8000-000000000002",
|
|
|
|
|
},
|
2026-07-27 18:35:59 +08:00
|
|
|
Evaluation: &CandidateEvaluation{
|
|
|
|
|
Decision: "REVIEW",
|
|
|
|
|
Score: 0.9,
|
|
|
|
|
Matched: []string{"图片相似"},
|
|
|
|
|
Confidence: 0.9,
|
|
|
|
|
HardConstraints: []CandidateHardConstraintEvaluation{
|
|
|
|
|
{
|
|
|
|
|
Kind: "COLOR",
|
|
|
|
|
Expected: "BLACK",
|
|
|
|
|
Status: "MATCH",
|
|
|
|
|
Evidence: "截图显示黑色",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Kind: "SIZE",
|
|
|
|
|
Expected: "L",
|
|
|
|
|
Status: "MATCH",
|
|
|
|
|
Evidence: "截图显示 L 码",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Recommendation: &CandidateRecommendation{
|
|
|
|
|
CandidateOrdinal: 1,
|
|
|
|
|
PolicyVersion: "sku-hard-constraints-v1",
|
|
|
|
|
Reasons: []string{"SKU 颜色和尺码硬约束均匹配"},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-28 12:21:55 +08:00
|
|
|
|
|
|
|
|
func TestStoreCandidatesRejectsDuplicateEvidenceReferences(t *testing.T) {
|
|
|
|
|
command := validAIExecutionCandidateCommand()
|
|
|
|
|
command.Candidates[0].EvidenceAssetIDs[1] =
|
|
|
|
|
command.Candidates[0].EvidenceAssetIDs[0]
|
|
|
|
|
|
|
|
|
|
if err := validateCandidateCommand(command); err == nil {
|
|
|
|
|
t.Fatal("expected duplicate evidence references to be rejected")
|
|
|
|
|
}
|
|
|
|
|
}
|