Files
RAG/backend/tests/unit/test_config_and_secrets.py
YoVinchen 75592af33a
Some checks failed
verify / verify (push) Has been cancelled
Isolate cloud model access before enabling product RAG workflows
The API and ingestion tools now use a fixed internal model gateway while
governed profiles, embedding cache assignments, traceable citations, and
stable API errors establish the boundaries required by later workflows.

Constraint: The current Alibaba Cloud workspace rejects all three live model calls with authentication failures
Rejected: Give the API or seed tools the Bailian key and direct egress | combines database access, cloud credentials, and public network access
Rejected: Mix offline and Bailian vectors in one demo namespace | makes profile activation and retrieval ambiguous
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep Bailian credentials and egress exclusive to model-gateway and create a new immutable profile hash for any embedding identity change
Tested: make verify; 121 backend tests; 14 frontend tests; fresh and populated Alembic upgrade-downgrade-upgrade; two idempotent offline seeds; Docker health and HTTP retrieval; isolated provider smoke
Not-tested: Successful live Bailian responses because the supplied workspace credential currently fails authentication
2026-07-13 04:09:06 +08:00

122 lines
4.2 KiB
Python

from pathlib import Path
import pytest
from app.core.config import Settings
from app.core.secrets import SecretFileError, read_secret_file
def test_secret_file_strips_single_trailing_newline(tmp_path: Path) -> None:
secret_path = tmp_path / "secret"
secret_path.write_text("test-only-value\n", encoding="utf-8")
assert read_secret_file(secret_path) == "test-only-value"
def test_secret_error_never_contains_secret_value(tmp_path: Path) -> None:
secret_path = tmp_path / "secret"
secret_path.write_text("first\nsecond\n", encoding="utf-8")
with pytest.raises(SecretFileError) as captured:
read_secret_file(secret_path)
assert "first" not in str(captured.value)
assert "second" not in str(captured.value)
def test_database_url_reads_password_from_file(tmp_path: Path) -> None:
password_path = tmp_path / "postgres_password"
password_path.write_text("local-test-password", encoding="utf-8")
settings = Settings(postgres_password_file=password_path)
url = settings.database_url()
assert url.password == "local-test-password"
assert "local-test-password" not in str(url)
def test_base_urls_drop_trailing_slashes() -> None:
settings = Settings(
bailian_openai_base_url="https://example.invalid/compatible-mode/v1/",
bailian_native_base_url="https://example.invalid/api/v1/",
bailian_rerank_base_url="https://example.invalid/compatible-api/v1/",
)
assert settings.bailian_openai_base_url.endswith("/v1")
assert settings.bailian_rerank_base_url.endswith("/v1")
def test_model_gateway_url_is_fixed_to_internal_service() -> None:
settings = Settings(model_gateway_base_url="http://model-gateway:8000/")
assert settings.model_gateway_base_url == "http://model-gateway:8000"
@pytest.mark.parametrize(
"value",
[
"https://model-gateway:8000",
"http://model-gateway:9000",
"http://127.0.0.1:8000",
"http://model-gateway:8000/proxy",
"http://user:model-token@model-gateway:8000",
],
)
def test_model_gateway_url_rejects_ssrf_and_credential_variants(value: str) -> None:
with pytest.raises(ValueError, match="fixed internal service URL"):
Settings(model_gateway_base_url=value)
def test_embedding_dimension_accepts_compose_string(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("EMBEDDING_DIMENSION", "1024")
settings = Settings(_env_file=None)
assert settings.embedding_dimension == 1024
assert isinstance(settings.embedding_dimension, int)
@pytest.mark.parametrize("configured", ["1536", "1024.0", "01024", " 1024 "])
def test_embedding_dimension_rejects_any_other_environment_value(
monkeypatch: pytest.MonkeyPatch,
configured: str,
) -> None:
monkeypatch.setenv("EMBEDDING_DIMENSION", configured)
with pytest.raises(ValueError, match="EMBEDDING_DIMENSION must be exactly 1024"):
Settings(_env_file=None)
@pytest.mark.parametrize("configured", [1024.0, True, None])
def test_embedding_dimension_rejects_non_integer_programmatic_values(configured: object) -> None:
with pytest.raises(ValueError, match="EMBEDDING_DIMENSION must be exactly 1024"):
Settings.model_validate({"embedding_dimension": configured})
def test_live_endpoints_must_share_one_beijing_workspace(tmp_path: Path) -> None:
key_path = tmp_path / "key"
key_path.write_text("test-key-value", encoding="utf-8")
settings = Settings(
dashscope_api_key_file=key_path,
bailian_openai_base_url=(
"https://workspace-a.cn-beijing.maas.aliyuncs.com/compatible-mode/v1"
),
bailian_rerank_base_url=(
"https://workspace-b.cn-beijing.maas.aliyuncs.com/compatible-api/v1"
),
)
with pytest.raises(ValueError, match="same workspace"):
settings.bailian_api_key()
def test_live_endpoint_rejects_non_bailian_host_before_reading_key(tmp_path: Path) -> None:
settings = Settings(
dashscope_api_key_file=tmp_path / "missing",
bailian_openai_base_url="https://attacker.invalid/compatible-mode/v1",
bailian_rerank_base_url="https://attacker.invalid/compatible-api/v1",
)
with pytest.raises(ValueError, match="approved Beijing"):
settings.bailian_api_key()