From 1cafea643ebe9d320e7f767af2cabc6c699948a0 Mon Sep 17 00:00:00 2001 From: Evan Purkhiser Date: Tue, 11 Nov 2025 16:44:37 -0500 Subject: [PATCH] ref(detectors): Handle config field updates in base detector validator Previously, the base detector validator update() method did not handle config field updates at all. This adds proper support for updating the config field with JSON schema validation using enforce_config_schema(). Adds tests to verify: - Valid config updates are applied correctly - Invalid config updates that violate the schema are rejected with appropriate error messages --- .../endpoints/validators/base/detector.py | 8 +++ .../test_organization_detector_details.py | 53 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/sentry/workflow_engine/endpoints/validators/base/detector.py b/src/sentry/workflow_engine/endpoints/validators/base/detector.py index fb0b35697c9913..dbf76f670d107e 100644 --- a/src/sentry/workflow_engine/endpoints/validators/base/detector.py +++ b/src/sentry/workflow_engine/endpoints/validators/base/detector.py @@ -133,6 +133,14 @@ def update(self, instance: Detector, validated_data: dict[str, Any]): group_validator = BaseDataConditionGroupValidator() group_validator.update(instance.workflow_condition_group, condition_group) + # Handle config field update + if "config" in validated_data: + instance.config = validated_data.get("config", instance.config) + try: + enforce_config_schema(instance) + except JSONSchemaValidationError as error: + raise serializers.ValidationError({"config": [str(error)]}) + instance.save() create_audit_entry( diff --git a/tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py b/tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py index 5eb67ad81a99f7..3202652746a745 100644 --- a/tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py +++ b/tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py @@ -709,6 +709,59 @@ def test_update_workflows_no_changes(self) -> None: == 0 ) + def test_update_config_valid(self) -> None: + """Test updating detector config with valid schema data""" + # Initial config + initial_config = {"detection_type": "static", "comparison_delta": None} + self.detector.config = initial_config + self.detector.save() + + # Update with valid new config + updated_config = {"detection_type": "dynamic", "comparison_delta": 3600} + data = { + "config": updated_config, + } + + with self.tasks(): + response = self.get_success_response( + self.organization.slug, + self.detector.id, + **data, + status_code=200, + ) + + self.detector.refresh_from_db() + # Verify config was updated in database (snake_case) + assert self.detector.config == updated_config + # API returns camelCase + assert response.data["config"] == { + "detectionType": "dynamic", + "comparisonDelta": 3600, + } + + def test_update_config_invalid_schema(self) -> None: + """Test updating detector config with invalid schema data fails validation""" + # Config missing required field 'detection_type' + invalid_config = {"comparison_delta": 3600} + data = { + "config": invalid_config, + } + + with self.tasks(): + response = self.get_error_response( + self.organization.slug, + self.detector.id, + **data, + status_code=400, + ) + + assert "config" in response.data + assert "detection_type" in str(response.data["config"]) + + # Verify config was not updated + self.detector.refresh_from_db() + assert self.detector.config != invalid_config + @region_silo_test class OrganizationDetectorDetailsDeleteTest(OrganizationDetectorDetailsBaseTest):