Files
knowledge-adapter-service/tests/test_settings.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

159 lines
5.4 KiB
Python

from pathlib import Path
from typing import Any
from uuid import UUID
import pytest
from pydantic import SecretStr, ValidationError
from knowledge_adapter.content_budget import TRUNCATION_MARKER
from knowledge_adapter.settings import Settings
BASE_SETTINGS: dict[str, Any] = {
"DIFY_KEY_HASH_CURRENT": "a" * 64,
"DIFY_KEY_HASH_NEXT": "",
"WEKNORA_BASE_URL": "http://weknora.internal:8080",
"WEKNORA_ALLOWED_KB_IDS": (
"3904b40e-278b-4273-a6fa-db6cbc975587,"
"7fa6e8d2-05d8-4e74-b98c-d8591b680db9"
),
"WEKNORA_TENANT_ID": 10008,
"WEKNORA_OSS_STORAGE_BACKEND_ID": "backend-test",
"OSS_REGION": "cn-shenzhen",
"OSS_BUCKET": "private-assets",
"OSS_RAM_ROLE_NAME": "knowledge-adapter-role",
"ASSET_PUBLIC_BASE_URL": "https://assets.example.com",
}
def test_settings_parse_allowlist_secrets_and_required_defaults() -> None:
settings = Settings(**BASE_SETTINGS)
assert settings.dify_key_hash_current == "a" * 64
assert settings.dify_key_hash_next is None
assert settings.weknora_api_key_file == Path("/run/secrets/weknora_api_key")
assert settings.weknora_allowed_kb_ids == frozenset(
{
UUID("3904b40e-278b-4273-a6fa-db6cbc975587"),
UUID("7fa6e8d2-05d8-4e74-b98c-d8591b680db9"),
}
)
assert settings.weknora_tenant_id == 10008
assert settings.weknora_oss_storage_backend_id == "backend-test"
assert settings.max_query_chars == 2000
assert settings.max_top_k == 5
assert settings.max_chunk_chars == 4000
assert settings.max_total_chars == 12000
assert settings.weknora_timeout_seconds == 15
assert settings.asset_database_url_file == Path("/run/secrets/asset_database_url")
assert settings.weknora_readonly_database_url_file == Path(
"/run/secrets/weknora_readonly_database_url"
)
def test_settings_reject_malformed_current_key_hash() -> None:
values = {**BASE_SETTINGS, "DIFY_KEY_HASH_CURRENT": "not-a-sha256"}
with pytest.raises(ValidationError):
Settings(**values)
def test_settings_uses_weknora_api_key_secret_file(tmp_path: Path) -> None:
secret_file = tmp_path / "weknora-api-key"
values = {
key: value
for key, value in BASE_SETTINGS.items()
if key != "WEKNORA_API_KEY"
}
values["WEKNORA_API_KEY_FILE"] = secret_file
settings = Settings(**values)
assert settings.weknora_api_key_file == secret_file
assert not hasattr(settings, "weknora_api_key")
def test_settings_reads_weknora_api_key_at_runtime(tmp_path: Path) -> None:
secret_file = tmp_path / "weknora-api-key"
secret_file.write_text("weknora-test-secret\n", encoding="utf-8")
values = {
key: value
for key, value in BASE_SETTINGS.items()
if key != "WEKNORA_API_KEY"
}
values["WEKNORA_API_KEY_FILE"] = secret_file
settings = Settings(**values)
api_key = settings.read_weknora_api_key()
assert isinstance(api_key, SecretStr)
assert api_key.get_secret_value() == "weknora-test-secret"
assert "weknora-test-secret" not in repr(settings)
def test_static_ak_mode_reads_secret_files_without_ram_role(tmp_path: Path) -> None:
access_key_id_file = tmp_path / "oss-access-key-id"
access_key_secret_file = tmp_path / "oss-access-key-secret"
access_key_id_file.write_text("test-access-key-id\n", encoding="utf-8")
access_key_secret_file.write_text("test-access-key-secret\n", encoding="utf-8")
values = {
**BASE_SETTINGS,
"OSS_CREDENTIAL_MODE": "static_ak",
"OSS_RAM_ROLE_NAME": None,
"OSS_ACCESS_KEY_ID_FILE": access_key_id_file,
"OSS_ACCESS_KEY_SECRET_FILE": access_key_secret_file,
}
settings = Settings(**values)
assert settings.oss_credential_mode == "static_ak"
assert settings.oss_ram_role_name is None
assert settings.read_oss_access_key_id().get_secret_value() == "test-access-key-id"
assert settings.read_oss_access_key_secret().get_secret_value() == "test-access-key-secret"
assert "test-access-key-id" not in repr(settings)
assert "test-access-key-secret" not in repr(settings)
@pytest.mark.parametrize("content", [None, " \n"])
def test_settings_rejects_unavailable_weknora_api_key_secret(
tmp_path: Path,
content: str | None,
) -> None:
secret_file = tmp_path / "weknora-api-key"
if content is not None:
secret_file.write_text(content, encoding="utf-8")
values = {
key: value
for key, value in BASE_SETTINGS.items()
if key != "WEKNORA_API_KEY"
}
values["WEKNORA_API_KEY_FILE"] = secret_file
settings = Settings(**values)
with pytest.raises(RuntimeError, match=r"^required secret unavailable$"):
settings.read_weknora_api_key()
def test_settings_reject_chunk_limit_smaller_than_truncation_marker() -> None:
values = {**BASE_SETTINGS, "MAX_CHUNK_CHARS": len(TRUNCATION_MARKER) - 1}
with pytest.raises(ValidationError):
Settings(**values)
@pytest.mark.parametrize(
("name", "value"),
[
("OSS_REGION", "https://attacker.example"),
("OSS_REGION", "cn-shenzhen/internal"),
("OSS_BUCKET", "Private_Assets"),
("OSS_BUCKET", "https://attacker.example"),
("OSS_RAM_ROLE_NAME", ""),
("OSS_RAM_ROLE_NAME", "role/name"),
],
)
def test_settings_reject_unsafe_oss_identifiers(name: str, value: str) -> None:
values = {**BASE_SETTINGS, name: value}
with pytest.raises(ValidationError):
Settings(**values)