Files
knowledge-adapter-service/tests/test_auth.py
T
姚章浩 693cd952cd feat(asset): 清理被截断的结尾图片引用,修复整单 502
仓库首次入库(此前仅存在于本地工作区与测试服构建产物中,未上传)。

改动(src/knowledge_adapter/asset_content_processor.py):
- 新增 _TRUNCATED_TRAILING_MARKDOWN_RESOURCE_IMAGE 规则,清除 chunk 末尾被截断的
  ![](resource://xxxx 残片(WeKnora 分块在图片引用中间截断时产生,旧逻辑无法匹配完整引用,
  残留 resource:// 触发 ContentProcessingError,导致整单 502)
- 回归测试 tests/test_asset_content_processor.py::
  test_truncated_trailing_markdown_resource_image_is_removed

验证证据:
- 测试服 120.76.217.51,镜像 knowledge-adapter-service:reviewed-test-aeab4b5fca2d
  (release aeab4b5fca2d8661):KB d015d05c-25c8-4d63-b2ab-e8590d3c2dfb 5 个查询全部
  http=200、records=5、残留 resource:// = 0;KB b9dacd62 无退化;容器日志无 error/exception
- 本地:pytest 141 项全通过、ruff 全通过
- 该文件与 258c31e0fe07fd08 源码的差异经逐文件 md5 比对,为唯一变更文件
2026-09-16 10:01:27 +08:00

92 lines
2.6 KiB
Python

import hashlib
import hmac
from collections.abc import AsyncIterator
import httpx
import pytest
from knowledge_adapter.app import create_app
from knowledge_adapter.auth import DifyKeyAuthenticator
CURRENT_KEY = "c" * 32
NEXT_KEY = "n" * 32
VALID_PAYLOAD = {
"knowledge_id": "3904b40e-278b-4273-a6fa-db6cbc975587",
"query": "test query",
"retrieval_setting": {"top_k": 3, "score_threshold": 0.5},
}
def sha256_hex(value: str) -> str:
return hashlib.sha256(value.encode()).hexdigest()
@pytest.fixture
async def client() -> AsyncIterator[httpx.AsyncClient]:
authenticator = DifyKeyAuthenticator(
current_hash=sha256_hex(CURRENT_KEY),
next_hash=sha256_hex(NEXT_KEY),
)
transport = httpx.ASGITransport(app=create_app(authenticator=authenticator))
async with httpx.AsyncClient(transport=transport, base_url="http://test") as value:
yield value
async def test_retrieval_without_bearer_key_returns_401(client: httpx.AsyncClient) -> None:
response = await client.post("/retrieval", json=VALID_PAYLOAD)
assert response.status_code == 401
async def test_retrieval_with_wrong_bearer_key_returns_401(client: httpx.AsyncClient) -> None:
response = await client.post(
"/retrieval",
headers={"Authorization": f"Bearer {'x' * 32}"},
json=VALID_PAYLOAD,
)
assert response.status_code == 401
async def test_retrieval_with_current_key_returns_200(client: httpx.AsyncClient) -> None:
response = await client.post(
"/retrieval",
headers={"Authorization": f"Bearer {CURRENT_KEY}"},
json=VALID_PAYLOAD,
)
assert response.status_code == 200
async def test_retrieval_with_next_key_returns_200(client: httpx.AsyncClient) -> None:
response = await client.post(
"/retrieval",
headers={"Authorization": f"Bearer {NEXT_KEY}"},
json=VALID_PAYLOAD,
)
assert response.status_code == 200
async def test_current_match_does_not_short_circuit_next_hash_comparison(
client: httpx.AsyncClient,
monkeypatch: pytest.MonkeyPatch,
) -> None:
original_compare = hmac.compare_digest
compared_hashes: list[str] = []
def recording_compare(digest: str, configured_hash: str) -> bool:
compared_hashes.append(configured_hash)
return original_compare(digest, configured_hash)
monkeypatch.setattr("knowledge_adapter.auth.hmac.compare_digest", recording_compare)
response = await client.post(
"/retrieval",
headers={"Authorization": f"Bearer {CURRENT_KEY}"},
json=VALID_PAYLOAD,
)
assert response.status_code == 200
assert len(compared_hashes) == 2