Files
blog.pyaqa.ru/tests/FEATURE_INFRASTRUCTURE.md
Sergey Vanyushkin d32ad29abc
Some checks failed
ci/woodpecker/pr/pipeline Pipeline failed
feat(i18n): add browser-language localization with Jinja2 _() and locale middleware
Add i18n support to the blog web UI with 4 languages (en/ru/fr/de),
80 translation keys, automatic Accept-Language detection, persistent
locale cookie, and a language switcher dropdown in the header.

- Infrastructure: TranslationService, translation dicts, convenience _()
- Presentation: locale middleware, /web/lang/{locale} switcher route
- Templates: all 9 templates use {{ _(key, current_locale) }}
- Tests: 26 tests across TranslationService, locale detection helpers
- Docs: TEST_MODEL.md and FEATURE_INFRASTRUCTURE.md updated with TC-UNIT-811-821
2026-05-10 16:22:06 +03:00

18 KiB

Test Model: Infrastructure & Bootstrap

Feature: Application initialization, configuration, authentication client, and transaction management. These tests validate the plumbing layer that supports the domain and application layers.

Unit Test Cases

App Bootstrap

TC-UNIT-501: Lifespan — Init and Close

  • Type: Positive
  • Layer: Unit
  • File: unit/test_main.py::test_lifespan
  • Preconditions: Mock init_db and close_db
  • Steps: Enter and exit lifespan context manager
  • Expected:
    • init_db called once on enter
    • close_db called once on exit
    • close_db not called during context
  • Last Verified: 2026-05-07

TC-UNIT-502: App Factory — Creates FastAPI App

  • Type: Positive
  • Layer: Unit
  • File: unit/test_main.py::test_app_factory
  • Expected: Returns FastAPI instance
  • Last Verified: 2026-05-07

TC-UNIT-503: App Factory — Has Routes

  • Type: Positive
  • Layer: Unit
  • File: unit/test_main.py::test_app_factory_has_routes
  • Expected: /health route exists; API routes registered
  • Last Verified: 2026-05-07

TC-UNIT-504: Main — Starts Uvicorn

  • Type: Positive
  • Layer: Unit
  • File: unit/test_main.py::test_main
  • Preconditions: Mock uvicorn.run
  • Expected:
    • uvicorn.run called with factory=True
    • host="0.0.0.0", port=8000
  • Last Verified: 2026-05-07

Configuration

TC-UNIT-601: Settings — Default Values

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestSettings::test_default_values
  • Expected:
    • app.name == "Blog API"
    • app.debug is False
    • database_url == "sqlite+aiosqlite:///./blog.db" (dev default)
    • environment == Environment.DEV
  • Last Verified: 2026-05-07

TC-UNIT-602: Settings — Custom Values

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestSettings::test_custom_values
  • Expected: Custom app, db, and env values applied correctly
  • Last Verified: 2026-05-07

TC-UNIT-603: Settings — is_dev / is_prod Properties

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestSettings::test_is_dev_property, test_is_prod_property
  • Expected: Boolean properties match environment enum
  • Last Verified: 2026-05-07

TC-UNIT-604: Settings — Prod Requires Security Secret

  • Type: Negative
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestSettings::test_prod_requires_security_secret
  • Expected: Raises ValueError with SECURITY_SECRET_KEY when secret is empty in prod
  • Last Verified: 2026-05-07

TC-UNIT-605: Settings — Prod Requires KC Secret

  • Type: Negative
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestSettings::test_prod_requires_kc_secret
  • Expected: Raises ValueError with KC_CLIENT_SECRET when KC secret is empty in prod
  • Last Verified: 2026-05-07

TC-UNIT-606: Settings — Database URL Dev Default

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestSettings::test_database_url_dev_default
  • Expected: Dev mode defaults to SQLite async URL
  • Last Verified: 2026-05-07

TC-UNIT-607: Settings — Database URL Prod Builds Postgres

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestSettings::test_database_url_prod_builds_postgres
  • Expected: When db.url is None in prod, URL built from host/port/user/password/name
  • Last Verified: 2026-05-07

TC-UNIT-608: Settings — Database URL Override

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestSettings::test_database_url_override
  • Expected: Explicit db.url overrides auto-building in prod
  • Last Verified: 2026-05-07

TC-UNIT-609: AppConfig — Defaults

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestAppConfig::test_default_values
  • Expected: Default name, debug, host, port values
  • Last Verified: 2026-05-07

TC-UNIT-610: DBConfig — Defaults

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestDBConfig::test_default_values
  • Expected: Default PostgreSQL connection params
  • Last Verified: 2026-05-07

TC-UNIT-611: DBConfig — URL Validation (Postgres)

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestDBConfig::test_postgres_url_validation
  • Expected: Postgres URL accepted
  • Last Verified: 2026-05-07

TC-UNIT-612: DBConfig — URL Validation (SQLite)

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestDBConfig::test_sqlite_url_validation
  • Expected: SQLite URL accepted
  • Last Verified: 2026-05-07

TC-UNIT-613: DBConfig — URL Validation Rejects Invalid

  • Type: Negative
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestDBConfig::test_invalid_url_validation
  • Expected: Raises ValueError for non-SQLite/non-Postgres URLs
  • Last Verified: 2026-05-07

TC-UNIT-614: KCConfig — Defaults

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestKCConfig::test_default_values
  • Expected: Default Keycloak server, realm, client settings
  • Last Verified: 2026-05-07

TC-UNIT-615: KCConfig — is_configured With Secret

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestKCConfig::test_is_configured_with_secret
  • Expected: is_configured is True when client_secret is set
  • Last Verified: 2026-05-07

TC-UNIT-616: KCConfig — is_configured Without Secret

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestKCConfig::test_is_configured_without_secret
  • Expected: is_configured is False when client_secret is empty
  • Last Verified: 2026-05-07

TC-UNIT-617: SecurityConfig — Defaults

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestSecurityConfig::test_default_values
  • Expected: Default token expiration (30 min)
  • Last Verified: 2026-05-07

TC-UNIT-618: SecurityConfig — is_configured

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestSecurityConfig::test_is_configured_with_secret, test_is_configured_without_secret
  • Expected: is_configured reflects secret presence
  • Last Verified: 2026-05-07

TC-UNIT-619: Environment Enum — Values

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_config.py::TestEnvironment::test_dev_value, test_prod_value
  • Expected: DEV.value == "dev", PROD.value == "prod"
  • Last Verified: 2026-05-07

Authentication Client

TC-UNIT-701: TokenInfo — Valid Token

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestTokenInfo::test_token_info_valid
  • Expected: is_valid is True, all fields populated
  • Last Verified: 2026-05-07

TC-UNIT-702: TokenInfo — Inactive Token

  • Type: Negative
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestTokenInfo::test_token_info_invalid_not_active
  • Expected: is_valid is False
  • Last Verified: 2026-05-07

TC-UNIT-703: TokenInfo — Missing user_id

  • Type: Negative
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestTokenInfo::test_token_info_invalid_no_user_id
  • Expected: is_valid is False
  • Last Verified: 2026-05-07

TC-UNIT-704: TokenInfo — Empty Roles

  • Type: Edge
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestTokenInfo::test_token_info_empty_roles
  • Expected: is_valid is True, roles is empty list
  • Last Verified: 2026-05-07

TC-UNIT-705: KeycloakUser — Creation

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakUser::test_keycloak_user_creation
  • Expected: All fields stored correctly
  • Last Verified: 2026-05-07

TC-UNIT-706: KeycloakUser — Defaults

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakUser::test_keycloak_user_defaults
  • Expected: Optional fields default to empty strings / lists
  • Last Verified: 2026-05-07

TC-UNIT-707: KeycloakAuthClient — Initialization

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakAuthClient::test_client_initialization
  • Expected: Base URL and credentials set from settings
  • Last Verified: 2026-05-07

TC-UNIT-708: KeycloakAuthClient — Introspection URL

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakAuthClient::test_get_introspection_url
  • Expected: URL built from settings (server_url, realm)
  • Last Verified: 2026-05-07

TC-UNIT-709: KeycloakAuthClient — Userinfo URL

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakAuthClient::test_get_userinfo_url
  • Expected: URL built from settings
  • Last Verified: 2026-05-07

TC-UNIT-710: KeycloakAuthClient — Introspect Success

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakAuthClient::test_introspect_token_success
  • Preconditions: Mock httpx.AsyncClient with active token response
  • Expected: Returns TokenInfo with active=True, roles parsed from realm_access
  • Last Verified: 2026-05-07

TC-UNIT-711: KeycloakAuthClient — Introspect Inactive Token

  • Type: Negative
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakAuthClient::test_introspect_token_inactive
  • Expected: Returns TokenInfo with active=False, is_valid=False
  • Last Verified: 2026-05-07

TC-UNIT-712: KeycloakAuthClient — Introspect HTTP Error

  • Type: Negative
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakAuthClient::test_introspect_token_http_error
  • Expected: Returns inactive TokenInfo (graceful degradation)
  • Last Verified: 2026-05-07

TC-UNIT-713: KeycloakAuthClient — Introspect Uses Cache

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakAuthClient::test_introspect_token_uses_cache
  • Expected: Second call with same token uses cache; HTTP client called only once
  • Last Verified: 2026-05-07

TC-UNIT-714: KeycloakAuthClient — Get Userinfo Success

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakAuthClient::test_get_userinfo_success
  • Expected: Returns KeycloakUser with all profile fields
  • Last Verified: 2026-05-07

TC-UNIT-715: KeycloakAuthClient — Get Userinfo Error

  • Type: Negative
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakAuthClient::test_get_userinfo_error
  • Expected: Returns None on HTTP error
  • Last Verified: 2026-05-07

TC-UNIT-716: KeycloakAuthClient — Introspect Without Realm Roles

  • Type: Edge
  • Layer: Unit
  • File: unit/infrastructure/test_auth.py::TestKeycloakAuthClient::test_introspect_token_no_realm_roles
  • Expected: Returns active token with empty roles list
  • Last Verified: 2026-05-07

Transaction Manager

TC-UNIT-801: SessionTransactionManager — Commit

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_transaction_manager.py::TestSessionTransactionManager::test_commit
  • Expected: Calls session.commit once
  • Last Verified: 2026-05-07

TC-UNIT-802: SessionTransactionManager — Rollback

  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_transaction_manager.py::TestSessionTransactionManager::test_rollback
  • Expected: Calls session.rollback once
  • Last Verified: 2026-05-07

i18n Localization

Translation Service

TC-UNIT-811: TranslationService — Existing key returns translation
  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_i18n.py::TestTranslationService::test_get_text_returns_translation_for_existing_key
  • Expected: Returns correct localized string when key exists in requested locale
  • Last Verified: 2026-05-10
TC-UNIT-812: TranslationService — Fallback chain
  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_i18n.py::TestTranslationService::{test_get_text_returns_english_fallback_for_missing_key, test_get_text_returns_key_when_neither_locale_nor_en_has_it, test_get_text_returns_english_fallback_for_unknown_locale}
  • Preconditions: Key missing in requested locale
  • Steps: Call get_text with partially-available keys
  • Expected:
    • Falls back to English when key exists in en but not in requested locale
    • Falls back to raw key when neither requested locale nor en has it
    • Falls back to English when locale is completely unknown
  • Last Verified: 2026-05-10
TC-UNIT-813: TranslationService — English locale
  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_i18n.py::TestTranslationService::test_get_text_returns_en_when_requested_locale_is_en
  • Expected: Returns English string when locale is explicitly en
  • Last Verified: 2026-05-10
TC-UNIT-814: TranslationService — Singleton pattern
  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_i18n.py::TestTranslationService::{test_singleton_returns_same_instance, test_singleton_shares_translations}
  • Expected: Multiple calls to get_instance() return the same object with shared data
  • Last Verified: 2026-05-10

Convenience Function

TC-UNIT-815: Convenience _() function
  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_i18n.py::TestConvenienceFunction::{test_convenience_function_returns_translation, test_convenience_function_defaults_to_english, test_convenience_function_returns_key_on_missing}
  • Expected:
    • Returns translation when key and locale are given
    • Defaults to DEFAULT_LOCALE ("en") when locale omitted
    • Returns raw key when no translation exists
  • Last Verified: 2026-05-10

Locale Detection

TC-UNIT-816: Parse Accept-Language — Empty and single locale
  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_i18n.py::TestParseAcceptLanguage::{test_empty_header_returns_empty_list, test_single_locale}
  • Expected:
    • Empty header returns empty list
    • Single locale returns single-element list
  • Last Verified: 2026-05-10
TC-UNIT-817: Parse Accept-Language — Multi-locale with quality
  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_i18n.py::TestParseAcceptLanguage::test_multiple_locales_with_quality
  • Expected: Locales sorted by descending q-value, quality values stripped
  • Last Verified: 2026-05-10
TC-UNIT-818: Parse Accept-Language — Region codes and complex input
  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_i18n.py::TestParseAcceptLanguage::{test_region_code_strips_to_base_language, test_complex_header, test_whitespace_around_locales}
  • Expected:
    • fr-FR normalised to fr
    • Realistic headers with region codes parsed correctly
    • Whitespace around locale codes handled gracefully
  • Last Verified: 2026-05-10
  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_i18n.py::TestGetBestLocale::{test_cookie_takes_priority, test_invalid_cookie_falls_back_to_header}
  • Expected:
    • Cookie value used when it is a supported locale
    • Unsupported locale in cookie falls back to Accept-Language header
  • Last Verified: 2026-05-10
TC-UNIT-820: Get best locale — Header fallback
  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_i18n.py::TestGetBestLocale::{test_no_cookie_uses_accept_language, test_no_cookie_no_header_returns_default, test_accept_language_unsupported_returns_default, test_missing_cookie_key_uses_header}
  • Expected:
    • Accept-Language used when no cookie present
    • Default locale returned when neither cookie nor header matches
    • Unsupported language in header falls back to default
    • Absent locale cookie key treated as no preference
  • Last Verified: 2026-05-10
TC-UNIT-821: Setup locale manager — Middleware helper
  • Type: Positive
  • Layer: Unit
  • File: unit/infrastructure/test_i18n.py::TestSetupLocaleManager::{test_sets_locale_on_request_state, test_does_not_override_existing_locale, test_default_locale_when_no_match}
  • Expected:
    • Sets request.state.locale from best-match locale
    • Does not override an already-set locale
    • Falls back to default when nothing matches
  • Last Verified: 2026-05-10

Coverage Summary

Component Cases Status
App Bootstrap 4 Lifespan, factory, routes, main entry
Settings & Config 19 Defaults, overrides, validation, env checks
Keycloak Auth Client 16 Token introspection, userinfo, caching, errors
Transaction Manager 2 ⚠️ Only commit/rollback; missing nested tx, error handling
i18n Localization 11 Translation service, locale detection, middleware helper

Gaps (Not Yet Covered)

  • TC-UNIT-803: Transaction Manager — rollback on exception
  • TC-UNIT-804: Transaction Manager — nested transaction behavior
  • TC-UNIT-805: KeycloakAuthClient — cache expiration (TTL)
  • TC-UNIT-806: KeycloakAuthClient — cache key isolation per token
  • TC-UNIT-807: Settings — prod database URL building with missing components
  • TC-UNIT-808: App Factory — CORS middleware configuration
  • TC-UNIT-809: App Factory — static files mounting
  • TC-UNIT-810: App Factory — error handler registration