SIENTIAPDE-2012: per-group levels/ignore_models_id filtering
Extends filter_notification_alerts (slot_manager.py) and
filter_notification_reports (formatters.py) to check
receiver_group.get('levels') and receiver_group.get('ignore_models_id'),
mirroring the existing contents/ignore pattern exactly. Closes the gap
SIENTIAPDE-2011 deliberately left open: the query-time union/intersection
filter widens what gets fetched for the union of all active groups, but until
now nothing narrowed each group back down to its own configured slice.
Both checks short-circuit via `not levels_list or ...` / `not
ignore_models_id_list or ...` rather than accessing notification['level']/
['model_id'] directly, so groups that don't configure either field (today's
common case, and every existing unit test fixture) never evaluate the
right-hand side and can't raise KeyError on notifications missing those keys.
ignore_models_id reads via `.get(...) or []`, defensively handling an
explicit `null` in a receiver_groups document from the start (same fix
SIENTIAPDE-2011 needed a follow-up commit for).
Tests:
- 8 new unit tests (4 per file): levels restriction, ignore_models_id
restriction, neither-configured passthrough, explicit-None safety.
- New e2e test (test_scenario_a_2_1_per_group_levels_and_ignore_models_id)
against real Mongo/Redis/Temporal: two active groups configured
differently: SIENTIAPDE-2011's query-time filter widens the fetch to the
union of both, and this subtask's per-group filter narrows each group back
to its own levels/ignore_models_id, including the case where a model is
ignored by only one of the active groups.
- Deduplicated the two e2e suites' identical _extract_html_body helper into
e2e/helpers.py (extract_html_body), now shared by both.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -766,3 +766,143 @@ def test_filter_notification_reports(formatters):
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_filter_notification_reports_levels_restriction(formatters):
|
||||
# SIENTIAPDE-2012: a group with `levels` set only receives notifications
|
||||
# matching one of those levels.
|
||||
input_data = {
|
||||
**metadata,
|
||||
'notification_package': [
|
||||
{
|
||||
'trigger': 'test_trigger_1',
|
||||
'notification_id': 'test_notification_id_1',
|
||||
'level': 'ERROR',
|
||||
},
|
||||
{
|
||||
'trigger': 'test_trigger_2',
|
||||
'notification_id': 'test_notification_id_2',
|
||||
'level': 'INFO',
|
||||
},
|
||||
],
|
||||
'sending_configs': [
|
||||
{'group_name': 'test_group_1', 'contents': ['reports'], 'levels': ['ERROR']},
|
||||
],
|
||||
}
|
||||
|
||||
response = formatters.filter_notification_reports(input_data)
|
||||
|
||||
assert response == {
|
||||
'test_group_1': {
|
||||
'group_name': 'test_group_1',
|
||||
'contents': ['reports'],
|
||||
'levels': ['ERROR'],
|
||||
'notifications': [
|
||||
{
|
||||
'trigger': 'test_trigger_1',
|
||||
'notification_id': 'test_notification_id_1',
|
||||
'level': 'ERROR',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_filter_notification_reports_ignore_models_id_restriction(formatters):
|
||||
# SIENTIAPDE-2012: a group with `ignore_models_id` set does not receive
|
||||
# notifications from those models, but still receives everything else.
|
||||
input_data = {
|
||||
**metadata,
|
||||
'notification_package': [
|
||||
{
|
||||
'trigger': 'test_trigger_1',
|
||||
'notification_id': 'test_notification_id_1',
|
||||
'model_id': 'model_x',
|
||||
},
|
||||
{
|
||||
'trigger': 'test_trigger_2',
|
||||
'notification_id': 'test_notification_id_2',
|
||||
'model_id': 'model_y',
|
||||
},
|
||||
],
|
||||
'sending_configs': [
|
||||
{
|
||||
'group_name': 'test_group_1',
|
||||
'contents': ['reports'],
|
||||
'ignore_models_id': ['model_x'],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
response = formatters.filter_notification_reports(input_data)
|
||||
|
||||
assert response == {
|
||||
'test_group_1': {
|
||||
'group_name': 'test_group_1',
|
||||
'contents': ['reports'],
|
||||
'ignore_models_id': ['model_x'],
|
||||
'notifications': [
|
||||
{
|
||||
'trigger': 'test_trigger_2',
|
||||
'notification_id': 'test_notification_id_2',
|
||||
'model_id': 'model_y',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_filter_notification_reports_no_levels_or_ignore_models_id_passes_everything(
|
||||
formatters,
|
||||
):
|
||||
# SIENTIAPDE-2012: a group configuring neither field is unaffected -- same
|
||||
# behavior as before this subtask (only `contents`/`ignore` apply).
|
||||
input_data = {
|
||||
**metadata,
|
||||
'notification_package': [
|
||||
{
|
||||
'trigger': 'test_trigger_1',
|
||||
'notification_id': 'test_notification_id_1',
|
||||
'level': 'ERROR',
|
||||
'model_id': 'model_x',
|
||||
},
|
||||
{
|
||||
'trigger': 'test_trigger_2',
|
||||
'notification_id': 'test_notification_id_2',
|
||||
'level': 'INFO',
|
||||
'model_id': 'model_y',
|
||||
},
|
||||
],
|
||||
'sending_configs': [
|
||||
{'group_name': 'test_group_1', 'contents': ['reports']},
|
||||
],
|
||||
}
|
||||
|
||||
response = formatters.filter_notification_reports(input_data)
|
||||
|
||||
assert response['test_group_1']['notifications'] == input_data['notification_package']
|
||||
|
||||
|
||||
def test_filter_notification_reports_ignore_models_id_explicit_none_does_not_raise(
|
||||
formatters,
|
||||
):
|
||||
# receiver_groups has no schema validation -- ignore_models_id can be
|
||||
# explicitly null (key present, value None) instead of omitted. Must not
|
||||
# crash (see the equivalent SIENTIAPDE-2011 fix in load_notification_package).
|
||||
input_data = {
|
||||
**metadata,
|
||||
'notification_package': [
|
||||
{
|
||||
'trigger': 'test_trigger_1',
|
||||
'notification_id': 'test_notification_id_1',
|
||||
'model_id': 'model_x',
|
||||
},
|
||||
],
|
||||
'sending_configs': [
|
||||
{'group_name': 'test_group_1', 'contents': ['reports'], 'ignore_models_id': None},
|
||||
],
|
||||
}
|
||||
|
||||
response = formatters.filter_notification_reports(input_data)
|
||||
|
||||
assert response['test_group_1']['notifications'] == input_data['notification_package']
|
||||
|
||||
@@ -389,6 +389,162 @@ def test_filter_notification_alerts(slot_manager):
|
||||
}
|
||||
|
||||
|
||||
def test_filter_notification_alerts_levels_restriction(slot_manager):
|
||||
# SIENTIAPDE-2012: a group with `levels` set only receives notifications
|
||||
# matching one of those levels.
|
||||
slot_manager.redis_repository.get = MagicMock(side_effect=[None, None])
|
||||
|
||||
input_data = {
|
||||
**metadata,
|
||||
'notification_package': [
|
||||
{
|
||||
'trigger': 'test_trigger_1',
|
||||
'notification_id': 'test_notification_id_1',
|
||||
'level': 'ERROR',
|
||||
},
|
||||
{
|
||||
'trigger': 'test_trigger_2',
|
||||
'notification_id': 'test_notification_id_2',
|
||||
'level': 'WARNING',
|
||||
},
|
||||
],
|
||||
'sending_configs': [
|
||||
{'group_name': 'test_group_1', 'contents': ['core_alerts'], 'levels': ['ERROR']},
|
||||
],
|
||||
'notification_ttl': 300,
|
||||
'mail_type': 'test_mail_type',
|
||||
}
|
||||
|
||||
response = slot_manager.filter_notification_alerts(input_data)
|
||||
|
||||
assert response == {
|
||||
'test_group_1': {
|
||||
'group_name': 'test_group_1',
|
||||
'contents': ['core_alerts'],
|
||||
'levels': ['ERROR'],
|
||||
'notifications': [
|
||||
{
|
||||
'trigger': 'test_trigger_1',
|
||||
'notification_id': 'test_notification_id_1',
|
||||
'level': 'ERROR',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_filter_notification_alerts_ignore_models_id_restriction(slot_manager):
|
||||
# SIENTIAPDE-2012: a group with `ignore_models_id` set does not receive
|
||||
# notifications from those models, but still receives everything else.
|
||||
slot_manager.redis_repository.get = MagicMock(side_effect=[None, None])
|
||||
|
||||
input_data = {
|
||||
**metadata,
|
||||
'notification_package': [
|
||||
{
|
||||
'trigger': 'test_trigger_1',
|
||||
'notification_id': 'test_notification_id_1',
|
||||
'model_id': 'model_x',
|
||||
},
|
||||
{
|
||||
'trigger': 'test_trigger_2',
|
||||
'notification_id': 'test_notification_id_2',
|
||||
'model_id': 'model_y',
|
||||
},
|
||||
],
|
||||
'sending_configs': [
|
||||
{
|
||||
'group_name': 'test_group_1',
|
||||
'contents': ['core_alerts'],
|
||||
'ignore_models_id': ['model_x'],
|
||||
},
|
||||
],
|
||||
'notification_ttl': 300,
|
||||
'mail_type': 'test_mail_type',
|
||||
}
|
||||
|
||||
response = slot_manager.filter_notification_alerts(input_data)
|
||||
|
||||
assert response == {
|
||||
'test_group_1': {
|
||||
'group_name': 'test_group_1',
|
||||
'contents': ['core_alerts'],
|
||||
'ignore_models_id': ['model_x'],
|
||||
'notifications': [
|
||||
{
|
||||
'trigger': 'test_trigger_2',
|
||||
'notification_id': 'test_notification_id_2',
|
||||
'model_id': 'model_y',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_filter_notification_alerts_no_levels_or_ignore_models_id_passes_everything(
|
||||
slot_manager,
|
||||
):
|
||||
# SIENTIAPDE-2012: a group configuring neither field is unaffected -- same
|
||||
# behavior as before this subtask (only `contents`/`ignore` apply).
|
||||
slot_manager.redis_repository.get = MagicMock(side_effect=[None, None])
|
||||
|
||||
input_data = {
|
||||
**metadata,
|
||||
'notification_package': [
|
||||
{
|
||||
'trigger': 'test_trigger_1',
|
||||
'notification_id': 'test_notification_id_1',
|
||||
'level': 'ERROR',
|
||||
'model_id': 'model_x',
|
||||
},
|
||||
{
|
||||
'trigger': 'test_trigger_2',
|
||||
'notification_id': 'test_notification_id_2',
|
||||
'level': 'INFO',
|
||||
'model_id': 'model_y',
|
||||
},
|
||||
],
|
||||
'sending_configs': [
|
||||
{'group_name': 'test_group_1', 'contents': ['core_alerts']},
|
||||
],
|
||||
'notification_ttl': 300,
|
||||
'mail_type': 'test_mail_type',
|
||||
}
|
||||
|
||||
response = slot_manager.filter_notification_alerts(input_data)
|
||||
|
||||
assert response['test_group_1']['notifications'] == input_data['notification_package']
|
||||
|
||||
|
||||
def test_filter_notification_alerts_ignore_models_id_explicit_none_does_not_raise(
|
||||
slot_manager,
|
||||
):
|
||||
# receiver_groups has no schema validation -- ignore_models_id can be
|
||||
# explicitly null (key present, value None) instead of omitted. Must not
|
||||
# crash (see the equivalent SIENTIAPDE-2011 fix in load_notification_package).
|
||||
slot_manager.redis_repository.get = MagicMock(side_effect=[None])
|
||||
|
||||
input_data = {
|
||||
**metadata,
|
||||
'notification_package': [
|
||||
{
|
||||
'trigger': 'test_trigger_1',
|
||||
'notification_id': 'test_notification_id_1',
|
||||
'model_id': 'model_x',
|
||||
},
|
||||
],
|
||||
'sending_configs': [
|
||||
{'group_name': 'test_group_1', 'contents': ['core_alerts'], 'ignore_models_id': None},
|
||||
],
|
||||
'notification_ttl': 300,
|
||||
'mail_type': 'test_mail_type',
|
||||
}
|
||||
|
||||
response = slot_manager.filter_notification_alerts(input_data)
|
||||
|
||||
assert response['test_group_1']['notifications'] == input_data['notification_package']
|
||||
|
||||
|
||||
def test_store_notification_cache(slot_manager):
|
||||
"""Test store_notification_cache"""
|
||||
test_data = {
|
||||
|
||||
Reference in New Issue
Block a user