SIENTIAPDE-1646
Update README, requirements, and E2E tests for improved configuration and functionality - Enhanced the README with updated model configuration examples, including the addition of an alias for production. - Removed the `requirements-light.txt` file and updated `requirements-local.txt` and `requirements.txt` to replace `asyncua` with `opcua`. - Refactored E2E test scenarios to utilize scenario input files for better maintainability and clarity. - Improved test coverage for MinIO offload functionality and added new helper functions for loading scenario inputs. - Updated `values.yaml` to reflect new global configurations and environment variables for the laborious worker.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
from unittest.mock import ANY, AsyncMock, MagicMock, call, patch
|
||||
from unittest.mock import ANY, MagicMock, call, patch
|
||||
|
||||
from pandas import DataFrame
|
||||
from pytest import fixture, mark
|
||||
from pytest import fixture
|
||||
from sientia_do.notifications.models import NotificationLevel
|
||||
from sientia_do.observability.sientia_monitoring import SientiaMonitoring
|
||||
|
||||
from laborious.activities.gates import Gates
|
||||
|
||||
@@ -17,17 +18,17 @@ def _passthrough_from_dict():
|
||||
|
||||
def _minio_payload(retrieve_return, status=None):
|
||||
"""
|
||||
Build a MinioDataFramePayload-like test double with async retrieve.
|
||||
Build a MinioDataFramePayload-like test double with retrieve.
|
||||
|
||||
Args:
|
||||
retrieve_return: Value returned from await retrieve(minio_repo, metadata).
|
||||
retrieve_return: Value returned from retrieve(minio_repo, metadata).
|
||||
status: Optional status dict for MLflow response gate (payload.status).
|
||||
|
||||
Return:
|
||||
MagicMock: Object with async retrieve and optional status.
|
||||
"""
|
||||
p = MagicMock()
|
||||
p.retrieve = AsyncMock(return_value=retrieve_return)
|
||||
p.retrieve = MagicMock(return_value=retrieve_return)
|
||||
p.status = status
|
||||
return p
|
||||
|
||||
@@ -37,7 +38,7 @@ def gates_activity():
|
||||
gates = Gates(
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=AsyncMock(),
|
||||
metrics_controller=MagicMock(),
|
||||
)
|
||||
gates.error = MagicMock()
|
||||
gates.debug = MagicMock()
|
||||
@@ -45,8 +46,7 @@ def gates_activity():
|
||||
gates.warning = MagicMock()
|
||||
gates.critical = MagicMock()
|
||||
gates.send_notification = MagicMock()
|
||||
gates.send_notification_async = AsyncMock()
|
||||
gates.emit_metric = AsyncMock()
|
||||
gates.emit_metric_sync = MagicMock()
|
||||
return gates
|
||||
|
||||
|
||||
@@ -60,8 +60,7 @@ metadata = {
|
||||
}
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_input_gate_invalid_filter(gates_activity):
|
||||
def test_input_gate_invalid_filter(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -71,7 +70,7 @@ async def test_input_gate_invalid_filter(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.input_gate(input_data)
|
||||
result = gates_activity.input_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
@@ -80,9 +79,8 @@ async def test_input_gate_invalid_filter(gates_activity):
|
||||
)
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
@patch('laborious.activities.gates.input_filter_functions')
|
||||
async def test_input_gate_filter_exception(mock_input_filter_functions, gates_activity):
|
||||
def test_input_gate_filter_exception(mock_input_filter_functions, gates_activity):
|
||||
# Arrange
|
||||
mock_input_filter_functions.__contains__.return_value = True
|
||||
mock_input_filter_functions.__getitem__.return_value = MagicMock(
|
||||
@@ -96,11 +94,11 @@ async def test_input_gate_filter_exception(mock_input_filter_functions, gates_ac
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.input_gate(input_data)
|
||||
result = gates_activity.input_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
gates_activity.send_notification_async.assert_called_once_with(
|
||||
gates_activity.send_notification.assert_called_once_with(
|
||||
metadata=metadata['metadata'],
|
||||
notification_id='INTPUT_GATE_ERROR__EMPTY_DATA',
|
||||
message="Error in filter EMPTY_DATA:{'POLICY': 'STOP', 'CONFIG': {}}: \n Test error",
|
||||
@@ -110,8 +108,7 @@ async def test_input_gate_filter_exception(mock_input_filter_functions, gates_ac
|
||||
)
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_input_gate_no_filters(gates_activity):
|
||||
def test_input_gate_no_filters(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -121,15 +118,14 @@ async def test_input_gate_no_filters(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.input_gate(input_data)
|
||||
result = gates_activity.input_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
gates_activity.debug.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_input_gate_with_filter(gates_activity):
|
||||
def test_input_gate_with_filter(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -139,15 +135,14 @@ async def test_input_gate_with_filter(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.input_gate(input_data)
|
||||
result = gates_activity.input_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == ('STOP', -1, 'Input data with bad quality')
|
||||
gates_activity.debug.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_input_gate_with_filter_lowercase_keys(gates_activity):
|
||||
def test_input_gate_with_filter_lowercase_keys(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -157,14 +152,13 @@ async def test_input_gate_with_filter_lowercase_keys(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.input_gate(input_data)
|
||||
result = gates_activity.input_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == ('STOP', -1, 'Input data with bad quality')
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_input_gate_with_filter_capitalized_keys(gates_activity):
|
||||
def test_input_gate_with_filter_capitalized_keys(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -174,14 +168,13 @@ async def test_input_gate_with_filter_capitalized_keys(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.input_gate(input_data)
|
||||
result = gates_activity.input_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == ('STOP', -1, 'Input data with bad quality')
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_input_gate_with_filter_not_caught(gates_activity):
|
||||
def test_input_gate_with_filter_not_caught(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -191,15 +184,14 @@ async def test_input_gate_with_filter_not_caught(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.input_gate(input_data)
|
||||
result = gates_activity.input_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
gates_activity.debug.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_mlflow_response_gate_invalid_filter(gates_activity):
|
||||
def test_mlflow_response_gate_invalid_filter(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -213,15 +205,14 @@ async def test_mlflow_response_gate_invalid_filter(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.mlflow_response_gate(input_data)
|
||||
result = gates_activity.mlflow_response_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
@patch('laborious.activities.gates.mlflow_response_filter_functions')
|
||||
async def test_mlflow_response_gate_filter_exception(
|
||||
def test_mlflow_response_gate_filter_exception(
|
||||
mock_mlflow_response_filter_functions, gates_activity
|
||||
):
|
||||
# Arrange
|
||||
@@ -241,11 +232,11 @@ async def test_mlflow_response_gate_filter_exception(
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.mlflow_response_gate(input_data)
|
||||
result = gates_activity.mlflow_response_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
gates_activity.send_notification_async.assert_called_once_with(
|
||||
gates_activity.send_notification.assert_called_once_with(
|
||||
metadata=metadata['metadata'],
|
||||
notification_id='MLFLOW_GATE_RESPONSE_FILTER__INVALID_FILTER',
|
||||
message="Error in filter INVALID_FILTER:{'POLICY': 'STOP'}: \n Test error",
|
||||
@@ -255,8 +246,7 @@ async def test_mlflow_response_gate_filter_exception(
|
||||
)
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_mlflow_response_gate_no_filters(gates_activity):
|
||||
def test_mlflow_response_gate_no_filters(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -270,15 +260,14 @@ async def test_mlflow_response_gate_no_filters(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.mlflow_response_gate(input_data)
|
||||
result = gates_activity.mlflow_response_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
gates_activity.debug.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_mlflow_response_gate_with_filter(gates_activity):
|
||||
def test_mlflow_response_gate_with_filter(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -292,16 +281,15 @@ async def test_mlflow_response_gate_with_filter(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.mlflow_response_gate(input_data)
|
||||
result = gates_activity.mlflow_response_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == ('STOP', -1, 'API error occurred')
|
||||
gates_activity.debug.assert_called()
|
||||
gates_activity.send_notification_async.assert_called()
|
||||
gates_activity.send_notification.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_mlflow_response_gate_with_filter_capitalized_keys(gates_activity):
|
||||
def test_mlflow_response_gate_with_filter_capitalized_keys(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -315,14 +303,13 @@ async def test_mlflow_response_gate_with_filter_capitalized_keys(gates_activity)
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.mlflow_response_gate(input_data)
|
||||
result = gates_activity.mlflow_response_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == ('STOP', -1, 'API error occurred')
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_mlflow_response_gate_with_filter_not_caught(gates_activity):
|
||||
def test_mlflow_response_gate_with_filter_not_caught(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -336,15 +323,14 @@ async def test_mlflow_response_gate_with_filter_not_caught(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.mlflow_response_gate(input_data)
|
||||
result = gates_activity.mlflow_response_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
gates_activity.debug.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_mlflow_content_gate_invalid_filter(gates_activity):
|
||||
def test_mlflow_content_gate_invalid_filter(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -355,17 +341,14 @@ async def test_mlflow_content_gate_invalid_filter(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.mlflow_content_gate(input_data)
|
||||
result = gates_activity.mlflow_content_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
@patch('laborious.activities.gates.mlflow_content_filter_functions')
|
||||
async def test_mlflow_content_gate_filter_exception(
|
||||
mock_mlflow_content_filter_functions, gates_activity
|
||||
):
|
||||
def test_mlflow_content_gate_filter_exception(mock_mlflow_content_filter_functions, gates_activity):
|
||||
# Arrange
|
||||
mock_mlflow_content_filter_functions.__contains__.return_value = True
|
||||
mock_mlflow_content_filter_functions.__getitem__.return_value = MagicMock(
|
||||
@@ -380,12 +363,12 @@ async def test_mlflow_content_gate_filter_exception(
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.mlflow_content_gate(input_data)
|
||||
result = gates_activity.mlflow_content_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
gates_activity.debug.assert_called()
|
||||
gates_activity.send_notification_async.assert_called_once_with(
|
||||
gates_activity.send_notification.assert_called_once_with(
|
||||
metadata=metadata['metadata'],
|
||||
notification_id='MLFLOW_GATE_CONTENT_FILTER__API_ERROR',
|
||||
message="Error in filter API_ERROR:{'POLICY': 'STOP'}: \n Test error",
|
||||
@@ -395,8 +378,7 @@ async def test_mlflow_content_gate_filter_exception(
|
||||
)
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_mlflow_content_gate_no_filters(gates_activity):
|
||||
def test_mlflow_content_gate_no_filters(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -407,15 +389,14 @@ async def test_mlflow_content_gate_no_filters(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.mlflow_content_gate(input_data)
|
||||
result = gates_activity.mlflow_content_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
gates_activity.debug.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_mlflow_content_gate_with_filter(gates_activity):
|
||||
def test_mlflow_content_gate_with_filter(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -426,16 +407,15 @@ async def test_mlflow_content_gate_with_filter(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.mlflow_content_gate(input_data)
|
||||
result = gates_activity.mlflow_content_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == ('STOP', -1, 'Transformed data not passed the content filter')
|
||||
gates_activity.debug.assert_called()
|
||||
gates_activity.send_notification_async.assert_called()
|
||||
gates_activity.send_notification.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_mlflow_content_gate_with_filter_not_caught(gates_activity):
|
||||
def test_mlflow_content_gate_with_filter_not_caught(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -446,15 +426,14 @@ async def test_mlflow_content_gate_with_filter_not_caught(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.mlflow_content_gate(input_data)
|
||||
result = gates_activity.mlflow_content_gate(input_data)
|
||||
|
||||
# Assert
|
||||
assert result == (None, 0, '')
|
||||
gates_activity.debug.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_mlflow_content_gate_filter_returns_false(gates_activity):
|
||||
def test_mlflow_content_gate_filter_returns_false(gates_activity):
|
||||
input_data = {
|
||||
**metadata,
|
||||
'filters': {'NAN_VALUES': {'POLICY': 'STOP', 'CONFIG': {}}},
|
||||
@@ -463,7 +442,7 @@ async def test_mlflow_content_gate_filter_returns_false(gates_activity):
|
||||
'path_priority': ['STOP', 'CONTINUE', 'REPEAT'],
|
||||
}
|
||||
|
||||
result = await gates_activity.mlflow_content_gate(input_data)
|
||||
result = gates_activity.mlflow_content_gate(input_data)
|
||||
|
||||
assert result == (None, 0, '')
|
||||
gates_activity.debug.assert_called()
|
||||
@@ -525,8 +504,7 @@ def test_get_prediction_store_policy_valid_policy(gates_activity):
|
||||
assert policy_value == 1
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_format_prediction_no_timestamp(gates_activity):
|
||||
def test_format_prediction_no_timestamp(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -545,7 +523,7 @@ async def test_format_prediction_no_timestamp(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.format_prediction(input_data)
|
||||
result = gates_activity.format_prediction(input_data)
|
||||
|
||||
# Assert
|
||||
assert result['prediction'] == {0: 1}
|
||||
@@ -557,8 +535,7 @@ async def test_format_prediction_no_timestamp(gates_activity):
|
||||
assert result['comments'] == {0: ''}
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_format_prediction_with_timestamp_erl(gates_activity):
|
||||
def test_format_prediction_with_timestamp_erl(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -585,7 +562,7 @@ async def test_format_prediction_with_timestamp_erl(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.format_prediction(input_data)
|
||||
result = gates_activity.format_prediction(input_data)
|
||||
|
||||
# Assert
|
||||
assert result['prediction'] == {0: 2, 1: 1}
|
||||
@@ -597,8 +574,7 @@ async def test_format_prediction_with_timestamp_erl(gates_activity):
|
||||
assert result['comments'] == {0: '', 1: ''}
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_format_prediction_with_timestamp_lts(gates_activity):
|
||||
def test_format_prediction_with_timestamp_lts(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -625,7 +601,7 @@ async def test_format_prediction_with_timestamp_lts(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.format_prediction(input_data)
|
||||
result = gates_activity.format_prediction(input_data)
|
||||
|
||||
# Assert
|
||||
assert result['prediction'] == {0: 3, 1: 2}
|
||||
@@ -637,8 +613,7 @@ async def test_format_prediction_with_timestamp_lts(gates_activity):
|
||||
assert result['comments'] == {0: '', 1: ''}
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_format_prediction_with_timestamp_invalid_policy(gates_activity):
|
||||
def test_format_prediction_with_timestamp_invalid_policy(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -663,16 +638,15 @@ async def test_format_prediction_with_timestamp_invalid_policy(gates_activity):
|
||||
gates_activity.get_prediction_store_policy = MagicMock(return_value=('invalid', 1))
|
||||
|
||||
try:
|
||||
await gates_activity.format_prediction(input_data)
|
||||
gates_activity.format_prediction(input_data)
|
||||
except ValueError as e:
|
||||
assert str(e) == 'Invalid policy type: invalid'
|
||||
else:
|
||||
raise AssertionError('Expected ValueError')
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
@patch('laborious.activities.gates.MinioDataFramePayload.from_dataframe', new_callable=AsyncMock)
|
||||
async def test_format_transformed_data_single_row(mock_from_dataframe, gates_activity):
|
||||
@patch('laborious.activities.gates.MinioDataFramePayload.from_dataframe', new_callable=MagicMock)
|
||||
def test_format_transformed_data_single_row(mock_from_dataframe, gates_activity):
|
||||
# Arrange
|
||||
payload_result = MagicMock()
|
||||
mock_from_dataframe.return_value = payload_result
|
||||
@@ -691,7 +665,7 @@ async def test_format_transformed_data_single_row(mock_from_dataframe, gates_act
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.format_transformed_data(input_data)
|
||||
result = gates_activity.format_transformed_data(input_data)
|
||||
|
||||
# Assert
|
||||
assert result is payload_result
|
||||
@@ -705,9 +679,8 @@ async def test_format_transformed_data_single_row(mock_from_dataframe, gates_act
|
||||
gates_activity.info.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
@patch('laborious.activities.gates.MinioDataFramePayload.from_dataframe', new_callable=AsyncMock)
|
||||
async def test_format_transformed_data_multiple_rows(mock_from_dataframe, gates_activity):
|
||||
@patch('laborious.activities.gates.MinioDataFramePayload.from_dataframe', new_callable=MagicMock)
|
||||
def test_format_transformed_data_multiple_rows(mock_from_dataframe, gates_activity):
|
||||
# Arrange
|
||||
payload_result = MagicMock()
|
||||
mock_from_dataframe.return_value = payload_result
|
||||
@@ -732,7 +705,7 @@ async def test_format_transformed_data_multiple_rows(mock_from_dataframe, gates_
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.format_transformed_data(input_data)
|
||||
result = gates_activity.format_transformed_data(input_data)
|
||||
|
||||
# Assert
|
||||
assert result is payload_result
|
||||
@@ -746,9 +719,8 @@ async def test_format_transformed_data_multiple_rows(mock_from_dataframe, gates_
|
||||
gates_activity.info.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
@patch('laborious.activities.gates.MinioDataFramePayload.from_dataframe', new_callable=AsyncMock)
|
||||
async def test_format_transformed_data_empty_data(mock_from_dataframe, gates_activity):
|
||||
@patch('laborious.activities.gates.MinioDataFramePayload.from_dataframe', new_callable=MagicMock)
|
||||
def test_format_transformed_data_empty_data(mock_from_dataframe, gates_activity):
|
||||
# Arrange
|
||||
payload_result = MagicMock()
|
||||
mock_from_dataframe.return_value = payload_result
|
||||
@@ -760,7 +732,7 @@ async def test_format_transformed_data_empty_data(mock_from_dataframe, gates_act
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.format_transformed_data(input_data)
|
||||
result = gates_activity.format_transformed_data(input_data)
|
||||
|
||||
# Assert
|
||||
assert result is payload_result
|
||||
@@ -774,8 +746,7 @@ async def test_format_transformed_data_empty_data(mock_from_dataframe, gates_act
|
||||
gates_activity.info.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_format_default_prediction(gates_activity):
|
||||
def test_format_default_prediction(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -786,7 +757,7 @@ async def test_format_default_prediction(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.format_default_prediction(input_data)
|
||||
result = gates_activity.format_default_prediction(input_data)
|
||||
|
||||
# Assert
|
||||
assert result['prediction'] == {0: 0}
|
||||
@@ -799,8 +770,7 @@ async def test_format_default_prediction(gates_activity):
|
||||
gates_activity.debug.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_format_retrain_report(gates_activity):
|
||||
def test_format_retrain_report(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -819,7 +789,7 @@ async def test_format_retrain_report(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.format_retrain_report(input_data)
|
||||
result = gates_activity.format_retrain_report(input_data)
|
||||
|
||||
# Assert
|
||||
assert result['model_id'] == {0: 'test_model'}
|
||||
@@ -831,8 +801,7 @@ async def test_format_retrain_report(gates_activity):
|
||||
assert result['mlflow_experiment_id'] == {0: 'test_mlflow_experiment_id'}
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_format_retrain_report_failure(gates_activity):
|
||||
def test_format_retrain_report_failure(gates_activity):
|
||||
# Arrange
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -851,7 +820,7 @@ async def test_format_retrain_report_failure(gates_activity):
|
||||
}
|
||||
|
||||
# Act
|
||||
result = await gates_activity.format_retrain_report(input_data)
|
||||
result = gates_activity.format_retrain_report(input_data)
|
||||
|
||||
# Assert
|
||||
assert result['model_id'] == {0: 'test_model'}
|
||||
@@ -865,9 +834,8 @@ async def test_format_retrain_report_failure(gates_activity):
|
||||
gates_activity.debug.assert_called()
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
@patch('laborious.activities.gates.metrics')
|
||||
async def test_write_metrics(mock_metrics, gates_activity):
|
||||
def test_write_metrics(mock_metrics, gates_activity):
|
||||
"""Test write_metrics method."""
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -878,7 +846,7 @@ async def test_write_metrics(mock_metrics, gates_activity):
|
||||
},
|
||||
'opc_metrics': {'server1': {'tag1': 0.1, 'tag2': 0.2}},
|
||||
}
|
||||
await gates_activity.write_metrics(input_data)
|
||||
gates_activity.write_metrics(input_data)
|
||||
core_tags = {
|
||||
'pod_id': gates_activity.pod_id,
|
||||
'runtime': gates_activity.runtime,
|
||||
@@ -886,7 +854,7 @@ async def test_write_metrics(mock_metrics, gates_activity):
|
||||
'model_name': metadata['metadata']['model_name'],
|
||||
'workflow_name': metadata['metadata']['workflow_name'],
|
||||
}
|
||||
gates_activity.emit_metric.assert_has_calls(
|
||||
gates_activity.emit_metric_sync.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
metric_object=mock_metrics.PREDICTIONS_WRITTEN_COUNT,
|
||||
@@ -894,7 +862,7 @@ async def test_write_metrics(mock_metrics, gates_activity):
|
||||
),
|
||||
]
|
||||
)
|
||||
gates_activity.emit_metric.assert_has_calls(
|
||||
gates_activity.emit_metric_sync.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
metric_object=mock_metrics.PREDICTION_CONFIDENCE_MONITOR,
|
||||
@@ -904,7 +872,7 @@ async def test_write_metrics(mock_metrics, gates_activity):
|
||||
),
|
||||
]
|
||||
)
|
||||
gates_activity.emit_metric.assert_has_calls(
|
||||
gates_activity.emit_metric_sync.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
metric_object=mock_metrics.PREDICTION_RESPONSE_TIME_MONITOR,
|
||||
@@ -914,7 +882,7 @@ async def test_write_metrics(mock_metrics, gates_activity):
|
||||
),
|
||||
]
|
||||
)
|
||||
gates_activity.emit_metric.assert_has_calls(
|
||||
gates_activity.emit_metric_sync.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
metric_object=mock_metrics.PREDICTION_OPC_WRITING_COUNT,
|
||||
@@ -926,7 +894,7 @@ async def test_write_metrics(mock_metrics, gates_activity):
|
||||
),
|
||||
]
|
||||
)
|
||||
gates_activity.emit_metric.assert_has_calls(
|
||||
gates_activity.emit_metric_sync.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
metric_object=mock_metrics.PREDICTION_OPC_WRITING_RESPONSE_TIME_MONITOR,
|
||||
@@ -940,7 +908,7 @@ async def test_write_metrics(mock_metrics, gates_activity):
|
||||
),
|
||||
]
|
||||
)
|
||||
gates_activity.emit_metric.assert_has_calls(
|
||||
gates_activity.emit_metric_sync.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
metric_object=mock_metrics.PREDICTION_OPC_WRITING_COUNT,
|
||||
@@ -952,7 +920,7 @@ async def test_write_metrics(mock_metrics, gates_activity):
|
||||
),
|
||||
]
|
||||
)
|
||||
gates_activity.emit_metric.assert_has_calls(
|
||||
gates_activity.emit_metric_sync.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
metric_object=mock_metrics.PREDICTION_OPC_WRITING_RESPONSE_TIME_MONITOR,
|
||||
@@ -968,9 +936,8 @@ async def test_write_metrics(mock_metrics, gates_activity):
|
||||
)
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
@patch('laborious.activities.gates.metrics')
|
||||
async def test_write_metrics_with_none_opc_response_time(mock_metrics, gates_activity):
|
||||
def test_write_metrics_with_none_opc_response_time(mock_metrics, gates_activity):
|
||||
"""Test write_metrics method with None response_time in opc_metrics."""
|
||||
input_data = {
|
||||
**metadata,
|
||||
@@ -981,10 +948,10 @@ async def test_write_metrics_with_none_opc_response_time(mock_metrics, gates_act
|
||||
},
|
||||
'opc_metrics': {'server1': {'tag1': 0.1, 'tag2': None}},
|
||||
}
|
||||
await gates_activity.write_metrics(input_data)
|
||||
gates_activity.write_metrics(input_data)
|
||||
|
||||
# Verify that metrics for tag1 are emitted
|
||||
gates_activity.emit_metric.assert_any_call(
|
||||
gates_activity.emit_metric_sync.assert_any_call(
|
||||
metric_object=mock_metrics.PREDICTION_OPC_WRITING_RESPONSE_TIME_MONITOR,
|
||||
method='observe',
|
||||
tags={
|
||||
@@ -1002,7 +969,38 @@ async def test_write_metrics_with_none_opc_response_time(mock_metrics, gates_act
|
||||
# Verify that metrics for tag2 (with None response_time) are NOT emitted
|
||||
calls = [
|
||||
c
|
||||
for c in gates_activity.emit_metric.call_args_list
|
||||
for c in gates_activity.emit_metric_sync.call_args_list
|
||||
if len(c[1].get('tags', {})) > 0 and c[1]['tags'].get('tag') == 'tag2'
|
||||
]
|
||||
assert len(calls) == 0, 'Metrics should not be emitted for None response_time'
|
||||
|
||||
|
||||
@patch.object(SientiaMonitoring, 'shutdown')
|
||||
def test_close_disposes_minio_repository(mock_shutdown):
|
||||
"""
|
||||
``Gates.close`` should close the optional MinIO client and clear the repository reference.
|
||||
"""
|
||||
minio = MagicMock()
|
||||
gates = Gates(
|
||||
minio_repository=minio,
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=MagicMock(),
|
||||
)
|
||||
gates.close()
|
||||
minio.close.assert_called_once()
|
||||
assert gates.minio_repository is None
|
||||
mock_shutdown.assert_called_once_with(gates)
|
||||
|
||||
|
||||
@patch.object(SientiaMonitoring, 'shutdown')
|
||||
def test_close_without_minio_repository(mock_shutdown):
|
||||
"""When no MinIO repository is configured, ``close`` only shuts down monitoring."""
|
||||
gates = Gates(
|
||||
minio_repository=None,
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=MagicMock(),
|
||||
)
|
||||
gates.close()
|
||||
mock_shutdown.assert_called_once_with(gates)
|
||||
|
||||
Reference in New Issue
Block a user