Skip to content

Commit caf8638

Browse files
committed
move fire_policy_webhooks in engine
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent 5673655 commit caf8638

3 files changed

Lines changed: 32 additions & 43 deletions

File tree

policy/engine.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,30 @@
88

99
from django.utils import timezone
1010

11+
from notification.models import fire_webhooks
1112
from policy.rules import RULE_REGISTRY
1213
from product_portfolio.models import ProductPolicyViolation
1314

1415

16+
def fire_policy_webhooks(product, new_violations, resolved_count):
17+
"""Fire policy webhooks for newly detected or resolved violations."""
18+
if new_violations:
19+
lines = [
20+
f"- {violation.rule_label}: {violation.violation_count} violation(s)"
21+
for violation in new_violations
22+
]
23+
payload = {
24+
"text": (f"[DejaCode] Policy violations detected for {product}\n" + "\n".join(lines))
25+
}
26+
fire_webhooks("policy.violation_detected", instance=product, payload_override=payload)
27+
28+
if resolved_count:
29+
payload = {
30+
"text": (f"[DejaCode] {resolved_count} policy violation(s) resolved for {product}")
31+
}
32+
fire_webhooks("policy.violation_resolved", instance=product, payload_override=payload)
33+
34+
1535
def get_effective_config(rule_type, dataspace):
1636
"""
1737
Resolve threshold, parameters, and is_active for a rule type in a given dataspace.
@@ -95,4 +115,5 @@ def evaluate_rules(product):
95115
new_violations.append(violation)
96116
resolved_count += resolved
97117

118+
fire_policy_webhooks(product, new_violations, resolved_count)
98119
return new_violations, resolved_count

policy/tasks.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,11 @@
1313
from django_rq import job
1414

1515
from dje.models import get_unsecured_manager
16-
from notification.models import fire_webhooks
1716
from policy.engine import evaluate_rules
1817

1918
logger = logging.getLogger(__name__)
2019

2120

22-
def fire_policy_webhooks(product, new_violations, resolved_count):
23-
"""Fire policy webhooks for newly detected or resolved violations."""
24-
if new_violations:
25-
lines = [
26-
f"- {violation.rule_label}: {violation.violation_count} violation(s)"
27-
for violation in new_violations
28-
]
29-
payload = {
30-
"text": (f"[DejaCode] Policy violations detected for {product}\n" + "\n".join(lines))
31-
}
32-
fire_webhooks("policy.violation_detected", instance=product, payload_override=payload)
33-
34-
if resolved_count:
35-
payload = {
36-
"text": (f"[DejaCode] {resolved_count} policy violation(s) resolved for {product}")
37-
}
38-
fire_webhooks("policy.violation_resolved", instance=product, payload_override=payload)
39-
40-
4121
@job
4222
def evaluate_product_rules_task(product_uuid):
4323
"""Evaluate all active policy rules for the given product and fire webhooks on changes."""
@@ -55,7 +35,6 @@ def evaluate_product_rules_task(product_uuid):
5535
f"Policy rules evaluated for {product}: "
5636
f"{len(new_violations)} new violation(s), {resolved_count} resolved."
5737
)
58-
fire_policy_webhooks(product, new_violations, resolved_count)
5938

6039

6140
@job
@@ -82,6 +61,5 @@ def evaluate_all_products_rules_task(include_locked=False, product_uuids=None):
8261
f"Policy rules evaluated for {product}: "
8362
f"{len(new_violations)} new violation(s), {resolved_count} resolved."
8463
)
85-
fire_policy_webhooks(product, new_violations, resolved_count)
8664

8765
logger.info(f"Policy rule evaluation complete for {count} product(s).")

policy/tests/test_tasks.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
from django.test import TestCase
1414

1515
from dje.models import Dataspace
16+
from policy.engine import fire_policy_webhooks
1617
from policy.tasks import evaluate_all_products_rules_task
1718
from policy.tasks import evaluate_product_rules_task
18-
from policy.tasks import fire_policy_webhooks
1919
from product_portfolio.tests import make_product
2020
from product_portfolio.tests import make_product_status
2121

@@ -25,7 +25,7 @@ def setUp(self):
2525
self.dataspace = Dataspace.objects.create(name="nexB")
2626
self.product = make_product(self.dataspace)
2727

28-
@patch("policy.tasks.fire_webhooks")
28+
@patch("policy.engine.fire_webhooks")
2929
def test_fire_policy_webhooks_dispatches_violation_detected(self, mock_fire):
3030
violation = MagicMock()
3131
violation.rule_label = "Usage Policy Error"
@@ -37,15 +37,15 @@ def test_fire_policy_webhooks_dispatches_violation_detected(self, mock_fire):
3737
self.assertIn("Policy violations detected", kwargs["payload_override"]["text"])
3838
self.assertIn("Usage Policy Error", kwargs["payload_override"]["text"])
3939

40-
@patch("policy.tasks.fire_webhooks")
40+
@patch("policy.engine.fire_webhooks")
4141
def test_fire_policy_webhooks_dispatches_violation_resolved(self, mock_fire):
4242
fire_policy_webhooks(self.product, new_violations=[], resolved_count=2)
4343
mock_fire.assert_called_once()
4444
event_name, kwargs = mock_fire.call_args[0][0], mock_fire.call_args[1]
4545
self.assertEqual("policy.violation_resolved", event_name)
4646
self.assertIn("2 policy violation(s) resolved", kwargs["payload_override"]["text"])
4747

48-
@patch("policy.tasks.fire_webhooks")
48+
@patch("policy.engine.fire_webhooks")
4949
def test_fire_policy_webhooks_dispatches_both_events(self, mock_fire):
5050
violation = MagicMock()
5151
violation.rule_label = "License Coverage Gap"
@@ -56,7 +56,7 @@ def test_fire_policy_webhooks_dispatches_both_events(self, mock_fire):
5656
self.assertIn("policy.violation_detected", events_fired)
5757
self.assertIn("policy.violation_resolved", events_fired)
5858

59-
@patch("policy.tasks.fire_webhooks")
59+
@patch("policy.engine.fire_webhooks")
6060
def test_fire_policy_webhooks_silent_when_no_changes(self, mock_fire):
6161
fire_policy_webhooks(self.product, new_violations=[], resolved_count=0)
6262
mock_fire.assert_not_called()
@@ -67,31 +67,26 @@ def setUp(self):
6767
self.dataspace = Dataspace.objects.create(name="nexB")
6868
self.product = make_product(self.dataspace)
6969

70-
@patch("policy.tasks.fire_policy_webhooks")
7170
@patch("policy.tasks.evaluate_rules")
72-
def test_evaluate_product_rules_task_runs_evaluation(self, mock_evaluate, mock_fire):
71+
def test_evaluate_product_rules_task_runs_evaluation(self, mock_evaluate):
7372
mock_evaluate.return_value = ([], 0)
7473
evaluate_product_rules_task(product_uuid=self.product.uuid)
7574
mock_evaluate.assert_called_once_with(self.product)
76-
mock_fire.assert_called_once_with(self.product, [], 0)
7775

78-
@patch("policy.tasks.fire_policy_webhooks")
7976
@patch("policy.tasks.evaluate_rules")
80-
def test_evaluate_product_rules_task_unknown_uuid_logs_error(self, mock_evaluate, mock_fire):
77+
def test_evaluate_product_rules_task_unknown_uuid_logs_error(self, mock_evaluate):
8178
with self.assertLogs("policy.tasks", level="ERROR") as captured:
8279
evaluate_product_rules_task(product_uuid=uuid.uuid4())
8380
mock_evaluate.assert_not_called()
84-
mock_fire.assert_not_called()
8581
self.assertTrue(any("not found" in line for line in captured.output))
8682

8783

8884
class EvaluateAllProductsRulesTaskTestCase(TestCase):
8985
def setUp(self):
9086
self.dataspace = Dataspace.objects.create(name="nexB")
9187

92-
@patch("policy.tasks.fire_policy_webhooks")
9388
@patch("policy.tasks.evaluate_rules")
94-
def test_evaluate_all_products_excludes_locked_by_default(self, mock_evaluate, mock_fire):
89+
def test_evaluate_all_products_excludes_locked_by_default(self, mock_evaluate):
9590
# Regression: previously called .exclude_locked() on DataspacedQuerySet which lacks that
9691
# method. Now uses .exclude(configuration_status__is_locked=True) inline.
9792
mock_evaluate.return_value = ([], 0)
@@ -106,9 +101,8 @@ def test_evaluate_all_products_excludes_locked_by_default(self, mock_evaluate, m
106101
self.assertIn(active_product, evaluated_products)
107102
self.assertNotIn(locked_product, evaluated_products)
108103

109-
@patch("policy.tasks.fire_policy_webhooks")
110104
@patch("policy.tasks.evaluate_rules")
111-
def test_evaluate_all_products_includes_locked_when_requested(self, mock_evaluate, mock_fire):
105+
def test_evaluate_all_products_includes_locked_when_requested(self, mock_evaluate):
112106
mock_evaluate.return_value = ([], 0)
113107
locked_status = make_product_status(self.dataspace, is_locked=True)
114108
locked_product = make_product(self.dataspace, configuration_status=locked_status)
@@ -119,9 +113,8 @@ def test_evaluate_all_products_includes_locked_when_requested(self, mock_evaluat
119113
evaluated_products = [c[0][0] for c in mock_evaluate.call_args_list]
120114
self.assertIn(locked_product, evaluated_products)
121115

122-
@patch("policy.tasks.fire_policy_webhooks")
123116
@patch("policy.tasks.evaluate_rules")
124-
def test_evaluate_all_products_filters_by_uuids(self, mock_evaluate, mock_fire):
117+
def test_evaluate_all_products_filters_by_uuids(self, mock_evaluate):
125118
mock_evaluate.return_value = ([], 0)
126119
product_a = make_product(self.dataspace)
127120
product_b = make_product(self.dataspace)
@@ -133,11 +126,8 @@ def test_evaluate_all_products_filters_by_uuids(self, mock_evaluate, mock_fire):
133126
self.assertIn(product_a, evaluated_products)
134127
self.assertNotIn(product_b, evaluated_products)
135128

136-
@patch("policy.tasks.fire_policy_webhooks")
137129
@patch("policy.tasks.evaluate_rules")
138-
def test_evaluate_all_products_uuid_filter_ignores_locked_exclusion(
139-
self, mock_evaluate, mock_fire
140-
):
130+
def test_evaluate_all_products_uuid_filter_ignores_locked_exclusion(self, mock_evaluate):
141131
mock_evaluate.return_value = ([], 0)
142132
locked_status = make_product_status(self.dataspace, is_locked=True)
143133
locked_product = make_product(self.dataspace, configuration_status=locked_status)

0 commit comments

Comments
 (0)