fix: 孤代理三入口堵漏+精确断言+截断清洗换序

- _utf8_ok 助手:event_id/path/title 含孤代理("\ud800")在入口即拒
  (原会流到下游 encode 必 500);props 字节守门 try/except 归入 properties_invalid
- 补零覆盖原因码测试:path=123 → invalid_field_type;utm 三层嵌套 → properties_depth
- 子串断言("sensitive" in r 等)全改精确等值断言,码即 API
- path 改为先 sanitize_url 再截 500:中文查询参数 urlencode 膨胀最多 3 倍,
  先截后洗会破坏出门 ≤MAX_PATH_LEN 不变量(实测汉*200 查询洗后截回 500)
- docstring 写明就地写回契约(Task 6 ingest 依赖)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-15 12:20:27 +08:00
parent c5f900f3bd
commit 7b26c7e14a
2 changed files with 44 additions and 17 deletions
+21 -10
View File
@@ -17,8 +17,7 @@ def test_unknown_event_name():
assert validate_event(_ev(event_name="xxx")) == "event_name_not_allowed"
def test_sensitive_property_rejected():
r = validate_event(_ev(properties={"user_password": "1"}))
assert "sensitive" in r
assert validate_event(_ev(properties={"user_password": "1"})) == "sensitive_field"
def test_shape_type_and_whitelist():
assert validate_event(["x"]) == "event_invalid_shape"
@@ -26,14 +25,11 @@ def test_shape_type_and_whitelist():
assert validate_event(_ev(properties={"foo": "bar"})) == "property_not_allowed"
def test_too_many_props_and_long_string():
r = validate_event(_ev(properties={f"k{i}": "v" for i in range(40)}))
assert "properties" in r
r = validate_event(_ev(properties={"referrer": "x" * 600}))
assert "properties" in r
assert validate_event(_ev(properties={f"k{i}": "v" for i in range(40)})) == "properties_invalid"
assert validate_event(_ev(properties={"referrer": "x" * 600})) == "properties_too_long"
def test_future_and_stale_time():
r = validate_event(_ev(occurred_at=NOW + timedelta(days=2)))
assert "occurred_at" in r # 超窗拒绝
assert validate_event(_ev(occurred_at=NOW + timedelta(days=2))) == "occurred_at_out_of_window" # 超窗拒绝
assert validate_event(_ev(occurred_at=NOW - timedelta(hours=2))) is None # 窗内迟到可收
def test_sanitize_url_drops_sensitive_params():
@@ -42,8 +38,7 @@ def test_sanitize_url_drops_sensitive_params():
assert "monkey=1" in u # fullmatch:含 key 子串的正常参数不误伤
def test_nested_sensitive_key_rejected():
r = validate_event(_ev(properties={"utm": {"utm_a": 1, "user_password": "x"}}))
assert "sensitive" in r # utm 在 page_view 白名单内,嵌套敏感键仍须命中
assert validate_event(_ev(properties={"utm": {"utm_a": 1, "user_password": "x"}})) == "sensitive_field" # utm 在白名单内,嵌套敏感键仍须命中
def test_naive_datetime_rejected():
assert validate_event(_ev(occurred_at=NOW.replace(tzinfo=None))) == "invalid_occurred_at"
@@ -57,3 +52,19 @@ def test_nested_string_length_capped():
def test_falsy_non_dict_properties_rejected():
assert validate_event(_ev(properties=[])) == "properties_invalid"
def test_lone_surrogate_event_id_rejected():
assert validate_event(_ev(event_id="\ud800")) == "invalid_event_id"
def test_lone_surrogate_field_rejected():
assert validate_event(_ev(path="/p\ud800")) == "invalid_field_type"
assert validate_event(_ev(title="t\ud800")) == "invalid_field_type"
def test_lone_surrogate_props_rejected():
assert validate_event(_ev(properties={"utm": {"a": "\ud800"}})) == "properties_invalid"
def test_non_str_path_rejected():
assert validate_event(_ev(path=123)) == "invalid_field_type"
def test_prop_depth_exceeded():
assert validate_event(_ev(properties={"utm": {"a": {"b": 1}}})) == "properties_depth"