test: close two always-green holes in the dimension tests
The cache-key test only asserted a hit, so a key degraded to a constant would still pass it. Adding a namespace control group that must miss proves the key still distinguishes inputs; verified by degrading build_cache_key to a constant and watching the case go red. The allow_nan=False branch had no test at all. A ChatRequest built with a nan meta value (bypassing the entry validation, i.e. a future entry point that forgets to validate) must drop the row and not raise; verified red by removing allow_nan=False. Also restore the read-only file permissions in a finally block, so a failing assertion does not get masked by a PermissionError from tmp_path cleanup; rename the warnings fixture to captured_warnings so it stops shadowing the stdlib module; and drop a downstream business term from a fixture value (zero-business-assumption rule).
This commit is contained in:
@@ -424,7 +424,7 @@ CREATE TABLE {schema}.llm_calls (
|
|||||||
_PRE_TENANT_INSERT = (
|
_PRE_TENANT_INSERT = (
|
||||||
"INSERT INTO {schema}.llm_calls (call_id, model, provider, source_name, messages, response, "
|
"INSERT INTO {schema}.llm_calls (call_id, model, provider, source_name, messages, response, "
|
||||||
"prompt_tokens, completion_tokens, usage_source, latency_ms) "
|
"prompt_tokens, completion_tokens, usage_source, latency_ms) "
|
||||||
"VALUES ($1, 'm', 'p', 's1', '[]', 'contract text', 1, 2, 'measured', 10)"
|
"VALUES ($1, 'm', 'p', 's1', '[]', 'old body', 1, 2, 'measured', 10)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -434,8 +434,12 @@ def _search_path_dsn(dsn: str, schema: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def warnings():
|
async def captured_warnings():
|
||||||
"""捕获库发出的 WARNING;loguru 不经标准 logging,pytest 的 caplog 抓不到。"""
|
"""捕获库发出的 WARNING;loguru 不经标准 logging,pytest 的 caplog 抓不到。
|
||||||
|
|
||||||
|
名字避开裸 `warnings`: 那会遮蔽标准库模块名,本文件将来任何一次
|
||||||
|
`import warnings` 都会与它静默互相顶掉,而报错点离真因很远。
|
||||||
|
"""
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
messages: list[str] = []
|
messages: list[str] = []
|
||||||
@@ -608,7 +612,7 @@ class TestCallerDimensionsAcceptance:
|
|||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
async def test_backfill_failure_degrades_per_row_not_wholesale(
|
async def test_backfill_failure_degrades_per_row_not_wholesale(
|
||||||
self, least_privilege_pre_tenant_dsn, warnings
|
self, least_privilege_pre_tenant_dsn, captured_warnings
|
||||||
):
|
):
|
||||||
"""补列失败的降级方向: 记 warning、不置 `_failed`、后续 INSERT 仍照发。
|
"""补列失败的降级方向: 记 warning、不置 `_failed`、后续 INSERT 仍照发。
|
||||||
|
|
||||||
@@ -619,8 +623,8 @@ class TestCallerDimensionsAcceptance:
|
|||||||
try:
|
try:
|
||||||
await _record_minimal(recorder, call_id=_cid("lpp1")) # 不得抛
|
await _record_minimal(recorder, call_id=_cid("lpp1")) # 不得抛
|
||||||
assert recorder._failed is False
|
assert recorder._failed is False
|
||||||
assert any("补列失败" in m for m in warnings)
|
assert any("补列失败" in m for m in captured_warnings)
|
||||||
# 缺列的表上 INSERT 必然失败;逐行 warning 正是"INSERT 照发了"的证据
|
# 缺列的表上 INSERT 必然失败;逐行 warning 正是"INSERT 照发了"的证据
|
||||||
assert any("写入失败" in m for m in warnings)
|
assert any("写入失败" in m for m in captured_warnings)
|
||||||
finally:
|
finally:
|
||||||
await recorder.aclose()
|
await recorder.aclose()
|
||||||
|
|||||||
@@ -232,13 +232,23 @@ class TestCallerDimensions:
|
|||||||
assert recorder.rows == []
|
assert recorder.rows == []
|
||||||
|
|
||||||
async def test_meta_does_not_enter_cache_key(self):
|
async def test_meta_does_not_enter_cache_key(self):
|
||||||
"""仅 meta 不同必须仍命中缓存(F1): 进 key 会让存量缓存全量冷启动且不报错。"""
|
"""仅 meta 不同必须仍命中缓存(F1): 进 key 会让存量缓存全量冷启动且不报错。
|
||||||
client = _client(cache=InMemoryCache(), cache_namespace="proj", cache_ttl_s=3600)
|
|
||||||
|
带对照组: 只断言"命中"的话,缓存 key 退化成常量(忽略一切输入)时本用例
|
||||||
|
照样绿——那是恒真断言。故再改一个**确实进 key** 的维度(namespace)断言
|
||||||
|
miss,证明 key 仍在区分输入,"meta 不进 key"才是被测出来的结论。
|
||||||
|
"""
|
||||||
|
cache = InMemoryCache() # 两个 client 共用一份存储,否则对照组的 miss 是白来的
|
||||||
|
client = _client(cache=cache, cache_namespace="proj", cache_ttl_s=3600)
|
||||||
async with client:
|
async with client:
|
||||||
first = await client.chat(self._MSG, meta={"batch": "b-1"})
|
first = await client.chat(self._MSG, meta={"batch": "b-1"})
|
||||||
second = await client.chat(self._MSG, meta={"batch": "b-2"})
|
second = await client.chat(self._MSG, meta={"batch": "b-2"})
|
||||||
assert first.cache_hit is False and second.cache_hit is True
|
assert first.cache_hit is False and second.cache_hit is True
|
||||||
|
|
||||||
|
other_ns = _client(cache=cache, cache_namespace="other", cache_ttl_s=3600)
|
||||||
|
async with other_ns:
|
||||||
|
assert (await other_ns.chat(self._MSG, meta={"batch": "b-1"})).cache_hit is False
|
||||||
|
|
||||||
|
|
||||||
class TestModelFingerprint:
|
class TestModelFingerprint:
|
||||||
"""配置级采样参数须进缓存身份,否则改 temperature 后仍读旧缓存(决策 C)。"""
|
"""配置级采样参数须进缓存身份,否则改 temperature 后仍读旧缓存(决策 C)。"""
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ CREATE TABLE llm_calls (
|
|||||||
_PRE_TENANT_INSERT = (
|
_PRE_TENANT_INSERT = (
|
||||||
"INSERT INTO llm_calls (call_id, model, provider, source_name, messages, response, "
|
"INSERT INTO llm_calls (call_id, model, provider, source_name, messages, response, "
|
||||||
"prompt_tokens, completion_tokens, usage_source, latency_ms) "
|
"prompt_tokens, completion_tokens, usage_source, latency_ms) "
|
||||||
"VALUES ('old-row', 'm', 'p', 's1', '[]', 'contract text', 1, 2, 'measured', 10)"
|
"VALUES ('old-row', 'm', 'p', 's1', '[]', 'old body', 1, 2, 'measured', 10)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -399,12 +399,16 @@ class TestSQLiteCallerDimensionsAcceptance:
|
|||||||
conn.close()
|
conn.close()
|
||||||
db.chmod(0o444)
|
db.chmod(0o444)
|
||||||
|
|
||||||
recorder = SQLiteRecorder(db) # 不得抛
|
# finally 还原权限位: 任一断言先失败时,不还原会让 tmp_path 清理连带报错,
|
||||||
assert recorder._conn is not None # 补列失败 ≠ recorder 失能
|
# 把"某条断言失败"的真因盖成一个无关的 PermissionError
|
||||||
await _record_minimal(recorder, call_id="doomed") # 只读库写不进,但不得抛
|
try:
|
||||||
recorder.close()
|
recorder = SQLiteRecorder(db) # 不得抛
|
||||||
|
assert recorder._conn is not None # 补列失败 ≠ recorder 失能
|
||||||
|
await _record_minimal(recorder, call_id="doomed") # 只读库写不进,但不得抛
|
||||||
|
recorder.close()
|
||||||
|
finally:
|
||||||
|
db.chmod(0o644)
|
||||||
|
|
||||||
db.chmod(0o644) # 还原,让 tmp_path 清理不受阻
|
|
||||||
stale = sqlite3.connect(db)
|
stale = sqlite3.connect(db)
|
||||||
assert [r[1] for r in stale.execute("PRAGMA table_info(llm_calls)")] == (
|
assert [r[1] for r in stale.execute("PRAGMA table_info(llm_calls)")] == (
|
||||||
_EXPECTED_COLUMNS[:-2]
|
_EXPECTED_COLUMNS[:-2]
|
||||||
@@ -875,6 +879,25 @@ class TestEmitterCallerDimensions:
|
|||||||
)
|
)
|
||||||
assert "研发" in rec.rows[0]["meta"]
|
assert "研发" in rec.rows[0]["meta"]
|
||||||
|
|
||||||
|
async def test_non_finite_meta_value_drops_the_row_instead_of_poisoning_it(self):
|
||||||
|
"""入口失守时 `allow_nan=False` 的真实结果: 整行降级丢弃,且不抛给调用方。
|
||||||
|
|
||||||
|
直接构造带 `nan` 的 `ChatRequest`(绕过 `validate_caller_dimensions` 这道
|
||||||
|
主防线,模拟将来某个新入口忘记校验)。没有 `allow_nan=False` 时,
|
||||||
|
`json.dumps` 会写出裸 `NaN` 字面量——PG 的 JSONB 会拒收,但 **SQLite 的
|
||||||
|
`meta` 是 TEXT 列不做校验**,那串非法 JSON 会被静默存进去,污染此后一切
|
||||||
|
按 JSON 解析 meta 的分析。宁可丢一行遥测,也不要一行毒数据。
|
||||||
|
|
||||||
|
同时断言不抛: 遥测的降级方向是"静默降级"(铁律),把调用方的一次正常
|
||||||
|
业务调用因为一个维度值炸掉,方向反了。
|
||||||
|
"""
|
||||||
|
rec = _MemoryRecorder()
|
||||||
|
req = ChatRequest(messages=[{"role": "user", "content": "hi"}], meta={"k": float("nan")})
|
||||||
|
await TelemetryEmitter(rec).emit_terminal_failure(
|
||||||
|
request=req, call_id="c", latency_ms=1, error="dead"
|
||||||
|
)
|
||||||
|
assert rec.rows == []
|
||||||
|
|
||||||
|
|
||||||
class TestCostWithCachedTier:
|
class TestCostWithCachedTier:
|
||||||
"""issue #3: 命中部分按缓存单价计费,避免 cost 系统性高估。"""
|
"""issue #3: 命中部分按缓存单价计费,避免 cost 系统性高估。"""
|
||||||
|
|||||||
Reference in New Issue
Block a user