- Introduced a new `LOG_LEVEL` configuration option in the `.env.example` file to allow users to set the logging level (DEBUG, INFO, WARNING, ERROR). - Updated the `Settings` class to include the `log_level` attribute, normalizing its value to ensure valid logging levels are used. - Modified the logging setup in `run.py` to utilize the configured log level, enhancing flexibility in log management. - Enhanced the Mini App to include the logging level in the JavaScript configuration, allowing for consistent logging behavior across the application. - Added a new `logger.js` module for frontend logging, implementing level-based filtering and console delegation. - Included unit tests for the new logger functionality to ensure proper behavior and level handling.
107 lines
4.5 KiB
Python
107 lines
4.5 KiB
Python
"""Tests for duty_teller.api.dependencies (lang, auth error, date validation, private client)."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
import duty_teller.api.dependencies as deps
|
|
import duty_teller.config as config
|
|
|
|
|
|
class TestLangFromAcceptLanguage:
|
|
"""Tests for _lang_from_accept_language: always returns config.DEFAULT_LANGUAGE."""
|
|
|
|
def test_always_returns_default_language(self):
|
|
"""Header is ignored; result is always config.DEFAULT_LANGUAGE."""
|
|
assert deps._lang_from_accept_language(None) == config.DEFAULT_LANGUAGE
|
|
assert deps._lang_from_accept_language("") == config.DEFAULT_LANGUAGE
|
|
assert deps._lang_from_accept_language(" ") == config.DEFAULT_LANGUAGE
|
|
assert (
|
|
deps._lang_from_accept_language("ru-RU,ru;q=0.9") == config.DEFAULT_LANGUAGE
|
|
)
|
|
assert deps._lang_from_accept_language("en-US") == config.DEFAULT_LANGUAGE
|
|
assert deps._lang_from_accept_language("zz") == config.DEFAULT_LANGUAGE
|
|
assert deps._lang_from_accept_language("x") == config.DEFAULT_LANGUAGE
|
|
|
|
def test_returns_ru_when_default_language_is_ru(self):
|
|
with patch.object(config, "DEFAULT_LANGUAGE", "ru"):
|
|
assert deps._lang_from_accept_language("en-US") == "ru"
|
|
assert deps._lang_from_accept_language(None) == "ru"
|
|
|
|
def test_returns_en_when_default_language_is_en(self):
|
|
with patch.object(config, "DEFAULT_LANGUAGE", "en"):
|
|
assert deps._lang_from_accept_language("ru-RU") == "en"
|
|
assert deps._lang_from_accept_language(None) == "en"
|
|
|
|
|
|
class TestAuthErrorDetail:
|
|
"""Tests for _auth_error_detail."""
|
|
|
|
def test_hash_mismatch_uses_bad_signature_key(self):
|
|
with patch("duty_teller.api.dependencies.t") as mock_t:
|
|
mock_t.return_value = "Bad signature"
|
|
result = deps._auth_error_detail("hash_mismatch", "en")
|
|
assert result == "Bad signature"
|
|
mock_t.assert_called_once_with("en", "api.auth_bad_signature")
|
|
|
|
def test_other_reason_uses_auth_invalid_key(self):
|
|
with patch("duty_teller.api.dependencies.t") as mock_t:
|
|
mock_t.return_value = "Invalid auth"
|
|
result = deps._auth_error_detail("expired", "ru")
|
|
assert result == "Invalid auth"
|
|
mock_t.assert_called_once_with("ru", "api.auth_invalid")
|
|
|
|
|
|
class TestValidateDutyDates:
|
|
"""Tests for _validate_duty_dates."""
|
|
|
|
def test_valid_range_no_exception(self):
|
|
deps._validate_duty_dates("2025-01-01", "2025-01-31", "en")
|
|
|
|
def test_bad_format_raises_400_with_i18n(self):
|
|
with patch("duty_teller.api.dependencies.t") as mock_t:
|
|
mock_t.return_value = "Bad format message"
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
deps._validate_duty_dates("01-01-2025", "2025-01-31", "en")
|
|
assert exc_info.value.status_code == 400
|
|
assert exc_info.value.detail == "Bad format message"
|
|
mock_t.assert_called_with("en", "dates.bad_format")
|
|
|
|
def test_from_after_to_raises_400_with_i18n(self):
|
|
with patch("duty_teller.api.dependencies.t") as mock_t:
|
|
mock_t.return_value = "From after to message"
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
deps._validate_duty_dates("2025-02-01", "2025-01-01", "ru")
|
|
assert exc_info.value.status_code == 400
|
|
assert exc_info.value.detail == "From after to message"
|
|
mock_t.assert_called_with("ru", "dates.from_after_to")
|
|
|
|
|
|
class TestFetchDutiesResponse:
|
|
"""Tests for fetch_duties_response (DutyWithUser list with phone, username)."""
|
|
|
|
def test_fetch_duties_response_includes_phone_and_username(self):
|
|
"""get_duties returns (Duty, full_name, phone, username); response has phone, username."""
|
|
from types import SimpleNamespace
|
|
|
|
from duty_teller.db.schemas import DutyWithUser
|
|
|
|
duty = SimpleNamespace(
|
|
id=1,
|
|
user_id=10,
|
|
start_at="2025-01-15T09:00:00Z",
|
|
end_at="2025-01-15T18:00:00Z",
|
|
event_type="duty",
|
|
)
|
|
rows = [(duty, "Alice", "+79001234567", "alice_dev")]
|
|
with patch.object(deps, "get_duties", return_value=rows):
|
|
result = deps.fetch_duties_response(
|
|
type("Session", (), {})(), "2025-01-01", "2025-01-31"
|
|
)
|
|
assert len(result) == 1
|
|
assert isinstance(result[0], DutyWithUser)
|
|
assert result[0].full_name == "Alice"
|
|
assert result[0].phone == "+79001234567"
|
|
assert result[0].username == "alice_dev"
|