SIENTIAPDE-1646
Update project configuration and dependencies - Added .mypy_cache and .cursor to .gitignore. - Changed asyncio_default_fixture_loop_scope and asyncio_default_test_loop_scope to "session" in pyproject.toml. - Updated e2e testing dependencies in requirements-dev.txt, replacing fakeredis and mongomock with pytest-httpserver. - Updated requirements.txt to use sientia_do instead of a specific git commit. - Modified sonar-project.properties to remove a file from coverage exclusions. - Enhanced E2E test fixtures in e2e/conftest.py for better container management. - Cleaned up e2e test files related to CoreScouter and PIWebAPIScouter workflows.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from typing import Any
|
||||
from unittest.mock import ANY, AsyncMock, MagicMock, Mock, call, patch
|
||||
from unittest.mock import ANY, MagicMock, Mock, call, patch
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
@@ -21,8 +21,6 @@ def gates_fixture():
|
||||
metrics_controller=metrics_controller,
|
||||
)
|
||||
gates.send_notification = MagicMock()
|
||||
gates.send_notification_async = AsyncMock()
|
||||
gates.emit_metric = AsyncMock()
|
||||
|
||||
gates.logger = logger
|
||||
gates.notification_handler = notification_handler
|
||||
@@ -48,8 +46,7 @@ def test_close(mock_sientia_monitoring, gates_fixture):
|
||||
mock_sientia_monitoring.shutdown.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_data_quality_gate_with_null_values_filter_discard(gates_fixture):
|
||||
def test_data_quality_gate_with_null_values_filter_discard(gates_fixture):
|
||||
"""Test data_quality_gate with NULL_VALUES_FILTER and DISCARD policy."""
|
||||
# Setup test data
|
||||
input_data = {
|
||||
@@ -69,16 +66,15 @@ async def test_data_quality_gate_with_null_values_filter_discard(gates_fixture):
|
||||
}
|
||||
|
||||
# Execute
|
||||
result = await gates_fixture.data_quality_gate(input_data)
|
||||
result = gates_fixture.data_quality_gate(input_data)
|
||||
|
||||
# Verify
|
||||
assert len(result['tag']) == 2
|
||||
assert 'tag2' not in result['tag']
|
||||
gates_fixture.send_notification_async.assert_called_once()
|
||||
gates_fixture.send_notification.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_data_quality_gate_with_out_of_bounds_filter_keep(gates_fixture):
|
||||
def test_data_quality_gate_with_out_of_bounds_filter_keep(gates_fixture):
|
||||
"""Test data_quality_gate with OUT_OF_BOUNDS_FILTER and KEEP policy."""
|
||||
# Setup test data with out of bounds values
|
||||
input_data = {
|
||||
@@ -103,15 +99,14 @@ async def test_data_quality_gate_with_out_of_bounds_filter_keep(gates_fixture):
|
||||
{'OUT_OF_BOUNDS_FILTER': lambda df: df[df['tag'] == 'tag2']},
|
||||
):
|
||||
# Execute
|
||||
result = await gates_fixture.data_quality_gate(input_data)
|
||||
result = gates_fixture.data_quality_gate(input_data)
|
||||
|
||||
# Verify data is kept but notification is sent
|
||||
assert len(result['tag']) == 3 # All rows kept
|
||||
gates_fixture.send_notification_async.assert_called_once()
|
||||
gates_fixture.send_notification.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_data_quality_gate_with_multiple_filters(gates_fixture):
|
||||
def test_data_quality_gate_with_multiple_filters(gates_fixture):
|
||||
"""Test data_quality_gate with multiple filters."""
|
||||
# Setup test data
|
||||
input_data = {
|
||||
@@ -134,7 +129,7 @@ async def test_data_quality_gate_with_multiple_filters(gates_fixture):
|
||||
**metadata,
|
||||
}
|
||||
|
||||
result = await gates_fixture.data_quality_gate(input_data)
|
||||
result = gates_fixture.data_quality_gate(input_data)
|
||||
|
||||
# Verify only tag1 and tag4 remain (tag2 has null, tag3 is out of bounds)
|
||||
assert result == {
|
||||
@@ -144,11 +139,10 @@ async def test_data_quality_gate_with_multiple_filters(gates_fixture):
|
||||
'timestamp': {0: '2023-01-01', 3: '2023-01-04'},
|
||||
}
|
||||
# Should be called twice (once for each filter)
|
||||
assert gates_fixture.send_notification_async.call_count == 2
|
||||
assert gates_fixture.send_notification.call_count == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_data_quality_gate_with_unknown_filter(gates_fixture):
|
||||
def test_data_quality_gate_with_unknown_filter(gates_fixture):
|
||||
"""Test data_quality_gate with an unknown filter."""
|
||||
# Setup test data with unknown filter
|
||||
gates_fixture.warning = MagicMock()
|
||||
@@ -160,7 +154,7 @@ async def test_data_quality_gate_with_unknown_filter(gates_fixture):
|
||||
}
|
||||
|
||||
# Execute
|
||||
result = await gates_fixture.data_quality_gate(input_data)
|
||||
result = gates_fixture.data_quality_gate(input_data)
|
||||
|
||||
# Verify data is unchanged and warning is logged
|
||||
assert len(result['tag']) == 1
|
||||
@@ -169,8 +163,7 @@ async def test_data_quality_gate_with_unknown_filter(gates_fixture):
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_data_quality_gate_with_filter_error(gates_fixture):
|
||||
def test_data_quality_gate_with_filter_error(gates_fixture):
|
||||
"""Test data_quality_gate when a filter raises an exception."""
|
||||
# Setup test data
|
||||
input_data = {
|
||||
@@ -188,19 +181,18 @@ async def test_data_quality_gate_with_filter_error(gates_fixture):
|
||||
'scouter.activities.gates.quality_gate_filters', {'NULL_VALUES_FILTER': failing_filter}
|
||||
):
|
||||
# Execute
|
||||
result = await gates_fixture.data_quality_gate(input_data)
|
||||
result = gates_fixture.data_quality_gate(input_data)
|
||||
|
||||
# Verify error notification is sent and data is unchanged
|
||||
assert len(result['tag']) == 1
|
||||
gates_fixture.send_notification_async.assert_called_once()
|
||||
call_args = gates_fixture.send_notification_async.call_args[1]
|
||||
gates_fixture.send_notification.assert_called_once()
|
||||
call_args = gates_fixture.send_notification.call_args[1]
|
||||
assert call_args['notification_id'] == 'DATA_QUALITY_GATE_ISSUES'
|
||||
assert call_args['level'] == NotificationLevel.ERROR
|
||||
assert 'Filter error' in call_args['message']
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_data_quality_gate_with_empty_data(gates_fixture):
|
||||
def test_data_quality_gate_with_empty_data(gates_fixture):
|
||||
"""Test data_quality_gate with empty input data."""
|
||||
# Setup empty input data
|
||||
input_data = {
|
||||
@@ -211,15 +203,14 @@ async def test_data_quality_gate_with_empty_data(gates_fixture):
|
||||
}
|
||||
|
||||
# Execute
|
||||
result = await gates_fixture.data_quality_gate(input_data)
|
||||
result = gates_fixture.data_quality_gate(input_data)
|
||||
|
||||
# Verify empty result and no notifications
|
||||
assert len(result['tag']) == 0
|
||||
gates_fixture.send_notification.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_data_quality_gate_with_no_filters(gates_fixture):
|
||||
def test_data_quality_gate_with_no_filters(gates_fixture):
|
||||
"""Test data_quality_gate with no filters specified."""
|
||||
# Setup test data with no filters
|
||||
input_data = {
|
||||
@@ -230,7 +221,7 @@ async def test_data_quality_gate_with_no_filters(gates_fixture):
|
||||
}
|
||||
|
||||
# Execute
|
||||
result = await gates_fixture.data_quality_gate(input_data)
|
||||
result = gates_fixture.data_quality_gate(input_data)
|
||||
|
||||
# Verify data is unchanged and no notifications
|
||||
assert len(result['tag']) == 1
|
||||
@@ -254,23 +245,22 @@ async def test_data_quality_gate_with_no_filters(gates_fixture):
|
||||
(pd.DataFrame({'value': [np.nan, np.nan]}), 'avg', None),
|
||||
# Invalid aggregation function
|
||||
(pd.DataFrame({'value': [1.0, 2.0]}), 'invalid', 'continue'),
|
||||
(pd.DataFrame({'value': [10.0]}), 'invalid', 'continue'),
|
||||
],
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_aggregation(gates_fixture, group_data, aggr_function, expected_result):
|
||||
def test_apply_aggregation(gates_fixture, group_data, aggr_function, expected_result):
|
||||
"""Test apply_aggregation method with various scenarios."""
|
||||
result = await gates_fixture.apply_aggregation(group_data, aggr_function, metadata)
|
||||
result = gates_fixture.apply_aggregation(group_data, aggr_function, metadata)
|
||||
assert result == expected_result
|
||||
|
||||
# Check notification was sent for invalid function
|
||||
if aggr_function == 'invalid':
|
||||
gates_fixture.send_notification_async.assert_called_once()
|
||||
gates_fixture.send_notification.assert_called_once()
|
||||
else:
|
||||
gates_fixture.send_notification_async.assert_not_called()
|
||||
gates_fixture.send_notification.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aggregate_data(gates_fixture):
|
||||
def test_aggregate_data(gates_fixture):
|
||||
"""Test aggregate_data method with multiple groups and aggregation functions."""
|
||||
input_data = {
|
||||
'data': [
|
||||
@@ -299,16 +289,15 @@ async def test_aggregate_data(gates_fixture):
|
||||
}
|
||||
|
||||
# Execute
|
||||
result = await gates_fixture.aggregate_data(input_data)
|
||||
result = gates_fixture.aggregate_data(input_data)
|
||||
|
||||
# Verify
|
||||
assert result == expected_result
|
||||
gates_fixture.send_notification.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aggregate_data_with_continue(gates_fixture):
|
||||
gates_fixture.apply_aggregation = AsyncMock(return_value='continue')
|
||||
def test_aggregate_data_with_continue(gates_fixture):
|
||||
gates_fixture.apply_aggregation = MagicMock(return_value='continue')
|
||||
|
||||
input_data = {
|
||||
'data': [
|
||||
@@ -331,15 +320,14 @@ async def test_aggregate_data_with_continue(gates_fixture):
|
||||
expected_result: dict[str, Any] = {}
|
||||
|
||||
# Execute
|
||||
result = await gates_fixture.aggregate_data(input_data)
|
||||
result = gates_fixture.aggregate_data(input_data)
|
||||
|
||||
# Verify
|
||||
assert result == expected_result
|
||||
gates_fixture.send_notification_async.assert_not_called()
|
||||
gates_fixture.send_notification.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aggregate_data_raise_exception(gates_fixture):
|
||||
def test_aggregate_data_raise_exception(gates_fixture):
|
||||
gates_fixture.apply_aggregation = MagicMock(side_effect=Exception('Test exception'))
|
||||
|
||||
input_data = {
|
||||
@@ -360,10 +348,10 @@ async def test_aggregate_data_raise_exception(gates_fixture):
|
||||
}
|
||||
|
||||
try:
|
||||
await gates_fixture.aggregate_data(input_data)
|
||||
gates_fixture.aggregate_data(input_data)
|
||||
except Exception as e:
|
||||
assert str(e) == 'Test exception'
|
||||
gates_fixture.send_notification_async.assert_called_once_with(
|
||||
gates_fixture.send_notification.assert_called_once_with(
|
||||
metadata=metadata['metadata'],
|
||||
notification_id='AGGREGATION_ISSUES',
|
||||
message='Error aggregating data: Test exception',
|
||||
@@ -375,9 +363,8 @@ async def test_aggregate_data_raise_exception(gates_fixture):
|
||||
raise AssertionError('Exception not raised')
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('scouter.activities.gates.metrics')
|
||||
async def test_write_metrics(mock_metrics, gates_fixture):
|
||||
def test_write_metrics(mock_metrics, gates_fixture):
|
||||
"""Test write_metrics method."""
|
||||
input_data = {
|
||||
'metadata': metadata['metadata'],
|
||||
@@ -386,7 +373,7 @@ async def test_write_metrics(mock_metrics, gates_fixture):
|
||||
'value': [1.0, 2.0, None],
|
||||
},
|
||||
}
|
||||
await gates_fixture.write_metrics(input_data)
|
||||
gates_fixture.write_metrics(input_data)
|
||||
mock_metrics.LABORIOUS_DATA_WRITTEN_COUNT.labels.assert_called_once_with(
|
||||
pod_id=gates_fixture.pod_id,
|
||||
model_name=metadata['metadata']['model_name'],
|
||||
|
||||
Reference in New Issue
Block a user