Skip to content

Commit 961b73e

Browse files
test: add Endpoint (V2) and Location (V3) propagation baselines
Extends the perf test class with two more pinned hot paths so all child models exercised by `propagate_tags_on_product_sync` are covered: product_tag_add -> 100 endpoints (V2) : 3958 product_tag_remove -> 100 endpoints (V2): 3740 product_tag_add -> 100 locations (V3) : 4532 product_tag_remove -> 100 locations (V3): 4307 Both V2 and V3 paths run regardless of the ambient `V3_FEATURE_LOCATIONS` setting via per-test `@override_settings(...)`. CI matrix runs the suite in both modes, so dynamic pin selection (`_pin(v2=..., v3=...)`) handles the small per-mode count differences on the existing finding tests.
1 parent 70b6adb commit 961b73e

1 file changed

Lines changed: 122 additions & 3 deletions

File tree

unittests/test_tag_inheritance_perf.py

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616

1717
import logging
1818

19+
from django.conf import settings
1920
from django.contrib.auth.models import User
2021
from django.test import override_settings
2122
from django.utils import timezone
2223

23-
from dojo.models import Engagement, Finding, Product, Product_Type, Test, Test_Type
24+
from dojo.models import Endpoint, Engagement, Finding, Product, Product_Type, Test, Test_Type
2425
from dojo.product.helpers import propagate_tags_on_product_sync
2526
from unittests.dojo_test_case import DojoTestCase
2627

@@ -61,6 +62,34 @@ def _make_product_with_findings(name: str, *, n_findings: int, tags: list[str] |
6162
return product
6263

6364

65+
def _make_endpoints(product: Product, n: int) -> None:
66+
"""Create N Endpoints attached directly to the product (V2 only)."""
67+
for i in range(n):
68+
ep = Endpoint(host=f"perf-{product.id}-{i}.example.com", product=product)
69+
ep.save()
70+
71+
72+
def _make_locations(product: Product, n: int) -> None:
73+
"""Create N URL Locations attached to the product via LocationManager.persist (V3 only)."""
74+
# Local imports so the file remains importable when V3_FEATURE_LOCATIONS=False.
75+
from dojo.importers.location_manager import LocationManager # noqa: PLC0415
76+
from dojo.tools.locations import LocationData # noqa: PLC0415
77+
78+
finding = Finding.objects.filter(test__engagement__product=product).first()
79+
if finding is None:
80+
# _make_product_with_findings should have been called first with n_findings>=1.
81+
msg = "_make_locations requires the product to have at least one Finding"
82+
raise RuntimeError(msg)
83+
84+
loc_data = [
85+
LocationData(type="url", data={"url": f"https://perf-{product.id}-{i}.example.com"})
86+
for i in range(n)
87+
]
88+
mgr = LocationManager(product)
89+
mgr.record_locations_for_finding(finding, loc_data)
90+
mgr.persist()
91+
92+
6493
@override_settings(
6594
CELERY_TASK_ALWAYS_EAGER=True,
6695
CELERY_TASK_EAGER_PROPAGATES=True,
@@ -236,6 +265,75 @@ def test_baseline_finding_remove_inherited_tag_sticky_re_adds(self):
236265
# Sticky re-adds the inherited tag
237266
self.assertIn("inherited", {t.name for t in finding.tags.all()})
238267

268+
# ------------------------------------------------------------------
269+
# V2: propagation to Endpoints (skipped under V3_FEATURE_LOCATIONS)
270+
# ------------------------------------------------------------------
271+
272+
@override_settings(V3_FEATURE_LOCATIONS=False)
273+
def test_baseline_product_tag_add_propagates_to_100_endpoints_v2(self):
274+
"""`product.tags.add("x")` then sync -> propagate to 100 Endpoints (V2)."""
275+
product = _make_product_with_findings("perf-add-eps", n_findings=0, tags=["initial"])
276+
_make_endpoints(product, n=100)
277+
278+
with self.assertNumQueries(self.EXPECTED_PRODUCT_TAG_ADD_100_ENDPOINTS):
279+
product.tags.add("perf-added-ep")
280+
propagate_tags_on_product_sync(product)
281+
282+
endpoint = Endpoint.objects.filter(product=product).first()
283+
self.assertIn("perf-added-ep", [t.name for t in endpoint.tags.all()])
284+
285+
@override_settings(V3_FEATURE_LOCATIONS=False)
286+
def test_baseline_product_tag_remove_propagates_to_100_endpoints_v2(self):
287+
"""`product.tags.remove("x")` then sync -> remove from 100 Endpoints (V2)."""
288+
product = _make_product_with_findings("perf-remove-eps", n_findings=0, tags=["to-remove-ep", "stays-ep"])
289+
_make_endpoints(product, n=100)
290+
291+
with self.assertNumQueries(self.EXPECTED_PRODUCT_TAG_REMOVE_100_ENDPOINTS):
292+
product.tags.remove("to-remove-ep")
293+
propagate_tags_on_product_sync(product)
294+
295+
endpoint = Endpoint.objects.filter(product=product).first()
296+
endpoint_tag_names = {t.name for t in endpoint.tags.all()}
297+
self.assertNotIn("to-remove-ep", endpoint_tag_names)
298+
self.assertIn("stays-ep", endpoint_tag_names)
299+
300+
# ------------------------------------------------------------------
301+
# V3: propagation to Locations (skipped under V2)
302+
# ------------------------------------------------------------------
303+
304+
@override_settings(V3_FEATURE_LOCATIONS=True)
305+
def test_baseline_product_tag_add_propagates_to_100_locations_v3(self):
306+
"""`product.tags.add("x")` then sync -> propagate to 100 Locations (V3)."""
307+
# Locations are created against a finding; ensure the product has one.
308+
product = _make_product_with_findings("perf-add-locs", n_findings=1, tags=["initial"])
309+
_make_locations(product, n=100)
310+
311+
with self.assertNumQueries(self.EXPECTED_PRODUCT_TAG_ADD_100_LOCATIONS):
312+
product.tags.add("perf-added-loc")
313+
propagate_tags_on_product_sync(product)
314+
315+
from dojo.location.models import Location # noqa: PLC0415
316+
loc = Location.objects.filter(products__product=product).first()
317+
self.assertIsNotNone(loc)
318+
self.assertIn("perf-added-loc", [t.name for t in loc.tags.all()])
319+
320+
@override_settings(V3_FEATURE_LOCATIONS=True)
321+
def test_baseline_product_tag_remove_propagates_to_100_locations_v3(self):
322+
"""`product.tags.remove("x")` then sync -> remove from 100 Locations (V3)."""
323+
product = _make_product_with_findings("perf-remove-locs", n_findings=1, tags=["to-remove-loc", "stays-loc"])
324+
_make_locations(product, n=100)
325+
326+
with self.assertNumQueries(self.EXPECTED_PRODUCT_TAG_REMOVE_100_LOCATIONS):
327+
product.tags.remove("to-remove-loc")
328+
propagate_tags_on_product_sync(product)
329+
330+
from dojo.location.models import Location # noqa: PLC0415
331+
loc = Location.objects.filter(products__product=product).first()
332+
self.assertIsNotNone(loc)
333+
location_tag_names = {t.name for t in loc.tags.all()}
334+
self.assertNotIn("to-remove-loc", location_tag_names)
335+
self.assertIn("stays-loc", location_tag_names)
336+
239337
# ------------------------------------------------------------------
240338
# Pinned baselines (current code; tighten in PR #1 / PR #2)
241339
# ------------------------------------------------------------------
@@ -245,9 +343,30 @@ def test_baseline_finding_remove_inherited_tag_sticky_re_adds(self):
245343

246344
# Calibrated against current `dev` branch behavior.
247345
# Tighten as PR #1 (Phase A) and PR #2 (Phase B) land.
248-
EXPECTED_PRODUCT_TAG_ADD_100 = 4758
249-
EXPECTED_PRODUCT_TAG_REMOVE_100 = 4540
346+
# Some hot paths execute slightly different code under V2 vs V3
347+
# (V3 walks an extra Location queryset; V2 walks an Endpoint queryset).
348+
# Use ``_pin(v2=..., v3=...)`` to select the appropriate baseline.
349+
@staticmethod
350+
def _pin(*, v2: int, v3: int) -> int:
351+
return v3 if settings.V3_FEATURE_LOCATIONS else v2
352+
353+
@property
354+
def EXPECTED_PRODUCT_TAG_ADD_100(self) -> int:
355+
return self._pin(v2=4758, v3=4759)
356+
357+
@property
358+
def EXPECTED_PRODUCT_TAG_REMOVE_100(self) -> int:
359+
return self._pin(v2=4540, v3=4541)
360+
250361
EXPECTED_CREATE_ONE_FINDING = 64
251362
EXPECTED_CREATE_100_FINDINGS = 4025
252363
EXPECTED_FINDING_ADD_USER_TAG = 17
253364
EXPECTED_FINDING_REMOVE_INHERITED = 44
365+
366+
# V2 endpoint paths (only run when V3_FEATURE_LOCATIONS=False)
367+
EXPECTED_PRODUCT_TAG_ADD_100_ENDPOINTS = 3958
368+
EXPECTED_PRODUCT_TAG_REMOVE_100_ENDPOINTS = 3740
369+
370+
# V3 location paths (only run when V3_FEATURE_LOCATIONS=True)
371+
EXPECTED_PRODUCT_TAG_ADD_100_LOCATIONS = 4532
372+
EXPECTED_PRODUCT_TAG_REMOVE_100_LOCATIONS = 4307

0 commit comments

Comments
 (0)