feat: Enhance currency handling and validation across scenarios
- Updated form template to prefill currency input with default value and added help text for clarity. - Modified integration tests to assert more descriptive error messages for invalid currency codes. - Introduced new tests for currency normalization and validation in various scenarios, including imports and exports. - Added comprehensive tests for pricing calculations, ensuring defaults are respected and overrides function correctly. - Implemented unit tests for pricing settings repository, ensuring CRUD operations and default settings are handled properly. - Enhanced scenario pricing evaluation tests to validate currency handling and metadata defaults. - Added simulation tests to ensure Monte Carlo runs are accurate and handle various distribution scenarios.
This commit is contained in:
@@ -7,8 +7,15 @@ from typing import Callable, Iterable
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from config.settings import Settings
|
||||
from models import Role, User
|
||||
from services.repositories import DEFAULT_ROLE_DEFINITIONS, RoleRepository, UserRepository
|
||||
from services.repositories import (
|
||||
DEFAULT_ROLE_DEFINITIONS,
|
||||
PricingSettingsSeedResult,
|
||||
RoleRepository,
|
||||
UserRepository,
|
||||
ensure_default_pricing_settings,
|
||||
)
|
||||
from services.unit_of_work import UnitOfWork
|
||||
|
||||
|
||||
@@ -45,7 +52,8 @@ def parse_bool(value: str | None) -> bool:
|
||||
def normalise_role_list(raw_value: str | None) -> tuple[str, ...]:
|
||||
if not raw_value:
|
||||
return ("admin",)
|
||||
parts = [segment.strip() for segment in raw_value.split(",") if segment.strip()]
|
||||
parts = [segment.strip()
|
||||
for segment in raw_value.split(",") if segment.strip()]
|
||||
if "admin" not in parts:
|
||||
parts.insert(0, "admin")
|
||||
seen: set[str] = set()
|
||||
@@ -59,7 +67,8 @@ def normalise_role_list(raw_value: str | None) -> tuple[str, ...]:
|
||||
|
||||
def load_config() -> SeedConfig:
|
||||
load_dotenv()
|
||||
admin_email = os.getenv("CALMINER_SEED_ADMIN_EMAIL", "admin@calminer.local")
|
||||
admin_email = os.getenv("CALMINER_SEED_ADMIN_EMAIL",
|
||||
"admin@calminer.local")
|
||||
admin_username = os.getenv("CALMINER_SEED_ADMIN_USERNAME", "admin")
|
||||
admin_password = os.getenv("CALMINER_SEED_ADMIN_PASSWORD", "ChangeMe123!")
|
||||
admin_roles = normalise_role_list(os.getenv("CALMINER_SEED_ADMIN_ROLES"))
|
||||
@@ -140,12 +149,15 @@ def ensure_admin_user(
|
||||
for role_name in config.admin_roles:
|
||||
role = role_repo.get_by_name(role_name)
|
||||
if role is None:
|
||||
logging.warning("Role '%s' is not defined and will be skipped", role_name)
|
||||
logging.warning(
|
||||
"Role '%s' is not defined and will be skipped", role_name)
|
||||
continue
|
||||
already_assigned = any(assignment.role_id == role.id for assignment in user.role_assignments)
|
||||
already_assigned = any(assignment.role_id ==
|
||||
role.id for assignment in user.role_assignments)
|
||||
if already_assigned:
|
||||
continue
|
||||
user_repo.assign_role(user_id=user.id, role_id=role.id, granted_by=user.id)
|
||||
user_repo.assign_role(
|
||||
user_id=user.id, role_id=role.id, granted_by=user.id)
|
||||
roles_granted += 1
|
||||
|
||||
return AdminSeedResult(
|
||||
@@ -164,9 +176,33 @@ def seed_initial_data(
|
||||
logging.info("Starting initial data seeding")
|
||||
factory = unit_of_work_factory or UnitOfWork
|
||||
with factory() as uow:
|
||||
assert uow.roles is not None and uow.users is not None
|
||||
assert (
|
||||
uow.roles is not None
|
||||
and uow.users is not None
|
||||
and uow.pricing_settings is not None
|
||||
and uow.projects is not None
|
||||
)
|
||||
role_result = ensure_default_roles(uow.roles)
|
||||
admin_result = ensure_admin_user(uow.users, uow.roles, config)
|
||||
pricing_metadata = uow.get_pricing_metadata()
|
||||
metadata_source = "database"
|
||||
if pricing_metadata is None:
|
||||
pricing_metadata = Settings.from_environment().pricing_metadata()
|
||||
metadata_source = "environment"
|
||||
pricing_result: PricingSettingsSeedResult = ensure_default_pricing_settings(
|
||||
uow.pricing_settings,
|
||||
metadata=pricing_metadata,
|
||||
)
|
||||
|
||||
projects_without_pricing = [
|
||||
project
|
||||
for project in uow.projects.list(with_pricing=True)
|
||||
if project.pricing_settings is None
|
||||
]
|
||||
assigned_projects = 0
|
||||
for project in projects_without_pricing:
|
||||
uow.set_project_pricing_settings(project, pricing_result.settings)
|
||||
assigned_projects += 1
|
||||
logging.info(
|
||||
"Roles processed: %s total, %s created, %s updated",
|
||||
role_result.total,
|
||||
@@ -180,4 +216,16 @@ def seed_initial_data(
|
||||
admin_result.password_rotated,
|
||||
admin_result.roles_granted,
|
||||
)
|
||||
logging.info("Initial data seeding completed successfully")
|
||||
logging.info(
|
||||
"Pricing settings ensured (source=%s): slug=%s created=%s updated_fields=%s impurity_upserts=%s",
|
||||
metadata_source,
|
||||
pricing_result.settings.slug,
|
||||
pricing_result.created,
|
||||
pricing_result.updated_fields,
|
||||
pricing_result.impurity_upserts,
|
||||
)
|
||||
logging.info(
|
||||
"Projects updated with default pricing settings: %s",
|
||||
assigned_projects,
|
||||
)
|
||||
logging.info("Initial data seeding completed successfully")
|
||||
|
||||
Reference in New Issue
Block a user