Files
g2s-aggregator/tests/config/test_config_sections.py
Раис Юсупалиев 4262b8a200
Deploy / deploy (push) Successful in 57s
fix ксе tariffs
2026-06-27 17:34:06 +03:00

433 lines
14 KiB
Python

from decimal import Decimal
from pathlib import Path
import pytest
from pydantic import ValidationError
from app import config as config_module
from app.config import Settings, get_settings
PROJECT_ROOT = Path(__file__).resolve().parents[2]
TEST_CONFIG_FILE = PROJECT_ROOT / "config.test.yaml"
MISSING_REQUIRED_SECTION_CONFIG_FILE = (
PROJECT_ROOT / "tests" / "config" / "fixtures" / "config.missing_adapter.yaml"
)
MISSING_ADDRESS_SUGGESTIONS_CONFIG_FILE = (
PROJECT_ROOT
/ "tests"
/ "config"
/ "fixtures"
/ "config.missing_address_suggestions.yaml"
)
DEFAULT_CONFIG_FILE_FIXTURE = (
PROJECT_ROOT / "tests" / "config" / "fixtures" / "config.default.yaml"
)
TEST_OVERRIDE_CONFIG_FILE_FIXTURE = (
PROJECT_ROOT / "tests" / "config" / "fixtures" / "config.test.override.yaml"
)
INVALID_PRICE_MULTIPLIER_CONFIG_FILE = (
PROJECT_ROOT / "tests" / "config" / "fixtures" / "config.invalid_price_multiplier.yaml"
)
MISSING_PRICE_MULTIPLIER_CONFIG_FILE = (
PROJECT_ROOT / "tests" / "config" / "fixtures" / "config.missing_price_multiplier.yaml"
)
MISSING_OBSERVABILITY_CONFIG_FILE = (
PROJECT_ROOT / "tests" / "config" / "fixtures" / "config.missing_observability.yaml"
)
MISSING_OBSERVABILITY_ENDPOINT_CONFIG_FILE = (
PROJECT_ROOT
/ "tests"
/ "config"
/ "fixtures"
/ "config.missing_observability_endpoint.yaml"
)
_DADATA_COUNTRIES = ("RU", "BY", "KZ")
_YANDEX_COUNTRIES = ("AM", "AZ", "KG", "MD", "TJ", "TM", "UZ")
_TOMTOM_COUNTRIES = (
"AL",
"AT",
"BE",
"BG",
"CH",
"CZ",
"DE",
"DK",
"EE",
"ES",
"FI",
"FR",
"GB",
"GR",
"HR",
"HU",
"IE",
"IT",
"LT",
"LV",
"NL",
"NO",
"PL",
"PT",
"RO",
"RS",
"SE",
"SI",
"SK",
"UA",
)
def _expected_country_mapping() -> dict[str, str]:
return {
**{country_code: "dadata" for country_code in _DADATA_COUNTRIES},
**{
country_code: "yandex_geosuggest"
for country_code in _YANDEX_COUNTRIES
},
**{country_code: "tomtom" for country_code in _TOMTOM_COUNTRIES},
}
def _use_runtime_config_files(
monkeypatch: pytest.MonkeyPatch,
*,
test_config_file: Path,
default_config_file: Path | None = None,
) -> None:
monkeypatch.setattr(config_module, "TEST_CONFIG_FILE", str(test_config_file))
if default_config_file is not None:
monkeypatch.setattr(
config_module,
"DEFAULT_CONFIG_FILE",
str(default_config_file),
)
get_settings.cache_clear()
def _write_tbank_url_validation_config(
tmp_path: Path,
*,
include_notification_url: bool = True,
include_success_url: bool = True,
) -> Path:
notification_url = (
' notification_url: "https://merchant.test/api/v1/delivery/tbank/notifications"\n'
if include_notification_url
else ""
)
success_url = (
' success_url: "https://merchant.test/payment/success"\n'
if include_success_url
else ""
)
config_file = tmp_path / "config.yaml"
config_file.write_text(
f"""
controller: {{}}
service: {{}}
business_logic:
provider_price_multiplier: 1.0
repository: {{}}
delivery_providers: {{}}
tbank_payment:
init_url: "https://securepay.tinkoff.ru/v2/Init"
{notification_url}{success_url} auth:
terminal_key: "test-terminal-key"
password: "test-password"
postgres:
dsn: "postgresql+asyncpg://postgres:postgres@localhost:5432/config_test"
address_suggestions: {{}}
observability:
service_name: "config-test"
otlp_endpoint: "http://collector:4317"
email:
smtp_host: "smtp.test"
smtp_port: 587
from_address: "no-reply@test"
""".strip(),
encoding="utf-8",
)
return config_file
def test_configuration_sections_are_loaded_from_yaml_file(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_use_runtime_config_files(monkeypatch, test_config_file=TEST_CONFIG_FILE)
settings = Settings()
assert settings.controller.api_prefix == "/api/v1"
assert settings.controller.request_id_header == "X-Request-ID"
assert settings.service.provider_timeout_seconds == 10.0
assert settings.service.max_parallel_providers == 8
assert settings.business_logic.weight_round_scale == 2
assert settings.business_logic.provider_price_multiplier == Decimal("1.0")
assert settings.repository.redis_dsn == "redis://localhost:6379/0"
assert settings.repository.price_cache_ttl_seconds == 900
assert settings.delivery_providers.cdek.enabled is True
assert settings.delivery_providers.cdek.base_url == "https://api.cdek.ru/v2"
assert settings.delivery_providers.cdek.client_id == "test-client-id"
assert settings.delivery_providers.cdek.client_secret == "test-client-secret"
assert settings.delivery_providers.cdek.retry_attempts == 2
assert settings.delivery_providers.cdek.retry_backoff_seconds == 0.2
assert settings.delivery_providers.cdek.timeout_seconds == 10.0
assert settings.delivery_providers.cdek.cache_ttl_seconds == 900
assert settings.delivery_providers.cse.enabled is True
assert (
settings.delivery_providers.cse.base_url
== "http://lk-test.cse.ru/1c/ws/web1c.1cws"
)
assert settings.delivery_providers.cse.login == "test"
assert settings.delivery_providers.cse.password == "2016"
assert settings.delivery_providers.cse.retry_attempts == 2
assert settings.delivery_providers.cse.retry_backoff_seconds == 0.2
assert settings.delivery_providers.cse.timeout_seconds == 10.0
assert settings.delivery_providers.cse.cache_ttl_seconds == 900
assert settings.delivery_providers.cse.delivery_service_guids == ["tariff-guid-2"]
assert settings.delivery_providers.cse.payer == "0"
assert settings.delivery_providers.cse.payment_method == "1"
assert (
settings.delivery_providers.cse.shipping_method
== "5052d0b3-5ea3-46f2-823f-1472686a51dd"
)
assert settings.tbank_payment.init_url == "https://securepay.tinkoff.ru/v2/Init"
assert (
settings.tbank_payment.notification_url
== "https://merchant.test/api/v1/delivery/tbank/notifications"
)
assert settings.tbank_payment.success_url == "https://merchant.test/payment/success"
assert settings.tbank_payment.auth.terminal_key == "test-terminal-key"
assert settings.tbank_payment.auth.password == "test-password"
assert settings.tbank_payment.timeout_seconds == 10.0
assert settings.tbank_payment.retry_attempts == 2
assert settings.tbank_payment.retry_backoff_seconds == 0.2
assert (
settings.postgres.dsn
== "postgresql+asyncpg://postgres:postgres@localhost:5432/g2s_aggregator_test"
)
assert settings.address_suggestions.country_to_provider == _expected_country_mapping()
assert (
settings.address_suggestions.dadata.url
== "https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/address"
)
assert settings.address_suggestions.dadata.api_key == "test-dadata-api-key"
assert settings.address_suggestions.dadata.timeout_seconds == 7.5
assert (
settings.address_suggestions.yandex_geosuggest.url
== "https://suggest-maps.yandex.ru/v1/suggest"
)
assert (
settings.address_suggestions.yandex_geosuggest.api_key
== "test-yandex-geosuggest-api-key"
)
assert settings.address_suggestions.yandex_geosuggest.timeout_seconds == 6.5
assert settings.address_suggestions.tomtom.url == "https://api.tomtom.com/search/2/search"
assert settings.address_suggestions.tomtom.api_key == "test-tomtom-api-key"
assert settings.address_suggestions.tomtom.timeout_seconds == 5.5
assert settings.observability.enabled is False
assert settings.observability.service_name == "g2s-aggregator-test"
assert settings.observability.otlp_endpoint == "http://localhost:4317"
assert settings.observability.otlp_insecure is True
def test_get_settings_fails_when_required_yaml_section_is_missing(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_use_runtime_config_files(
monkeypatch,
test_config_file=MISSING_REQUIRED_SECTION_CONFIG_FILE,
)
with pytest.raises(ValidationError) as error:
get_settings()
locations = {tuple(item["loc"]) for item in error.value.errors()}
assert ("delivery_providers",) in locations
get_settings.cache_clear()
def test_get_settings_fails_when_address_suggestions_section_is_missing(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_use_runtime_config_files(
monkeypatch,
test_config_file=MISSING_ADDRESS_SUGGESTIONS_CONFIG_FILE,
)
with pytest.raises(ValidationError) as error:
get_settings()
locations = {tuple(item["loc"]) for item in error.value.errors()}
assert ("address_suggestions",) in locations
get_settings.cache_clear()
def test_get_settings_returns_cached_instance(monkeypatch: pytest.MonkeyPatch) -> None:
_use_runtime_config_files(monkeypatch, test_config_file=TEST_CONFIG_FILE)
first = get_settings()
second = get_settings()
assert first is second
assert first.service.provider_timeout_seconds == 10.0
assert first.business_logic.provider_price_multiplier == Decimal("1.0")
assert first.tbank_payment.auth.terminal_key == "test-terminal-key"
assert (
first.tbank_payment.notification_url
== "https://merchant.test/api/v1/delivery/tbank/notifications"
)
assert first.postgres.dsn.endswith("/g2s_aggregator_test")
assert first.address_suggestions.country_to_provider["RU"] == "dadata"
assert first.observability.service_name == "g2s-aggregator-test"
get_settings.cache_clear()
def test_get_settings_uses_config_test_yaml_in_pytest_environment(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_use_runtime_config_files(
monkeypatch,
test_config_file=TEST_OVERRIDE_CONFIG_FILE_FIXTURE,
default_config_file=DEFAULT_CONFIG_FILE_FIXTURE,
)
settings = get_settings()
assert settings.service.provider_timeout_seconds == 17.0
assert settings.controller.api_prefix == "/from-config-test-yaml"
assert settings.business_logic.provider_price_multiplier == Decimal("1.25")
assert settings.delivery_providers.cdek.client_id == "test-id"
assert settings.tbank_payment.auth.terminal_key == "override-terminal-key"
assert settings.tbank_payment.auth.password == "override-password"
assert (
settings.tbank_payment.notification_url
== "https://override.test/api/v1/delivery/tbank/notifications"
)
assert settings.tbank_payment.success_url == "https://override.test/payment/success"
assert settings.tbank_payment.timeout_seconds == 4.25
assert settings.tbank_payment.retry_attempts == 1
assert settings.tbank_payment.retry_backoff_seconds == 0.05
assert (
settings.postgres.dsn
== "postgresql+asyncpg://postgres:postgres@localhost:5432/override"
)
assert settings.address_suggestions.country_to_provider == {
"RU": "dadata",
"AM": "yandex_geosuggest",
"DE": "tomtom",
}
assert settings.address_suggestions.dadata.api_key == "override-dadata-key"
assert settings.address_suggestions.dadata.timeout_seconds == 3.0
assert settings.address_suggestions.yandex_geosuggest.api_key == "override-yandex-key"
assert settings.address_suggestions.yandex_geosuggest.timeout_seconds == 4.5
assert settings.address_suggestions.tomtom.api_key == "override-tomtom-key"
assert settings.address_suggestions.tomtom.timeout_seconds == 5.25
assert settings.observability.enabled is True
assert settings.observability.service_name == "override-config-service"
assert settings.observability.otlp_endpoint == "http://override-collector:4317"
assert settings.observability.otlp_insecure is False
get_settings.cache_clear()
def test_get_settings_fails_when_provider_price_multiplier_is_invalid(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_use_runtime_config_files(
monkeypatch,
test_config_file=INVALID_PRICE_MULTIPLIER_CONFIG_FILE,
)
with pytest.raises(ValidationError) as error:
get_settings()
locations = {tuple(item["loc"]) for item in error.value.errors()}
assert ("business_logic", "provider_price_multiplier") in locations
get_settings.cache_clear()
def test_get_settings_fails_when_provider_price_multiplier_is_missing(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_use_runtime_config_files(
monkeypatch,
test_config_file=MISSING_PRICE_MULTIPLIER_CONFIG_FILE,
)
with pytest.raises(ValidationError) as error:
get_settings()
locations = {tuple(item["loc"]) for item in error.value.errors()}
assert ("business_logic", "provider_price_multiplier") in locations
get_settings.cache_clear()
@pytest.mark.parametrize(
("include_notification_url", "include_success_url", "expected_location"),
(
(False, True, ("tbank_payment", "notification_url")),
(True, False, ("tbank_payment", "success_url")),
),
)
def test_get_settings_fails_when_tbank_payment_url_is_missing(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
include_notification_url: bool,
include_success_url: bool,
expected_location: tuple[str, str],
) -> None:
config_file = _write_tbank_url_validation_config(
tmp_path,
include_notification_url=include_notification_url,
include_success_url=include_success_url,
)
_use_runtime_config_files(monkeypatch, test_config_file=config_file)
with pytest.raises(ValidationError) as error:
get_settings()
locations = {tuple(item["loc"]) for item in error.value.errors()}
assert expected_location in locations
get_settings.cache_clear()
def test_get_settings_fails_when_observability_section_is_missing(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_use_runtime_config_files(
monkeypatch,
test_config_file=MISSING_OBSERVABILITY_CONFIG_FILE,
)
with pytest.raises(ValidationError) as error:
get_settings()
locations = {tuple(item["loc"]) for item in error.value.errors()}
assert ("observability",) in locations
get_settings.cache_clear()
def test_get_settings_fails_when_observability_endpoint_is_missing(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_use_runtime_config_files(
monkeypatch,
test_config_file=MISSING_OBSERVABILITY_ENDPOINT_CONFIG_FILE,
)
with pytest.raises(ValidationError) as error:
get_settings()
locations = {tuple(item["loc"]) for item in error.value.errors()}
assert ("observability", "otlp_endpoint") in locations
get_settings.cache_clear()