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:
xsl
2026-08-14 00:26:39 +08:00
parent 746b66da01
commit 93b88db2f5
16 changed files with 1709 additions and 0 deletions
+89
View File
@@ -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")