test: unit suite for the gateway invariants plus an integration slice
Covers routing, failover, the circuit breaker, structured-output retries, the cache-prefix validator, cost computation including cached-token pricing, the daily budget breaker, the quota protocol, the task state machine and log redaction. HTTP is mocked -- no unit test spends money. The integration test runs the real slice against PostgreSQL and Redis with a faked provider.
This commit is contained in:
Executable
+89
@@ -0,0 +1,89 @@
|
||||
"""Prompt-cache prefix discipline (架构文档 §7.2 缓存前缀纪律, TD-04).
|
||||
|
||||
A volatile token in the fixed prefix cannot be caught by functional tests: the
|
||||
answer still looks right, the bill just quietly multiplies. So it gets its own
|
||||
mechanical check.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.llm.errors import CachePrefixViolationError, PromptNotFoundError
|
||||
from packages.llm.prompts import (
|
||||
PromptRegistry,
|
||||
find_volatile_tokens,
|
||||
parse_prompt_file,
|
||||
validate_cache_prefix,
|
||||
)
|
||||
|
||||
CLEAN_PREFIX = "你是「美搭」的穿搭顾问助手。只回答穿搭、色彩与风格相关的问题。"
|
||||
|
||||
|
||||
def test_clean_prefix_has_no_violations() -> None:
|
||||
assert find_volatile_tokens(CLEAN_PREFIX) == []
|
||||
assert validate_cache_prefix(CLEAN_PREFIX, mode="strict") == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("prefix", "expected_kind"),
|
||||
[
|
||||
("今天是 2026-08-13,请为用户搭配。", "iso-date"),
|
||||
("当前时间 09:31:00,请回答。", "iso-time"),
|
||||
("现在是 2026 年 8 月。", "cn-date"),
|
||||
("时间戳 1755012345 供参考。", "epoch-seconds"),
|
||||
("会话 550e8400-e29b-41d4-a716-446655440000 开始。", "uuid"),
|
||||
("追踪号 9f8c1d2e3b4a5f6071829304 记录在案。", "long-hex-id"),
|
||||
("你好 {nickname},我来帮你搭配。", "volatile-placeholder"),
|
||||
("用户编号 {user_id} 的偏好如下。", "volatile-placeholder"),
|
||||
("当前时刻 {now}。", "volatile-placeholder"),
|
||||
],
|
||||
)
|
||||
def test_volatile_tokens_are_detected(prefix: str, expected_kind: str) -> None:
|
||||
kinds = {v.kind for v in find_volatile_tokens(prefix)}
|
||||
assert expected_kind in kinds
|
||||
|
||||
|
||||
def test_strict_mode_raises_with_the_offending_samples() -> None:
|
||||
with pytest.raises(CachePrefixViolationError) as excinfo:
|
||||
validate_cache_prefix("生成于 2026-08-13 的提示词", mode="strict", ref="p@v1")
|
||||
assert "p@v1" in str(excinfo.value)
|
||||
assert excinfo.value.violations
|
||||
|
||||
|
||||
def test_warn_mode_reports_without_raising() -> None:
|
||||
violations = validate_cache_prefix("生成于 2026-08-13", mode="warn")
|
||||
assert [v.kind for v in violations] == ["iso-date"]
|
||||
|
||||
|
||||
def test_off_mode_skips_the_check() -> None:
|
||||
assert validate_cache_prefix("生成于 2026-08-13", mode="off") == []
|
||||
|
||||
|
||||
def test_registry_rejects_a_bad_prompt_at_load_time(tmp_path: Path) -> None:
|
||||
bad = tmp_path / "bad" / "v1.txt"
|
||||
bad.parent.mkdir(parents=True)
|
||||
bad.write_text("[system]\n你好 {nickname}\n\n[user]\n{user_input}\n", encoding="utf-8")
|
||||
|
||||
registry = PromptRegistry(tmp_path, cache_prefix_mode="strict")
|
||||
with pytest.raises(CachePrefixViolationError):
|
||||
registry.get("bad", "v1")
|
||||
|
||||
|
||||
def test_shipped_prompts_all_satisfy_the_discipline(repo_root: Path) -> None:
|
||||
"""Regression guard: every prompt in the repo keeps a cacheable prefix."""
|
||||
prompts_root = repo_root / "prompts"
|
||||
files = sorted(prompts_root.glob("*/*.txt"))
|
||||
assert files, "no prompt templates found"
|
||||
for path in files:
|
||||
template = parse_prompt_file(
|
||||
path.read_text(encoding="utf-8"), prompt_id=path.parent.name, version=path.stem
|
||||
)
|
||||
assert find_volatile_tokens(template.system_prefix) == [], f"{path} has a volatile prefix"
|
||||
|
||||
|
||||
def test_prompt_file_without_system_section_is_rejected() -> None:
|
||||
with pytest.raises(PromptNotFoundError):
|
||||
parse_prompt_file("[user]\nhello\n", prompt_id="x", version="v1")
|
||||
Reference in New Issue
Block a user