2026-03-12 17:11:14 +08:00
|
|
|
import pytest
|
|
|
|
|
|
2026-03-05 23:22:25 +08:00
|
|
|
from src.api.admin.system import AdminExportConfigAdapter, AdminImportConfigAdapter
|
2026-03-12 17:11:14 +08:00
|
|
|
from src.core.exceptions import InvalidRequestException
|
2026-03-06 21:06:45 +08:00
|
|
|
from src.services.provider_ops.types import SENSITIVE_CREDENTIAL_FIELDS
|
2026-03-05 23:22:25 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_export_key_api_formats_falls_back_to_provider_endpoints_when_none() -> None:
|
|
|
|
|
adapter = AdminExportConfigAdapter()
|
|
|
|
|
|
|
|
|
|
result = adapter._resolve_export_key_api_formats(
|
|
|
|
|
None,
|
|
|
|
|
["claude:chat", "openai:cli"],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert result == ["claude:chat", "openai:cli"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_export_key_api_formats_keeps_explicit_empty_list() -> None:
|
|
|
|
|
adapter = AdminExportConfigAdapter()
|
|
|
|
|
|
|
|
|
|
result = adapter._resolve_export_key_api_formats(
|
|
|
|
|
[],
|
|
|
|
|
["openai:chat"],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert result == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_export_key_api_formats_normalizes_and_deduplicates() -> None:
|
|
|
|
|
adapter = AdminExportConfigAdapter()
|
|
|
|
|
|
|
|
|
|
result = adapter._resolve_export_key_api_formats(
|
|
|
|
|
[" OPENAI:CHAT ", "openai:chat", "openai:cli", "bad-format"],
|
|
|
|
|
["claude:chat"],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert result == ["openai:chat", "openai:cli"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_import_key_api_formats_uses_supported_endpoints_alias() -> None:
|
|
|
|
|
result = AdminImportConfigAdapter._extract_import_key_api_formats(
|
|
|
|
|
{"supported_endpoints": ["openai:chat"]},
|
|
|
|
|
{"openai:chat", "openai:cli"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert result == ["openai:chat"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_import_key_api_formats_falls_back_to_provider_endpoints_when_none() -> None:
|
|
|
|
|
result = AdminImportConfigAdapter._extract_import_key_api_formats(
|
|
|
|
|
{"api_formats": None},
|
|
|
|
|
{"openai:chat", "claude:cli"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert result == ["claude:cli", "openai:chat"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_import_key_api_formats_keeps_explicit_empty_list() -> None:
|
|
|
|
|
result = AdminImportConfigAdapter._extract_import_key_api_formats(
|
|
|
|
|
{"api_formats": []},
|
|
|
|
|
{"openai:chat"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert result == []
|
2026-03-06 21:06:45 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class _FakeCrypto:
|
|
|
|
|
def encrypt(self, value: str) -> str:
|
|
|
|
|
return f"enc:{value}"
|
|
|
|
|
|
|
|
|
|
def decrypt(self, value: str) -> str:
|
|
|
|
|
return value.removeprefix("enc:")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_provider_ops_sensitive_fields_include_refresh_token() -> None:
|
|
|
|
|
assert "refresh_token" in SENSITIVE_CREDENTIAL_FIELDS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_export_provider_config_decrypts_refresh_token() -> None:
|
|
|
|
|
adapter = AdminExportConfigAdapter()
|
|
|
|
|
|
|
|
|
|
config = {
|
|
|
|
|
"provider_ops": {
|
|
|
|
|
"connector": {
|
|
|
|
|
"credentials": {
|
|
|
|
|
"refresh_token": "enc:rt-1",
|
|
|
|
|
"api_key": "enc:key-1",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = adapter._decrypt_provider_config(config, _FakeCrypto())
|
|
|
|
|
|
|
|
|
|
assert result["provider_ops"]["connector"]["credentials"]["refresh_token"] == "rt-1"
|
|
|
|
|
assert result["provider_ops"]["connector"]["credentials"]["api_key"] == "key-1"
|
|
|
|
|
assert config["provider_ops"]["connector"]["credentials"]["refresh_token"] == "enc:rt-1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_import_provider_config_encrypts_refresh_token() -> None:
|
|
|
|
|
adapter = AdminImportConfigAdapter()
|
|
|
|
|
|
|
|
|
|
config = {
|
|
|
|
|
"provider_ops": {
|
|
|
|
|
"connector": {
|
|
|
|
|
"credentials": {
|
|
|
|
|
"refresh_token": "rt-1",
|
|
|
|
|
"api_key": "key-1",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = adapter._encrypt_provider_config(config, _FakeCrypto())
|
|
|
|
|
|
|
|
|
|
assert result["provider_ops"]["connector"]["credentials"]["refresh_token"] == "enc:rt-1"
|
|
|
|
|
assert result["provider_ops"]["connector"]["credentials"]["api_key"] == "enc:key-1"
|
|
|
|
|
assert config["provider_ops"]["connector"]["credentials"]["refresh_token"] == "rt-1"
|
2026-03-12 17:11:14 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_import_endpoint_payload_rejects_dict_base_url() -> None:
|
|
|
|
|
with pytest.raises(InvalidRequestException, match="导入 Endpoint 失败"):
|
|
|
|
|
AdminImportConfigAdapter._normalize_import_endpoint_payload(
|
|
|
|
|
"provider-1",
|
|
|
|
|
{
|
|
|
|
|
"api_format": "claude:chat",
|
|
|
|
|
"base_url": {"url": "https://api.anthropic.com"},
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_import_endpoint_payload_normalizes_base_url() -> None:
|
|
|
|
|
result = AdminImportConfigAdapter._normalize_import_endpoint_payload(
|
|
|
|
|
"provider-1",
|
|
|
|
|
{
|
|
|
|
|
"api_format": "claude:chat",
|
|
|
|
|
"base_url": "https://api.anthropic.com/",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert result["base_url"] == "https://api.anthropic.com"
|
|
|
|
|
assert result["api_format"] == "claude:chat"
|