fix: 对齐规格解析 Mock 契约 (#257)
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import unicodedata
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
@@ -195,8 +197,12 @@ class MockAdminGateway(AdminGateway):
|
||||
) -> SpecResolutionReceipt:
|
||||
with self._lock:
|
||||
self._raise_forced_error()
|
||||
task = self._validate_submission_target(task_id, idempotency_key)
|
||||
self._validate_spec_observation(task, idempotency_key, observation)
|
||||
self._validate_spec_observation(
|
||||
task_id, idempotency_key, observation
|
||||
)
|
||||
task = self._validate_spec_resolution_target(
|
||||
task_id, observation
|
||||
)
|
||||
fingerprint = self._fingerprint(
|
||||
"spec_resolution", task_id, observation
|
||||
)
|
||||
@@ -233,42 +239,59 @@ class MockAdminGateway(AdminGateway):
|
||||
|
||||
@staticmethod
|
||||
def _validate_spec_observation(
|
||||
task: AdminTask,
|
||||
task_id: str,
|
||||
idempotency_key: str,
|
||||
observation: Mapping[str, Any],
|
||||
) -> None:
|
||||
if task.task_type.value != "purchase":
|
||||
try:
|
||||
encoded = json.dumps(
|
||||
observation,
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise AdminGatewayError(
|
||||
"TASK_NOT_PURCHASE", "当前任务不是采购任务", False
|
||||
"INVALID_BODY", "规格解析请求不是有效 JSON", False
|
||||
) from exc
|
||||
if len(encoded) > 64 * 1024:
|
||||
raise AdminGatewayError(
|
||||
"INVALID_BODY", "规格解析请求超过 64 KiB", False
|
||||
)
|
||||
if observation.get("schema_version") != 1:
|
||||
raise AdminGatewayError(
|
||||
"INVALID_SPEC_RESOLUTION_SCHEMA", "规格解析版本无效", False
|
||||
)
|
||||
if observation.get("task_version") != task.version:
|
||||
raise AdminGatewayError(
|
||||
"TASK_VERSION_CONFLICT", "规格解析任务版本不一致", False
|
||||
)
|
||||
payload = task.payload
|
||||
if observation.get("pdd_goods_id") != payload.get("goods_id"):
|
||||
raise AdminGatewayError(
|
||||
"PDD_GOODS_MISMATCH", "规格解析商品编号不一致", False
|
||||
)
|
||||
if dict(observation.get("original_options") or {}) != dict(
|
||||
payload.get("options") or {}
|
||||
):
|
||||
raise AdminGatewayError(
|
||||
"INVALID_SPEC_RESOLUTION_REQUEST", "原始规格不一致", False
|
||||
)
|
||||
task_version = observation.get("task_version")
|
||||
original_options = observation.get("original_options")
|
||||
candidates = observation.get("candidates")
|
||||
snapshot_hash = observation.get("candidate_snapshot_hash")
|
||||
selected_color = observation.get("selected_color")
|
||||
target_size = observation.get("target_size")
|
||||
observed_at = observation.get("observed_at")
|
||||
if (
|
||||
not isinstance(candidates, list)
|
||||
not isinstance(task_version, int)
|
||||
or isinstance(task_version, bool)
|
||||
or task_version <= 0
|
||||
or not MockAdminGateway._valid_spec_text(
|
||||
observation.get("attempt_id")
|
||||
)
|
||||
or not MockAdminGateway._valid_spec_text(
|
||||
observation.get("pdd_goods_id")
|
||||
)
|
||||
or not MockAdminGateway._valid_spec_text(selected_color)
|
||||
or not MockAdminGateway._valid_spec_text(target_size)
|
||||
or not isinstance(original_options, Mapping)
|
||||
or not 1 <= len(original_options) <= 16
|
||||
or any(
|
||||
not MockAdminGateway._valid_spec_text(key)
|
||||
or not MockAdminGateway._valid_spec_text(value)
|
||||
for key, value in original_options.items()
|
||||
)
|
||||
or not isinstance(candidates, list)
|
||||
or not 1 <= len(candidates) <= 100
|
||||
or not isinstance(snapshot_hash, str)
|
||||
or len(snapshot_hash) != 64
|
||||
or not str(observation.get("attempt_id") or "").strip()
|
||||
or re.fullmatch(r"[0-9a-f]{64}", snapshot_hash) is None
|
||||
or not MockAdminGateway._valid_observed_at(observed_at)
|
||||
):
|
||||
raise AdminGatewayError(
|
||||
"INVALID_SPEC_RESOLUTION_REQUEST", "规格候选结构无效", False
|
||||
@@ -276,8 +299,9 @@ class MockAdminGateway(AdminGateway):
|
||||
if any(
|
||||
not isinstance(candidate, Mapping)
|
||||
or candidate.get("candidate_id") != f"c{index}"
|
||||
or not isinstance(candidate.get("raw_text"), str)
|
||||
or not candidate["raw_text"]
|
||||
or not MockAdminGateway._valid_spec_text(
|
||||
candidate.get("raw_text")
|
||||
)
|
||||
or dict(candidate.get("options") or {})
|
||||
!= {
|
||||
"color": selected_color,
|
||||
@@ -319,7 +343,7 @@ class MockAdminGateway(AdminGateway):
|
||||
identity = "".join(
|
||||
MockAdminGateway._frame(str(value))
|
||||
for value in (
|
||||
task.task_id,
|
||||
task_id,
|
||||
observation["attempt_id"],
|
||||
snapshot_hash,
|
||||
"spec-resolution-v1",
|
||||
@@ -333,6 +357,88 @@ class MockAdminGateway(AdminGateway):
|
||||
"SPEC_RESOLUTION_HASH_MISMATCH", "规格解析幂等键不一致", False
|
||||
)
|
||||
|
||||
def _validate_spec_resolution_target(
|
||||
self,
|
||||
task_id: str,
|
||||
observation: Mapping[str, Any],
|
||||
) -> AdminTask:
|
||||
item = self._find_task(task_id)
|
||||
if item is None:
|
||||
raise AdminGatewayError(
|
||||
"TASK_NOT_FOUND", "规格解析任务不存在", False
|
||||
)
|
||||
task = item.task
|
||||
if task.task_type.value != "purchase":
|
||||
raise AdminGatewayError(
|
||||
"TASK_NOT_PURCHASE", "当前任务不是采购任务", False
|
||||
)
|
||||
if observation.get("task_version") != task.version:
|
||||
raise AdminGatewayError(
|
||||
"TASK_VERSION_CONFLICT", "规格解析任务版本不一致", False
|
||||
)
|
||||
payload = task.payload
|
||||
if observation.get("pdd_goods_id") != payload.get("goods_id"):
|
||||
raise AdminGatewayError(
|
||||
"PDD_GOODS_MISMATCH", "规格解析商品编号不一致", False
|
||||
)
|
||||
original_options = dict(observation.get("original_options") or {})
|
||||
if (
|
||||
original_options != dict(payload.get("options") or {})
|
||||
or not MockAdminGateway._target_option_was_claimed(
|
||||
original_options,
|
||||
"color",
|
||||
str(observation.get("selected_color") or ""),
|
||||
)
|
||||
or not MockAdminGateway._target_option_was_claimed(
|
||||
original_options,
|
||||
"size",
|
||||
str(observation.get("target_size") or ""),
|
||||
)
|
||||
):
|
||||
raise AdminGatewayError(
|
||||
"INVALID_SPEC_RESOLUTION_REQUEST", "原始规格不一致", False
|
||||
)
|
||||
if task_id not in self._claimed_task_ids:
|
||||
raise AdminGatewayError(
|
||||
"TASK_NOT_CLAIMED_BY_CLIENT",
|
||||
"该 Client 从未领取过此任务",
|
||||
False,
|
||||
)
|
||||
return task
|
||||
|
||||
@staticmethod
|
||||
def _target_option_was_claimed(
|
||||
options: Mapping[str, Any], preferred_key: str, target: str
|
||||
) -> bool:
|
||||
if preferred_key in options:
|
||||
return options[preferred_key] == target
|
||||
return target in options.values()
|
||||
|
||||
@staticmethod
|
||||
def _valid_spec_text(value: object) -> bool:
|
||||
return (
|
||||
isinstance(value, str)
|
||||
and 1 <= len(value) <= 191
|
||||
and not any(
|
||||
unicodedata.category(character) == "Cc"
|
||||
for character in value
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _valid_observed_at(value: object) -> bool:
|
||||
if not isinstance(value, str) or re.fullmatch(
|
||||
r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}"
|
||||
r"(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})",
|
||||
value,
|
||||
) is None:
|
||||
return False
|
||||
try:
|
||||
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
except ValueError:
|
||||
return False
|
||||
return parsed.tzinfo is not None
|
||||
|
||||
@staticmethod
|
||||
def _frame(value: str) -> str:
|
||||
return f"{len(value.encode('utf-8'))}:{value}"
|
||||
|
||||
Reference in New Issue
Block a user