Some checks failed
ci/woodpecker/pr/pipeline Pipeline failed
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
444 lines
18 KiB
Markdown
444 lines
18 KiB
Markdown
# 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
|
|
|
|
##### TC-UNIT-819: Get best locale — Cookie priority
|
|
- **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
|