Code import - branch release/SIENTIAPDE-1645

This commit is contained in:
2026-08-05 13:53:37 +00:00
commit d481e0acff
116 changed files with 92848 additions and 0 deletions

View File

@@ -0,0 +1,298 @@
"""
End-to-end tests for TrainModel parameter validation paths.
Covers scenarios 2.1.x: workflows that must terminate with
ORCHESTRATOR_VALIDATION_ERROR due to invalid parameter values.
"""
import pytest
from temporalio.client import WorkflowFailureError
from temporalio.testing import WorkflowEnvironment
from temporalio.worker import Worker
from e2e.helpers import (
assert_experiment_error,
insert_experiment_run,
load_scenario,
make_workflow_id,
start_and_await_workflow,
)
from model_manager.workflows.train_model import TrainModel
# Base experiment_run ids for validation test scenarios (offset to avoid collision)
_VALIDATION_ID_BASE = 3000
def _exception_chain_text(exc: BaseException) -> str:
"""Concatenate messages from an exception __cause__/__context__ chain."""
parts: list[str] = []
cur: BaseException | None = exc
seen: set[int] = set()
while cur is not None and id(cur) not in seen:
seen.add(id(cur))
text = str(cur).strip()
if text:
parts.append(text)
cur = cur.__cause__ or getattr(cur, '__context__', None)
return ' | '.join(parts).lower()
@pytest.mark.asyncio
@pytest.mark.integration
async def test_scenario_2_1_1_train_size_out_of_range(
temporal_test_env: WorkflowEnvironment,
temporal_worker: Worker,
postgres_engine,
):
"""Scenario 2.1.1 train_size=5 violates the 10100 business rule.
Expected: workflow updates status → ORCHESTRATOR_VALIDATION_ERROR
and error_message references 'train_size'.
"""
experiment_run_id = _VALIDATION_ID_BASE + 1
scenario = load_scenario('01-linear-regression-basic.json')
scenario = {**scenario, 'experiment_run_id': experiment_run_id, 'train_size': 5}
insert_experiment_run(postgres_engine, experiment_run_id)
with pytest.raises(Exception):
await start_and_await_workflow(
temporal_test_env.client,
TrainModel.run,
scenario,
make_workflow_id('test-s2-1-1'),
)
assert_experiment_error(
postgres_engine,
experiment_run_id,
expected_status='ORCHESTRATOR_VALIDATION_ERROR',
error_substr='train_size',
)
@pytest.mark.asyncio
@pytest.mark.integration
async def test_scenario_2_1_2_empty_variable_columns(
temporal_test_env: WorkflowEnvironment,
temporal_worker: Worker,
postgres_engine,
):
"""Scenario 2.1.2 variable_columns=[] → ORCHESTRATOR_VALIDATION_ERROR."""
experiment_run_id = _VALIDATION_ID_BASE + 2
scenario = load_scenario('01-linear-regression-basic.json')
scenario = {**scenario, 'experiment_run_id': experiment_run_id, 'variable_columns': []}
insert_experiment_run(postgres_engine, experiment_run_id)
with pytest.raises(Exception):
await start_and_await_workflow(
temporal_test_env.client,
TrainModel.run,
scenario,
make_workflow_id('test-s2-1-2'),
)
assert_experiment_error(
postgres_engine,
experiment_run_id,
expected_status='ORCHESTRATOR_VALIDATION_ERROR',
error_substr='variable_columns',
)
@pytest.mark.asyncio
@pytest.mark.integration
async def test_scenario_2_1_3_invalid_date_format(
temporal_test_env: WorkflowEnvironment,
temporal_worker: Worker,
postgres_engine,
):
"""Scenario 2.1.3 date_format='INVALID' is not in the allowed list."""
experiment_run_id = _VALIDATION_ID_BASE + 3
scenario = load_scenario('01-linear-regression-basic.json')
scenario = {**scenario, 'experiment_run_id': experiment_run_id, 'date_format': 'INVALID'}
insert_experiment_run(postgres_engine, experiment_run_id)
with pytest.raises(Exception):
await start_and_await_workflow(
temporal_test_env.client,
TrainModel.run,
scenario,
make_workflow_id('test-s2-1-3'),
)
assert_experiment_error(
postgres_engine,
experiment_run_id,
expected_status='ORCHESTRATOR_VALIDATION_ERROR',
error_substr='date_format',
)
@pytest.mark.asyncio
@pytest.mark.integration
async def test_scenario_2_1_4_whitespace_only_model_name(
temporal_test_env: WorkflowEnvironment,
temporal_worker: Worker,
postgres_engine,
):
"""Scenario 2.1.4 model_name=' ' (whitespace) → ORCHESTRATOR_VALIDATION_ERROR."""
experiment_run_id = _VALIDATION_ID_BASE + 4
scenario = load_scenario('01-linear-regression-basic.json')
scenario = {**scenario, 'experiment_run_id': experiment_run_id, 'model_name': ' '}
insert_experiment_run(postgres_engine, experiment_run_id)
with pytest.raises(Exception):
await start_and_await_workflow(
temporal_test_env.client,
TrainModel.run,
scenario,
make_workflow_id('test-s2-1-4'),
)
assert_experiment_error(
postgres_engine,
experiment_run_id,
expected_status='ORCHESTRATOR_VALIDATION_ERROR',
error_substr='model_name',
)
@pytest.mark.asyncio
@pytest.mark.integration
async def test_scenario_2_1_5_unknown_model_type(
temporal_test_env: WorkflowEnvironment,
temporal_worker: Worker,
postgres_engine,
):
"""Scenario 2.1.5 model_type='totally_unknown' → ORCHESTRATOR_VALIDATION_ERROR.
The PluginStore will not find this model in the Gitea repo, causing
load_model_metadata to fail before validate_train_params is even called.
"""
experiment_run_id = _VALIDATION_ID_BASE + 5
scenario = load_scenario('01-linear-regression-basic.json')
scenario = {
**scenario,
'experiment_run_id': experiment_run_id,
'model_type': 'totally_unknown_model',
}
insert_experiment_run(postgres_engine, experiment_run_id)
with pytest.raises(Exception):
await start_and_await_workflow(
temporal_test_env.client,
TrainModel.run,
scenario,
make_workflow_id('test-s2-1-5'),
)
assert_experiment_error(
postgres_engine,
experiment_run_id,
expected_status='ORCHESTRATOR_VALIDATION_ERROR',
error_substr='totally_unknown_model',
)
@pytest.mark.asyncio
@pytest.mark.integration
async def test_scenario_2_1_6_missing_target_variable(
temporal_test_env: WorkflowEnvironment,
temporal_worker: Worker,
postgres_engine,
):
"""Scenario 2.1.6 target_variable='' (empty string) → ORCHESTRATOR_VALIDATION_ERROR."""
experiment_run_id = _VALIDATION_ID_BASE + 6
scenario = load_scenario('01-linear-regression-basic.json')
scenario = {**scenario, 'experiment_run_id': experiment_run_id, 'target_variable': ''}
insert_experiment_run(postgres_engine, experiment_run_id)
with pytest.raises(Exception):
await start_and_await_workflow(
temporal_test_env.client,
TrainModel.run,
scenario,
make_workflow_id('test-s2-1-6'),
)
assert_experiment_error(
postgres_engine,
experiment_run_id,
expected_status='ORCHESTRATOR_VALIDATION_ERROR',
error_substr='target_variable',
)
@pytest.mark.asyncio
@pytest.mark.integration
async def test_scenario_2_1_7_missing_experiment_run_id(
temporal_test_env: WorkflowEnvironment,
temporal_worker: Worker,
):
"""Scenario 2.1.7 experiment_run_id missing → workflow raises ValueError immediately.
No DB row is inserted because experiment_run_id is mandatory to even
know which row to update. The workflow should raise before any DB call.
"""
scenario = load_scenario('01-linear-regression-basic.json')
scenario = {k: v for k, v in scenario.items() if k != 'experiment_run_id'}
with pytest.raises(WorkflowFailureError) as excinfo:
await start_and_await_workflow(
temporal_test_env.client,
TrainModel.run,
scenario,
make_workflow_id('test-s2-1-7'),
)
combined = _exception_chain_text(excinfo.value)
assert 'experiment_run_id' in combined
@pytest.mark.asyncio
@pytest.mark.integration
async def test_scenario_2_1_8_missing_date_column(
temporal_test_env: WorkflowEnvironment,
temporal_worker: Worker,
):
"""Scenario 2.1.8 date_column missing in payload raises before workflow business validation."""
scenario = load_scenario('01-linear-regression-basic.json')
scenario = {k: v for k, v in scenario.items() if k != 'date_column'}
with pytest.raises(WorkflowFailureError) as excinfo:
await start_and_await_workflow(
temporal_test_env.client,
TrainModel.run,
scenario,
make_workflow_id('test-s2-1-8'),
)
combined = _exception_chain_text(excinfo.value)
assert 'date_column' in combined
@pytest.mark.asyncio
@pytest.mark.integration
async def test_scenario_2_1_9_whitespace_date_column(
temporal_test_env: WorkflowEnvironment,
temporal_worker: Worker,
postgres_engine,
):
"""Scenario 2.1.9 date_column=' ' must produce ORCHESTRATOR_VALIDATION_ERROR."""
experiment_run_id = _VALIDATION_ID_BASE + 9
scenario = load_scenario('01-linear-regression-basic.json')
scenario = {**scenario, 'experiment_run_id': experiment_run_id, 'date_column': ' '}
insert_experiment_run(postgres_engine, experiment_run_id)
with pytest.raises(Exception):
await start_and_await_workflow(
temporal_test_env.client,
TrainModel.run,
scenario,
make_workflow_id('test-s2-1-9'),
)
assert_experiment_error(
postgres_engine,
experiment_run_id,
expected_status='ORCHESTRATOR_VALIDATION_ERROR',
error_substr='date_column',
)