feat: enhance import functionality with commit results and summary models for projects and scenarios

This commit is contained in:
2025-11-10 09:20:41 +01:00
parent 3bc124c11f
commit eaef99f0ac
3 changed files with 422 additions and 1 deletions

View File

@@ -9,11 +9,19 @@ import pytest
from models.project import MiningOperationType, Project
from models.scenario import Scenario, ScenarioStatus
from services.importers import (
ImportCommitResult,
ImportIngestionService,
ImportPreviewState,
StagedImportView,
)
from services.repositories import ProjectRepository
from services.unit_of_work import UnitOfWork
from schemas.imports import (
ProjectImportCommitResponse,
ProjectImportPreviewResponse,
ScenarioImportCommitResponse,
ScenarioImportPreviewResponse,
)
@pytest.fixture()
@@ -73,6 +81,9 @@ def test_preview_projects_flags_updates_and_duplicates(
assert update_context is not None and update_context.get(
"project_id") is not None
response_model = ProjectImportPreviewResponse.model_validate(preview)
assert response_model.summary.accepted == preview.summary.accepted
def test_preview_scenarios_validates_projects_and_updates(
ingestion_service: ImportIngestionService,
@@ -148,6 +159,9 @@ def test_preview_scenarios_validates_projects_and_updates(
error_row = preview.rows[2]
assert any("does not exist" in msg for msg in error_row.issues)
response_model = ScenarioImportPreviewResponse.model_validate(preview)
assert response_model.summary.errored == preview.summary.errored
def test_preview_scenarios_aggregates_parser_errors(
ingestion_service: ImportIngestionService,
@@ -182,6 +196,9 @@ def test_preview_scenarios_aggregates_parser_errors(
assert any(detail.field == "status" for detail in bundle.issues)
assert all(detail.message for detail in bundle.issues)
response_model = ScenarioImportPreviewResponse.model_validate(preview)
assert response_model.summary.total_rows == 1
def test_consume_staged_projects_removes_token(
ingestion_service: ImportIngestionService,
@@ -235,3 +252,144 @@ def test_clear_staged_scenarios_drops_entry(
assert ingestion_service.clear_staged_scenarios(token) is True
assert ingestion_service.get_staged_scenarios(token) is None
assert ingestion_service.clear_staged_scenarios(token) is False
def test_commit_project_import_applies_create_and_update(
ingestion_service: ImportIngestionService,
unit_of_work_factory: Callable[[], UnitOfWork],
) -> None:
with unit_of_work_factory() as uow:
assert uow.projects is not None
existing = Project(
name="Project A",
location="Chile",
operation_type=MiningOperationType.OPEN_PIT,
)
uow.projects.create(existing)
csv_content = (
"name,location,operation_type\n"
"Project A,Peru,underground\n"
"Project B,Canada,open pit\n"
)
stream = BytesIO(csv_content.encode("utf-8"))
preview = ingestion_service.preview_projects(stream, "projects.csv")
assert preview.stage_token is not None
result = ingestion_service.commit_project_import(preview.stage_token)
assert isinstance(result, ImportCommitResult)
assert result.summary.created == 1
assert result.summary.updated == 1
assert ingestion_service.get_staged_projects(preview.stage_token) is None
commit_response = ProjectImportCommitResponse.model_validate(result)
assert commit_response.summary.updated == 1
with unit_of_work_factory() as uow:
assert uow.projects is not None
projects = uow.projects.list()
names = sorted(project.name for project in projects)
assert names == ["Project A", "Project B"]
updated_project = next(p for p in projects if p.name == "Project A")
assert updated_project.location == "Peru"
assert updated_project.operation_type == MiningOperationType.UNDERGROUND
new_project = next(p for p in projects if p.name == "Project B")
assert new_project.location == "Canada"
def test_commit_project_import_with_invalid_token_raises(
ingestion_service: ImportIngestionService,
) -> None:
with pytest.raises(ValueError):
ingestion_service.commit_project_import("missing-token")
def test_commit_scenario_import_applies_create_and_update(
ingestion_service: ImportIngestionService,
unit_of_work_factory: Callable[[], UnitOfWork],
) -> None:
with unit_of_work_factory() as uow:
assert uow.projects is not None and uow.scenarios is not None
project = Project(
name="Project X",
location="Chile",
operation_type=MiningOperationType.OPEN_PIT,
)
uow.projects.create(project)
scenario = Scenario(
project_id=project.id,
name="Existing Scenario",
status=ScenarioStatus.ACTIVE,
)
uow.scenarios.create(scenario)
csv_content = (
"project_name,name,status\n"
"Project X,Existing Scenario,Archived\n"
"Project X,New Scenario,Draft\n"
)
stream = BytesIO(csv_content.encode("utf-8"))
preview = ingestion_service.preview_scenarios(stream, "scenarios.csv")
assert preview.stage_token is not None
result = ingestion_service.commit_scenario_import(preview.stage_token)
assert result.summary.created == 1
assert result.summary.updated == 1
assert ingestion_service.get_staged_scenarios(preview.stage_token) is None
commit_response = ScenarioImportCommitResponse.model_validate(result)
assert commit_response.summary.created == 1
with unit_of_work_factory() as uow:
assert uow.projects is not None and uow.scenarios is not None
scenarios = uow.scenarios.list_for_project(uow.projects.list()[0].id)
names = sorted(scenario.name for scenario in scenarios)
assert names == ["Existing Scenario", "New Scenario"]
updated_scenario = next(
item for item in scenarios if item.name == "Existing Scenario"
)
assert updated_scenario.status == ScenarioStatus.ARCHIVED
new_scenario = next(
item for item in scenarios if item.name == "New Scenario"
)
assert new_scenario.status == ScenarioStatus.DRAFT
def test_commit_scenario_import_with_invalid_token_raises(
ingestion_service: ImportIngestionService,
) -> None:
with pytest.raises(ValueError):
ingestion_service.commit_scenario_import("missing-token")
def test_commit_project_import_rolls_back_on_failure(
ingestion_service: ImportIngestionService,
unit_of_work_factory: Callable[[], UnitOfWork],
monkeypatch: pytest.MonkeyPatch,
) -> None:
with unit_of_work_factory() as uow:
assert uow.projects is not None
csv_content = (
"name,location,operation_type\n"
"Project Fail,Peru,open pit\n"
)
stream = BytesIO(csv_content.encode("utf-8"))
preview = ingestion_service.preview_projects(stream, "projects.csv")
assert preview.stage_token is not None
token = preview.stage_token
def _boom(self: ProjectRepository, project: Project) -> Project:
raise RuntimeError("boom")
monkeypatch.setattr(ProjectRepository, "create", _boom)
with pytest.raises(RuntimeError):
ingestion_service.commit_project_import(token)
# Token should still be present for retry.
assert ingestion_service.get_staged_projects(token) is not None
with unit_of_work_factory() as uow:
assert uow.projects is not None
assert uow.projects.count() == 0