mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-27 23:50:19 -05:00
Implements comprehensive type and range validation to prevent invalid data
from being set via Python GRPC interface. Invalid values are rejected with
clear error messages. Uses two-phase commit pattern to ensure atomic updates
where either all fields update or none do.
Validation Features:
- Type validation: Ensures field value types match (parsing errors caught)
- Range validation: Checks values against min/max constraints defined with
setRange(), setMinValue(), setMaxValue()
- Object validation: Validates cross-field constraints via validate() override
- Atomic updates: Two-phase commit ensures all-or-nothing semantics
- Rollback on error: Failed validation leaves ALL fields at previous values
- Clear error messages: Aggregates and returns all validation failures
Implementation:
- Use std::expected<void, QString> for type-safe error handling (C++23)
- assignFieldValue(): Validates after parsing, rolls back individual field on error
- copyPdmObjectFromRipsToCaf(): Two-phase commit pattern
- Phase 1: Validate all fields and collect changes (defer UI notifications)
- Phase 2: If any validation fails, rollback all fields; otherwise commit all
- UpdateExistingPdmObject(): Returns grpc::INVALID_ARGUMENT with validation details
Testing:
- Comprehensive Python tests demonstrating validation behavior
- Test atomic rollback: multiple fields rolled back when one fails
- No regression in existing functionality
Documentation:
- Added validation documentation to CLAUDE.md with examples
- Explains how to set field ranges and handle validation errors in Python
Built on validation infrastructure from commit f342ca77 (PdmFieldHandle and
PdmObjectHandle validation support).
107 lines
3.7 KiB
Python
107 lines
3.7 KiB
Python
"""
|
|
Test file for issue #12940: Python GRPC interface validates field values
|
|
|
|
This test verifies that validation prevents invalid values from being set.
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import pytest
|
|
|
|
sys.path.insert(1, os.path.join(sys.path[0], "../../"))
|
|
import rips
|
|
|
|
|
|
def test_range_validation_minimum_enforced(rips_instance, initialize_test):
|
|
"""
|
|
Verifies that setting value below minimum range is rejected.
|
|
|
|
The wellBoreFluidPvtTable field has setMinValue(0) in C++, so negative values
|
|
should be rejected with a clear error message.
|
|
"""
|
|
well_path_coll = rips_instance.project.well_path_collection()
|
|
|
|
# Create a new modeled well path
|
|
well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
|
|
well_path.name = "test_validation"
|
|
well_path.update()
|
|
|
|
# Get completion settings
|
|
completions_settings = well_path.completion_settings()
|
|
|
|
# Set wellBoreFluidPvtTable to a negative value (below minimum of 0)
|
|
completions_settings.well_bore_fluid_pvt_table = -5
|
|
|
|
# Should raise grpc._channel._InactiveRpcError with validation message
|
|
with pytest.raises(Exception) as exc_info:
|
|
completions_settings.update()
|
|
|
|
error_message = str(exc_info.value)
|
|
assert "Validation failed" in error_message
|
|
assert (
|
|
"WellBoreFluidPvtTable" in error_message
|
|
or "well_bore_fluid_pvt_table" in error_message
|
|
)
|
|
assert "-5" in error_message or "below minimum" in error_message
|
|
|
|
|
|
def test_valid_value_should_succeed(rips_instance, initialize_test):
|
|
"""
|
|
Control test: Valid values should always work.
|
|
|
|
This test should pass both before and after implementing validation.
|
|
"""
|
|
well_path_coll = rips_instance.project.well_path_collection()
|
|
|
|
well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
|
|
well_path.name = "test_valid"
|
|
well_path.update()
|
|
|
|
completions_settings = well_path.completion_settings()
|
|
|
|
# Set valid values (within range)
|
|
completions_settings.well_bore_fluid_pvt_table = 5 # Valid: >= 0
|
|
completions_settings.fluid_in_place_region = 10 # Valid
|
|
|
|
# This should succeed both before and after validation implementation
|
|
completions_settings.update()
|
|
|
|
# Verify values were set correctly
|
|
completions_settings_updated = well_path.completion_settings()
|
|
assert completions_settings_updated.well_bore_fluid_pvt_table == 5
|
|
assert completions_settings_updated.fluid_in_place_region == 10
|
|
|
|
|
|
def test_value_unchanged_after_validation_error(rips_instance, initialize_test):
|
|
"""Verifies field value doesn't change if validation fails (rollback)."""
|
|
well_path_coll = rips_instance.project.well_path_collection()
|
|
well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
|
|
well_path.name = "test_rollback"
|
|
well_path.update()
|
|
|
|
completions_settings = well_path.completion_settings()
|
|
|
|
# Set valid initial value
|
|
completions_settings.well_bore_fluid_pvt_table = 42
|
|
completions_settings.fluid_in_place_region = 99
|
|
completions_settings.update()
|
|
|
|
# Try to set invalid value
|
|
completions_settings.well_bore_fluid_pvt_table = -5
|
|
|
|
# Also set a different version for (should also be rolled back)
|
|
completions_settings.fluid_in_place_region = 12
|
|
|
|
with pytest.raises(Exception):
|
|
completions_settings.update()
|
|
|
|
# Value should remain at the previous valid value (42)
|
|
completions_settings_refreshed = well_path.completion_settings()
|
|
assert (
|
|
completions_settings_refreshed.well_bore_fluid_pvt_table == 42
|
|
), "Field value should not change after failed validation (rollback should occur)"
|
|
|
|
assert (
|
|
completions_settings_refreshed.fluid_in_place_region == 99
|
|
), "Field value should not change after failed validation (rollback should occur)"
|