Enhance testing framework and UI feedback
- Updated architecture documentation to include details on UI rendering checks and Playwright end-to-end tests. - Revised testing documentation to specify Playwright for frontend E2E tests and added details on running tests. - Implemented feedback mechanism in scenario form for successful creation notifications. - Added feedback div in ScenarioForm.html for user notifications. - Created new fixtures for Playwright tests to manage server and browser instances. - Developed comprehensive E2E tests for consumption, costs, equipment, maintenance, production, and scenarios. - Added smoke tests to verify UI page loading and form submissions. - Enhanced unit tests for simulation and validation, including new tests for report generation and validation errors. - Created new test files for router validation to ensure consistent error handling. - Established a new test suite for UI routes to validate dashboard and reporting functionalities. - Implemented validation tests to ensure proper handling of JSON payloads.
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
from typing import Generator
|
||||
from datetime import date
|
||||
from typing import Any, Dict, Generator
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -8,6 +10,15 @@ from sqlalchemy.pool import StaticPool
|
||||
|
||||
from config.database import Base
|
||||
from main import app
|
||||
from models.capex import Capex
|
||||
from models.consumption import Consumption
|
||||
from models.equipment import Equipment
|
||||
from models.maintenance import Maintenance
|
||||
from models.opex import Opex
|
||||
from models.parameters import Parameter
|
||||
from models.production_output import ProductionOutput
|
||||
from models.scenario import Scenario
|
||||
from models.simulation_result import SimulationResult
|
||||
|
||||
SQLALCHEMY_TEST_URL = "sqlite:///:memory:"
|
||||
engine = create_engine(
|
||||
@@ -35,6 +46,19 @@ def setup_database() -> Generator[None, None, None]:
|
||||
simulation_result,
|
||||
) # noqa: F401 - imported for side effects
|
||||
|
||||
_ = (
|
||||
capex,
|
||||
consumption,
|
||||
distribution,
|
||||
equipment,
|
||||
maintenance,
|
||||
opex,
|
||||
parameters,
|
||||
production_output,
|
||||
scenario,
|
||||
simulation_result,
|
||||
)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
yield
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
@@ -65,3 +89,151 @@ def api_client(db_session: Session) -> Generator[TestClient, None, None]:
|
||||
yield client
|
||||
|
||||
app.dependency_overrides.pop(route_dependencies.get_db, None)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def seeded_ui_data(db_session: Session) -> Generator[Dict[str, Any], None, None]:
|
||||
"""Populate a scenario with representative related records for UI tests."""
|
||||
scenario_name = f"Scenario Alpha {uuid4()}"
|
||||
scenario = Scenario(name=scenario_name,
|
||||
description="Seeded UI scenario")
|
||||
db_session.add(scenario)
|
||||
db_session.flush()
|
||||
|
||||
parameter = Parameter(
|
||||
scenario_id=scenario.id,
|
||||
name="Ore Grade",
|
||||
value=1.5,
|
||||
distribution_type="normal",
|
||||
distribution_parameters={"mean": 1.5, "std_dev": 0.1},
|
||||
)
|
||||
capex = Capex(
|
||||
scenario_id=scenario.id,
|
||||
amount=1_000_000.0,
|
||||
description="Drill purchase",
|
||||
)
|
||||
opex = Opex(
|
||||
scenario_id=scenario.id,
|
||||
amount=250_000.0,
|
||||
description="Fuel spend",
|
||||
)
|
||||
consumption = Consumption(
|
||||
scenario_id=scenario.id,
|
||||
amount=1_200.0,
|
||||
description="Diesel (L)",
|
||||
)
|
||||
production = ProductionOutput(
|
||||
scenario_id=scenario.id,
|
||||
amount=800.0,
|
||||
description="Ore (tonnes)",
|
||||
)
|
||||
equipment = Equipment(
|
||||
scenario_id=scenario.id,
|
||||
name="Excavator 42",
|
||||
description="Primary loader",
|
||||
)
|
||||
db_session.add_all(
|
||||
[parameter, capex, opex, consumption, production, equipment]
|
||||
)
|
||||
db_session.flush()
|
||||
|
||||
maintenance = Maintenance(
|
||||
scenario_id=scenario.id,
|
||||
equipment_id=equipment.id,
|
||||
maintenance_date=date(2025, 1, 15),
|
||||
description="Hydraulic service",
|
||||
cost=15_000.0,
|
||||
)
|
||||
simulation_results = [
|
||||
SimulationResult(
|
||||
scenario_id=scenario.id,
|
||||
iteration=index,
|
||||
result=value,
|
||||
)
|
||||
for index, value in enumerate((950_000.0, 975_000.0, 990_000.0), start=1)
|
||||
]
|
||||
|
||||
db_session.add(maintenance)
|
||||
db_session.add_all(simulation_results)
|
||||
db_session.commit()
|
||||
|
||||
try:
|
||||
yield {
|
||||
"scenario": scenario,
|
||||
"equipment": equipment,
|
||||
"simulation_results": simulation_results,
|
||||
}
|
||||
finally:
|
||||
db_session.query(SimulationResult).filter_by(
|
||||
scenario_id=scenario.id
|
||||
).delete()
|
||||
db_session.query(Maintenance).filter_by(
|
||||
scenario_id=scenario.id
|
||||
).delete()
|
||||
db_session.query(Equipment).filter_by(id=equipment.id).delete()
|
||||
db_session.query(ProductionOutput).filter_by(
|
||||
scenario_id=scenario.id
|
||||
).delete()
|
||||
db_session.query(Consumption).filter_by(
|
||||
scenario_id=scenario.id
|
||||
).delete()
|
||||
db_session.query(Opex).filter_by(scenario_id=scenario.id).delete()
|
||||
db_session.query(Capex).filter_by(scenario_id=scenario.id).delete()
|
||||
db_session.query(Parameter).filter_by(scenario_id=scenario.id).delete()
|
||||
db_session.query(Scenario).filter_by(id=scenario.id).delete()
|
||||
db_session.commit()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def invalid_request_payloads(db_session: Session) -> Generator[Dict[str, Any], None, None]:
|
||||
"""Provide reusable invalid request bodies for exercising validation branches."""
|
||||
duplicate_name = f"Scenario Duplicate {uuid4()}"
|
||||
existing = Scenario(name=duplicate_name,
|
||||
description="Existing scenario for duplicate checks")
|
||||
db_session.add(existing)
|
||||
db_session.commit()
|
||||
|
||||
payloads: Dict[str, Any] = {
|
||||
"existing_scenario": existing,
|
||||
"scenario_duplicate": {
|
||||
"name": duplicate_name,
|
||||
"description": "Second scenario should fail with duplicate name",
|
||||
},
|
||||
"parameter_missing_scenario": {
|
||||
"scenario_id": existing.id + 99,
|
||||
"name": "Invalid Parameter",
|
||||
"value": 1.0,
|
||||
},
|
||||
"parameter_invalid_distribution": {
|
||||
"scenario_id": existing.id,
|
||||
"name": "Weird Dist",
|
||||
"value": 2.5,
|
||||
"distribution_type": "invalid",
|
||||
},
|
||||
"simulation_unknown_scenario": {
|
||||
"scenario_id": existing.id + 99,
|
||||
"iterations": 10,
|
||||
"parameters": [
|
||||
{"name": "grade", "value": 1.2, "distribution": "normal"}
|
||||
],
|
||||
},
|
||||
"simulation_missing_parameters": {
|
||||
"scenario_id": existing.id,
|
||||
"iterations": 5,
|
||||
"parameters": [],
|
||||
},
|
||||
"reporting_non_list_payload": {"result": 10.0},
|
||||
"reporting_missing_result": [{"value": 12.0}],
|
||||
"maintenance_negative_cost": {
|
||||
"equipment_id": 1,
|
||||
"scenario_id": existing.id,
|
||||
"maintenance_date": "2025-01-15",
|
||||
"cost": -500.0,
|
||||
},
|
||||
}
|
||||
|
||||
try:
|
||||
yield payloads
|
||||
finally:
|
||||
db_session.query(Scenario).filter_by(id=existing.id).delete()
|
||||
db_session.commit()
|
||||
|
||||
Reference in New Issue
Block a user