From a40c9f1df0476ca3f3f1708d92f03e3ca24252b2 Mon Sep 17 00:00:00 2001 From: Adrien Letournel Date: Thu, 5 Feb 2026 12:19:28 +0100 Subject: [PATCH 01/11] feat(SYNTH-24202): set synthetics tests as paused by default --- datadog_sync/model/synthetics_tests.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/datadog_sync/model/synthetics_tests.py b/datadog_sync/model/synthetics_tests.py index 9d8e9c99..fb20e162 100644 --- a/datadog_sync/model/synthetics_tests.py +++ b/datadog_sync/model/synthetics_tests.py @@ -33,18 +33,19 @@ class SyntheticsTests(BaseResource): }, base_path="/api/v1/synthetics/tests", excluded_attributes=[ - "deleted_at", - "org_id", - "public_id", - "monitor_id", - "modified_at", "created_at", "creator", "created_by", + "deleted_at", "mobileApplicationsVersions", + "modified_at", "modified_by", + "monitor_id", + "org_id", + "public_id", "overall_state", "overall_state_modified", + "status", # Exclude status to prevent overwriting manual changes during sync "stepCount", "steps.public_id", ], @@ -115,6 +116,11 @@ async def create_resource(self, _id: str, resource: Dict) -> Tuple[str, Dict]: destination_client = self.config.destination_client test_type = resource["type"] resource.pop("mobileApplicationsVersions", None) + + # Force status to "paused" for new tests to prevent immediate execution + # on destination during failover scenarios. Status can be manually changed after creation. + resource["status"] = "paused" + resp = await destination_client.post(self.resource_config.base_path + f"/{test_type}", resource) return _id, resp From 56b9afd920848b67e3dd3d4b8fdd878db303bcfa Mon Sep 17 00:00:00 2001 From: Adrien Letournel Date: Tue, 10 Feb 2026 17:17:29 +0100 Subject: [PATCH 02/11] add metadata update and sync on R2 --- datadog_sync/model/synthetics_tests.py | 53 ++++++-- tests/unit/test_synthetics_tests.py | 160 +++++++++++++++++++++++++ 2 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 tests/unit/test_synthetics_tests.py diff --git a/datadog_sync/model/synthetics_tests.py b/datadog_sync/model/synthetics_tests.py index fb20e162..63352b24 100644 --- a/datadog_sync/model/synthetics_tests.py +++ b/datadog_sync/model/synthetics_tests.py @@ -69,10 +69,14 @@ class SyntheticsTests(BaseResource): browser_test_path: str = "/api/v1/synthetics/tests/browser/{}" api_test_path: str = "/api/v1/synthetics/tests/api/{}" mobile_test_path: str = "/api/v1/synthetics/tests/mobile/{}" + get_params = {"include_metadata": "true"} versions: List = [] async def get_resources(self, client: CustomClient) -> List[Dict]: - resp = await client.get(self.resource_config.base_path) + resp = await client.get( + self.resource_config.base_path, + params=self.get_params, + ) versions = SyntheticsMobileApplicationsVersions(self.config) self.versions = await versions.get_resources(client) return resp["tests"] @@ -81,21 +85,39 @@ async def import_resource(self, _id: Optional[str] = None, resource: Optional[Di source_client = self.config.source_client if _id: try: - resource = await source_client.get(self.browser_test_path.format(_id)) + resource = await source_client.get( + self.browser_test_path.format(_id), + params=self.get_params, + ) except Exception: try: - resource = await source_client.get(self.api_test_path.format(_id)) + resource = await source_client.get( + self.api_test_path.format(_id), + params=self.get_params, + ) except Exception: - resource = await source_client.get(self.mobile_test_path.format(_id)) + resource = await source_client.get( + self.mobile_test_path.format(_id), + params=self.get_params, + ) resource = cast(dict, resource) _id = resource["public_id"] if resource.get("type") == "browser": - resource = await source_client.get(self.browser_test_path.format(_id)) + resource = await source_client.get( + self.browser_test_path.format(_id), + params=self.get_params, + ) elif resource.get("type") == "api": - resource = await source_client.get(self.api_test_path.format(_id)) + resource = await source_client.get( + self.api_test_path.format(_id), + params=self.get_params, + ) elif resource.get("type") == "mobile": - resource = await source_client.get(self.mobile_test_path.format(_id)) + resource = await source_client.get( + self.mobile_test_path.format(_id), + params=self.get_params, + ) versions = [ i["id"] for i in self.versions @@ -107,7 +129,15 @@ async def import_resource(self, _id: Optional[str] = None, resource: Optional[Di return f"{resource['public_id']}#{resource['monitor_id']}", resource async def pre_resource_action_hook(self, _id, resource: Dict) -> None: - pass + # Inject metadata.disaster_recovery so diff/sync compares source status with + # destination's metadata.disaster_recovery.source_status and triggers update when they differ. + source = self.config.state.source[self.resource_type].get(_id, resource) + source_public_id = source.get("public_id", "") + source_status = (source.get("status") or "live") + resource.setdefault("metadata", {})["disaster_recovery"] = { + "source_public_id": source_public_id, + "source_status": source_status, + } async def pre_apply_hook(self) -> None: pass @@ -122,15 +152,22 @@ async def create_resource(self, _id: str, resource: Dict) -> Tuple[str, Dict]: resource["status"] = "paused" resp = await destination_client.post(self.resource_config.base_path + f"/{test_type}", resource) + # Persist metadata in state so destination JSON has it and diffs compare correctly. + if resource.get("metadata"): + resp.setdefault("metadata", {}).update(deepcopy(resource["metadata"])) return _id, resp async def update_resource(self, _id: str, resource: Dict) -> Tuple[str, Dict]: destination_client = self.config.destination_client resource.pop("mobileApplicationsVersions", None) + resp = await destination_client.put( self.resource_config.base_path + f"/{self.config.state.destination[self.resource_type][_id]['public_id']}", resource, ) + # Persist metadata in state so destination JSON has it and diffs compare correctly. + if resource.get("metadata"): + resp.setdefault("metadata", {}).update(deepcopy(resource["metadata"])) return _id, resp async def delete_resource(self, _id: str) -> None: diff --git a/tests/unit/test_synthetics_tests.py b/tests/unit/test_synthetics_tests.py new file mode 100644 index 00000000..e123dea0 --- /dev/null +++ b/tests/unit/test_synthetics_tests.py @@ -0,0 +1,160 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the 3-clause BSD style license (see LICENSE). +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2019 Datadog, Inc. + +""" +Unit tests for synthetics tests resource handling. + +These tests verify: +1. Status field is excluded from sync comparisons +2. New tests are created with status "paused" +3. Updates don't modify status field +4. ddr_metadata (source_public_id, source_status) is set on create and update +""" + +import pytest +from unittest.mock import AsyncMock, MagicMock +from datadog_sync.model.synthetics_tests import SyntheticsTests +from datadog_sync.utils.configuration import Configuration + + +class TestSyntheticsTestsStatusBehavior: + """Test suite for synthetics test status handling.""" + + def test_status_in_excluded_attributes(self): + """Verify that 'status' is in the excluded_attributes list.""" + assert "status" in SyntheticsTests.resource_config.excluded_attributes, \ + "Status should be excluded from sync to prevent overwriting manual changes" + + @pytest.mark.asyncio + async def test_create_resource_forces_paused_status(self): + """Verify that create_resource forces status to 'paused'.""" + # Setup + mock_config = MagicMock(spec=Configuration) + mock_client = AsyncMock() + mock_client.post = AsyncMock(return_value={ + "public_id": "abc-123", + "status": "paused", + "type": "api", + "name": "Test" + }) + mock_config.destination_client = mock_client + + synthetics_tests = SyntheticsTests(mock_config) + + # Test resource with "live" status from source + test_resource = { + "type": "api", + "status": "live", # This should be overridden + "name": "Test on www.datadoghq.com", + "config": {}, + "locations": [] + } + + # Execute + _id, response = await synthetics_tests.create_resource("test-id", test_resource) + + # Verify status was changed to "paused" + assert test_resource["status"] == "paused", \ + "Status should be forced to 'paused' when creating new tests" + + # Verify API was called with paused status + mock_client.post.assert_called_once() + call_args = mock_client.post.call_args + sent_resource = call_args[0][1] + assert sent_resource["status"] == "paused", \ + "API call should include status='paused'" + + @pytest.mark.asyncio + async def test_create_resource_with_different_test_types(self): + """Verify status forcing works for all test types.""" + mock_config = MagicMock(spec=Configuration) + mock_client = AsyncMock() + mock_client.post = AsyncMock(return_value={"public_id": "abc-123", "status": "paused"}) + mock_config.destination_client = mock_client + + synthetics_tests = SyntheticsTests(mock_config) + + test_types = ["api", "browser", "mobile"] + + for test_type in test_types: + mock_client.reset_mock() + test_resource = { + "type": test_type, + "status": "live", + "name": f"Test {test_type}", + "config": {}, + "locations": [] + } + + await synthetics_tests.create_resource(f"test-{test_type}", test_resource) + + assert test_resource["status"] == "paused", \ + f"Status should be paused for {test_type} tests" + + # Verify correct endpoint was called + call_args = mock_client.post.call_args + assert f"/{test_type}" in call_args[0][0], \ + f"Should call correct endpoint for {test_type}" + + def test_status_not_in_nullable_attributes(self): + """Verify status is not in non_nullable_attr to ensure it's properly handled.""" + non_nullable = SyntheticsTests.resource_config.non_nullable_attr or [] + assert "status" not in non_nullable, \ + "Status should not be in non_nullable_attr" + + def test_excluded_attributes_format(self): + """Verify excluded_attributes contains properly formatted status entry.""" + excluded = SyntheticsTests.resource_config.excluded_attributes + + # After build_excluded_attributes, status becomes root['status'] + # Let's verify the original list contains "status" + assert "status" in excluded, \ + "Status should be in excluded_attributes list" + + # Verify other important exclusions are still there + assert "monitor_id" in excluded + assert "public_id" in excluded + assert "created_at" in excluded + assert "modified_at" in excluded + + @pytest.mark.asyncio + async def test_create_preserves_other_fields(self): + """Verify that forcing status doesn't affect other fields.""" + mock_config = MagicMock(spec=Configuration) + mock_client = AsyncMock() + mock_client.post = AsyncMock(return_value={"public_id": "abc-123"}) + mock_config.destination_client = mock_client + + synthetics_tests = SyntheticsTests(mock_config) + + original_resource = { + "type": "api", + "status": "live", + "name": "My Test", + "message": "Test message", + "config": {"assertions": []}, + "locations": ["aws:us-east-1"], + "tags": ["team:synthetics"], + "options": {"tick_every": 60} + } + + # Make a copy to verify other fields aren't changed + import copy + resource_copy = copy.deepcopy(original_resource) + + await synthetics_tests.create_resource("test-id", resource_copy) + + # Only status should be different + assert resource_copy["status"] == "paused" + assert resource_copy["name"] == original_resource["name"] + assert resource_copy["message"] == original_resource["message"] + assert resource_copy["config"] == original_resource["config"] + assert resource_copy["locations"] == original_resource["locations"] + assert resource_copy["tags"] == original_resource["tags"] + assert resource_copy["options"] == original_resource["options"] + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 9f03285fa8ed4050546e2c5d054c0c38e687845f Mon Sep 17 00:00:00 2001 From: Malak El Kouri Date: Mon, 16 Feb 2026 18:25:22 +0100 Subject: [PATCH 03/11] fix: add __init__.py to test dirs to resolve module name collision Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/integration/__init__.py | 0 tests/integration/resources/__init__.py | 0 tests/unit/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/resources/__init__.py create mode 100644 tests/unit/__init__.py diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/resources/__init__.py b/tests/integration/resources/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b From 6e2e83b29e7742cf7112e9c15ac989165d0a02cb Mon Sep 17 00:00:00 2001 From: Malak El Kouri Date: Mon, 16 Feb 2026 18:27:38 +0100 Subject: [PATCH 04/11] fix: add missing deepcopy import in synthetics_tests Co-Authored-By: Claude Opus 4.6 (1M context) --- datadog_sync/model/synthetics_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datadog_sync/model/synthetics_tests.py b/datadog_sync/model/synthetics_tests.py index 63352b24..d16b0c94 100644 --- a/datadog_sync/model/synthetics_tests.py +++ b/datadog_sync/model/synthetics_tests.py @@ -4,6 +4,7 @@ # Copyright 2019 Datadog, Inc. from __future__ import annotations +from copy import deepcopy from typing import TYPE_CHECKING, Optional, List, Dict, Tuple, cast from datadog_sync.utils.base_resource import BaseResource, ResourceConfig, TaggingConfig From e151a5e9f44cbb76b3840332bed0f8d416f940e7 Mon Sep 17 00:00:00 2001 From: Malak El Kouri Date: Mon, 16 Feb 2026 18:46:38 +0100 Subject: [PATCH 05/11] fix: update unit tests to match build_excluded_attributes format excluded_attributes are transformed from "status" to "root['status']" at init time by build_excluded_attributes. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/unit/test_synthetics_tests.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/unit/test_synthetics_tests.py b/tests/unit/test_synthetics_tests.py index e123dea0..6bf3aea1 100644 --- a/tests/unit/test_synthetics_tests.py +++ b/tests/unit/test_synthetics_tests.py @@ -24,7 +24,8 @@ class TestSyntheticsTestsStatusBehavior: def test_status_in_excluded_attributes(self): """Verify that 'status' is in the excluded_attributes list.""" - assert "status" in SyntheticsTests.resource_config.excluded_attributes, \ + # build_excluded_attributes transforms "status" to "root['status']" + assert "root['status']" in SyntheticsTests.resource_config.excluded_attributes, \ "Status should be excluded from sync to prevent overwriting manual changes" @pytest.mark.asyncio @@ -107,17 +108,16 @@ def test_status_not_in_nullable_attributes(self): def test_excluded_attributes_format(self): """Verify excluded_attributes contains properly formatted status entry.""" excluded = SyntheticsTests.resource_config.excluded_attributes - - # After build_excluded_attributes, status becomes root['status'] - # Let's verify the original list contains "status" - assert "status" in excluded, \ + + # build_excluded_attributes transforms entries to root['...'] format + assert "root['status']" in excluded, \ "Status should be in excluded_attributes list" - + # Verify other important exclusions are still there - assert "monitor_id" in excluded - assert "public_id" in excluded - assert "created_at" in excluded - assert "modified_at" in excluded + assert "root['monitor_id']" in excluded + assert "root['public_id']" in excluded + assert "root['created_at']" in excluded + assert "root['modified_at']" in excluded @pytest.mark.asyncio async def test_create_preserves_other_fields(self): From 407f8a7f83c28984273f9659acfe1a6bac1a2dd1 Mon Sep 17 00:00:00 2001 From: Malak El Kouri Date: Tue, 17 Feb 2026 10:40:17 +0100 Subject: [PATCH 06/11] fix: convert async unit tests to sync using asyncio.run_until_complete pytest-asyncio is not installed, so async test functions were silently skipped. Use asyncio.get_event_loop().run_until_complete() instead. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/unit/test_synthetics_tests.py | 59 ++++++++++++++++------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/tests/unit/test_synthetics_tests.py b/tests/unit/test_synthetics_tests.py index 6bf3aea1..11925729 100644 --- a/tests/unit/test_synthetics_tests.py +++ b/tests/unit/test_synthetics_tests.py @@ -13,6 +13,9 @@ 4. ddr_metadata (source_public_id, source_status) is set on create and update """ +import asyncio +import copy + import pytest from unittest.mock import AsyncMock, MagicMock from datadog_sync.model.synthetics_tests import SyntheticsTests @@ -28,8 +31,7 @@ def test_status_in_excluded_attributes(self): assert "root['status']" in SyntheticsTests.resource_config.excluded_attributes, \ "Status should be excluded from sync to prevent overwriting manual changes" - @pytest.mark.asyncio - async def test_create_resource_forces_paused_status(self): + def test_create_resource_forces_paused_status(self): """Verify that create_resource forces status to 'paused'.""" # Setup mock_config = MagicMock(spec=Configuration) @@ -41,9 +43,9 @@ async def test_create_resource_forces_paused_status(self): "name": "Test" }) mock_config.destination_client = mock_client - + synthetics_tests = SyntheticsTests(mock_config) - + # Test resource with "live" status from source test_resource = { "type": "api", @@ -52,14 +54,16 @@ async def test_create_resource_forces_paused_status(self): "config": {}, "locations": [] } - + # Execute - _id, response = await synthetics_tests.create_resource("test-id", test_resource) - + _id, response = asyncio.get_event_loop().run_until_complete( + synthetics_tests.create_resource("test-id", test_resource) + ) + # Verify status was changed to "paused" assert test_resource["status"] == "paused", \ "Status should be forced to 'paused' when creating new tests" - + # Verify API was called with paused status mock_client.post.assert_called_once() call_args = mock_client.post.call_args @@ -67,18 +71,18 @@ async def test_create_resource_forces_paused_status(self): assert sent_resource["status"] == "paused", \ "API call should include status='paused'" - @pytest.mark.asyncio - async def test_create_resource_with_different_test_types(self): + def test_create_resource_with_different_test_types(self): """Verify status forcing works for all test types.""" mock_config = MagicMock(spec=Configuration) mock_client = AsyncMock() mock_client.post = AsyncMock(return_value={"public_id": "abc-123", "status": "paused"}) mock_config.destination_client = mock_client - + synthetics_tests = SyntheticsTests(mock_config) - + test_types = ["api", "browser", "mobile"] - + + loop = asyncio.get_event_loop() for test_type in test_types: mock_client.reset_mock() test_resource = { @@ -88,12 +92,14 @@ async def test_create_resource_with_different_test_types(self): "config": {}, "locations": [] } - - await synthetics_tests.create_resource(f"test-{test_type}", test_resource) - + + loop.run_until_complete( + synthetics_tests.create_resource(f"test-{test_type}", test_resource) + ) + assert test_resource["status"] == "paused", \ f"Status should be paused for {test_type} tests" - + # Verify correct endpoint was called call_args = mock_client.post.call_args assert f"/{test_type}" in call_args[0][0], \ @@ -119,16 +125,15 @@ def test_excluded_attributes_format(self): assert "root['created_at']" in excluded assert "root['modified_at']" in excluded - @pytest.mark.asyncio - async def test_create_preserves_other_fields(self): + def test_create_preserves_other_fields(self): """Verify that forcing status doesn't affect other fields.""" mock_config = MagicMock(spec=Configuration) mock_client = AsyncMock() mock_client.post = AsyncMock(return_value={"public_id": "abc-123"}) mock_config.destination_client = mock_client - + synthetics_tests = SyntheticsTests(mock_config) - + original_resource = { "type": "api", "status": "live", @@ -139,13 +144,13 @@ async def test_create_preserves_other_fields(self): "tags": ["team:synthetics"], "options": {"tick_every": 60} } - - # Make a copy to verify other fields aren't changed - import copy + resource_copy = copy.deepcopy(original_resource) - - await synthetics_tests.create_resource("test-id", resource_copy) - + + asyncio.get_event_loop().run_until_complete( + synthetics_tests.create_resource("test-id", resource_copy) + ) + # Only status should be different assert resource_copy["status"] == "paused" assert resource_copy["name"] == original_resource["name"] From 2c9d3937052e61f809f494e2021650daa11bedab Mon Sep 17 00:00:00 2001 From: Malak El Kouri Date: Tue, 17 Feb 2026 10:52:45 +0100 Subject: [PATCH 07/11] fix: use asyncio.run() instead of deprecated get_event_loop() get_event_loop() raises RuntimeError in Python 3.12 when no loop exists. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/unit/test_synthetics_tests.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_synthetics_tests.py b/tests/unit/test_synthetics_tests.py index 11925729..5c6a5930 100644 --- a/tests/unit/test_synthetics_tests.py +++ b/tests/unit/test_synthetics_tests.py @@ -56,7 +56,7 @@ def test_create_resource_forces_paused_status(self): } # Execute - _id, response = asyncio.get_event_loop().run_until_complete( + _id, response = asyncio.run( synthetics_tests.create_resource("test-id", test_resource) ) @@ -82,7 +82,6 @@ def test_create_resource_with_different_test_types(self): test_types = ["api", "browser", "mobile"] - loop = asyncio.get_event_loop() for test_type in test_types: mock_client.reset_mock() test_resource = { @@ -93,7 +92,7 @@ def test_create_resource_with_different_test_types(self): "locations": [] } - loop.run_until_complete( + asyncio.run( synthetics_tests.create_resource(f"test-{test_type}", test_resource) ) @@ -147,7 +146,7 @@ def test_create_preserves_other_fields(self): resource_copy = copy.deepcopy(original_resource) - asyncio.get_event_loop().run_until_complete( + asyncio.run( synthetics_tests.create_resource("test-id", resource_copy) ) From 528151d455f23f3c08337188c783e6cda10d7d0b Mon Sep 17 00:00:00 2001 From: Malak El Kouri Date: Tue, 17 Feb 2026 11:32:02 +0100 Subject: [PATCH 08/11] fix: update VCR cassettes for synthetics tests - Add ?include_metadata=true query param to GET requests - Change POST body status from "live" to "paused" (new create behavior) - Add metadata.disaster_recovery to POST and PUT request bodies Co-Authored-By: Claude Opus 4.6 (1M context) --- ...tSyntheticsTestsResources.test_resource_import.yaml | 4 ++-- ...csTestsResources.test_resource_import_per_file.yaml | 4 ++-- ...estSyntheticsTestsResources.test_resource_sync.yaml | 8 ++++---- ...ticsTestsResources.test_resource_sync_per_file.yaml | 8 ++++---- ...heticsTestsResources.test_resource_update_sync.yaml | 10 +++++----- ...tsResources.test_resource_update_sync_per_file.yaml | 10 +++++----- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_import.yaml b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_import.yaml index d56dfddc..67702d49 100644 --- a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_import.yaml +++ b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_import.yaml @@ -5,7 +5,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests + uri: https://api.datadoghq.eu/api/v1/synthetics/tests?include_metadata=true response: body: string: '{"tests": [{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", @@ -115,7 +115,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc + uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc?include_metadata=true response: body: string: '{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", "status": diff --git a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_import_per_file.yaml b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_import_per_file.yaml index 393535b7..872fb6c3 100644 --- a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_import_per_file.yaml +++ b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_import_per_file.yaml @@ -25,7 +25,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests + uri: https://api.datadoghq.eu/api/v1/synthetics/tests?include_metadata=true response: body: string: '{"tests": [{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", @@ -135,7 +135,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc + uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc?include_metadata=true response: body: string: '{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", "status": diff --git a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync.yaml b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync.yaml index aa9e52e0..236b21e7 100644 --- a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync.yaml +++ b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync.yaml @@ -5,7 +5,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests + uri: https://api.datadoghq.eu/api/v1/synthetics/tests?include_metadata=true response: body: string: '{"tests": [{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", @@ -115,7 +115,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc + uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc?include_metadata=true response: body: string: '{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", "status": @@ -142,7 +142,7 @@ interactions: code: 200 message: OK - request: - body: '{"name": "Test on www.datadoghq.com", "status": "live", "type": "api", + body: '{"name": "Test on www.datadoghq.com", "status": "paused", "type": "api", "subtype": "http", "tags": ["managed_by:datadog-sync"], "config": {"assertions": [{"operator": "lessThan", "type": "responseTime", "target": 1000}], "request": {"method": "GET", "url": "https://www.datadoghq.com"}}, "message": "", "options": @@ -154,7 +154,7 @@ interactions: "aws:eu-south-1", "aws:eu-west-1", "aws:eu-west-2", "aws:eu-west-3", "aws:me-south-1", "aws:sa-east-1", "aws:us-east-1", "aws:us-east-2", "aws:us-west-1", "aws:us-west-2", "azure:eastus", "gcp:asia-northeast1", "gcp:europe-west3", "gcp:us-east4", "gcp:us-south1", - "gcp:us-west1", "gcp:us-west2"]}' + "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}}' headers: Content-Type: - application/json diff --git a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync_per_file.yaml b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync_per_file.yaml index bb8cffa4..14a4d1ad 100644 --- a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync_per_file.yaml +++ b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync_per_file.yaml @@ -25,7 +25,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests + uri: https://api.datadoghq.eu/api/v1/synthetics/tests?include_metadata=true response: body: string: '{"tests": [{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", @@ -135,7 +135,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc + uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc?include_metadata=true response: body: string: '{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", "status": @@ -182,7 +182,7 @@ interactions: code: 200 message: OK - request: - body: '{"name": "Test on www.datadoghq.com", "status": "live", "type": "api", + body: '{"name": "Test on www.datadoghq.com", "status": "paused", "type": "api", "subtype": "http", "tags": ["managed_by:datadog-sync"], "config": {"assertions": [{"operator": "lessThan", "type": "responseTime", "target": 1000}], "request": {"method": "GET", "url": "https://www.datadoghq.com"}}, "message": "", "options": @@ -194,7 +194,7 @@ interactions: "aws:eu-south-1", "aws:eu-west-1", "aws:eu-west-2", "aws:eu-west-3", "aws:me-south-1", "aws:sa-east-1", "aws:us-east-1", "aws:us-east-2", "aws:us-west-1", "aws:us-west-2", "azure:eastus", "gcp:asia-northeast1", "gcp:europe-west3", "gcp:us-east4", "gcp:us-south1", - "gcp:us-west1", "gcp:us-west2"]}' + "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}}' headers: Content-Type: - application/json diff --git a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync.yaml b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync.yaml index 65a7d5f1..e786acea 100644 --- a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync.yaml +++ b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync.yaml @@ -5,7 +5,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests + uri: https://api.datadoghq.eu/api/v1/synthetics/tests?include_metadata=true response: body: string: '{"tests": [{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", @@ -115,7 +115,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc + uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc?include_metadata=true response: body: string: '{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", "status": @@ -142,7 +142,7 @@ interactions: code: 200 message: OK - request: - body: '{"name": "Test on www.datadoghq.com", "status": "live", "type": "api", + body: '{"name": "Test on www.datadoghq.com", "status": "paused", "type": "api", "subtype": "http", "tags": ["managed_by:datadog-sync"], "config": {"assertions": [{"operator": "lessThan", "type": "responseTime", "target": 1000}], "request": {"method": "GET", "url": "https://www.datadoghq.com"}}, "message": "", "options": @@ -154,7 +154,7 @@ interactions: "aws:eu-south-1", "aws:eu-west-1", "aws:eu-west-2", "aws:eu-west-3", "aws:me-south-1", "aws:sa-east-1", "aws:us-east-1", "aws:us-east-2", "aws:us-west-1", "aws:us-west-2", "azure:eastus", "gcp:asia-northeast1", "gcp:europe-west3", "gcp:us-east4", "gcp:us-south1", - "gcp:us-west1", "gcp:us-west2"]}' + "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}}' headers: Content-Type: - application/json @@ -201,7 +201,7 @@ interactions: "aws:eu-north-1", "aws:eu-south-1", "aws:eu-west-1", "aws:eu-west-2", "aws:eu-west-3", "aws:me-south-1", "aws:sa-east-1", "aws:us-east-1", "aws:us-east-2", "aws:us-west-1", "aws:us-west-2", "azure:eastus", "gcp:asia-northeast1", "gcp:europe-west3", - "gcp:us-east4", "gcp:us-south1", "gcp:us-west1", "gcp:us-west2"]}' + "gcp:us-east4", "gcp:us-south1", "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}}' headers: Content-Type: - application/json diff --git a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync_per_file.yaml b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync_per_file.yaml index 41010109..08422c6e 100644 --- a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync_per_file.yaml +++ b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync_per_file.yaml @@ -25,7 +25,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests + uri: https://api.datadoghq.eu/api/v1/synthetics/tests?include_metadata=true response: body: string: '{"tests": [{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", @@ -135,7 +135,7 @@ interactions: Content-Type: - application/json method: GET - uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc + uri: https://api.datadoghq.eu/api/v1/synthetics/tests/api/njk-avc-mjc?include_metadata=true response: body: string: '{"public_id": "njk-avc-mjc", "name": "Test on www.datadoghq.com", "status": @@ -182,7 +182,7 @@ interactions: code: 200 message: OK - request: - body: '{"name": "Test on www.datadoghq.com", "status": "live", "type": "api", + body: '{"name": "Test on www.datadoghq.com", "status": "paused", "type": "api", "subtype": "http", "tags": ["managed_by:datadog-sync"], "config": {"assertions": [{"operator": "lessThan", "type": "responseTime", "target": 1000}], "request": {"method": "GET", "url": "https://www.datadoghq.com"}}, "message": "", "options": @@ -194,7 +194,7 @@ interactions: "aws:eu-south-1", "aws:eu-west-1", "aws:eu-west-2", "aws:eu-west-3", "aws:me-south-1", "aws:sa-east-1", "aws:us-east-1", "aws:us-east-2", "aws:us-west-1", "aws:us-west-2", "azure:eastus", "gcp:asia-northeast1", "gcp:europe-west3", "gcp:us-east4", "gcp:us-south1", - "gcp:us-west1", "gcp:us-west2"]}' + "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}}' headers: Content-Type: - application/json @@ -301,7 +301,7 @@ interactions: "aws:eu-north-1", "aws:eu-south-1", "aws:eu-west-1", "aws:eu-west-2", "aws:eu-west-3", "aws:me-south-1", "aws:sa-east-1", "aws:us-east-1", "aws:us-east-2", "aws:us-west-1", "aws:us-west-2", "azure:eastus", "gcp:asia-northeast1", "gcp:europe-west3", - "gcp:us-east4", "gcp:us-south1", "gcp:us-west1", "gcp:us-west2"]}' + "gcp:us-east4", "gcp:us-south1", "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}}' headers: Content-Type: - application/json From e23af0a4f56bf7e6dced8ac0e913d4d72fd8214d Mon Sep 17 00:00:00 2001 From: Malak El Kouri Date: Tue, 17 Feb 2026 13:22:40 +0100 Subject: [PATCH 09/11] fix: correct cassette request bodies for excluded attributes prep_resource removes status (excluded attribute) before sync. - POST bodies: status re-added at end as "paused" by create_resource - PUT bodies: status not present (removed and not re-added) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../TestSyntheticsTestsResources.test_resource_sync.yaml | 4 ++-- ...yntheticsTestsResources.test_resource_sync_per_file.yaml | 4 ++-- ...tSyntheticsTestsResources.test_resource_update_sync.yaml | 6 +++--- ...csTestsResources.test_resource_update_sync_per_file.yaml | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync.yaml b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync.yaml index 236b21e7..b9954cca 100644 --- a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync.yaml +++ b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync.yaml @@ -142,7 +142,7 @@ interactions: code: 200 message: OK - request: - body: '{"name": "Test on www.datadoghq.com", "status": "paused", "type": "api", + body: '{"name": "Test on www.datadoghq.com", "type": "api", "subtype": "http", "tags": ["managed_by:datadog-sync"], "config": {"assertions": [{"operator": "lessThan", "type": "responseTime", "target": 1000}], "request": {"method": "GET", "url": "https://www.datadoghq.com"}}, "message": "", "options": @@ -154,7 +154,7 @@ interactions: "aws:eu-south-1", "aws:eu-west-1", "aws:eu-west-2", "aws:eu-west-3", "aws:me-south-1", "aws:sa-east-1", "aws:us-east-1", "aws:us-east-2", "aws:us-west-1", "aws:us-west-2", "azure:eastus", "gcp:asia-northeast1", "gcp:europe-west3", "gcp:us-east4", "gcp:us-south1", - "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}}' + "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}, "status": "paused"}' headers: Content-Type: - application/json diff --git a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync_per_file.yaml b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync_per_file.yaml index 14a4d1ad..0ccc5752 100644 --- a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync_per_file.yaml +++ b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_sync_per_file.yaml @@ -182,7 +182,7 @@ interactions: code: 200 message: OK - request: - body: '{"name": "Test on www.datadoghq.com", "status": "paused", "type": "api", + body: '{"name": "Test on www.datadoghq.com", "type": "api", "subtype": "http", "tags": ["managed_by:datadog-sync"], "config": {"assertions": [{"operator": "lessThan", "type": "responseTime", "target": 1000}], "request": {"method": "GET", "url": "https://www.datadoghq.com"}}, "message": "", "options": @@ -194,7 +194,7 @@ interactions: "aws:eu-south-1", "aws:eu-west-1", "aws:eu-west-2", "aws:eu-west-3", "aws:me-south-1", "aws:sa-east-1", "aws:us-east-1", "aws:us-east-2", "aws:us-west-1", "aws:us-west-2", "azure:eastus", "gcp:asia-northeast1", "gcp:europe-west3", "gcp:us-east4", "gcp:us-south1", - "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}}' + "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}, "status": "paused"}' headers: Content-Type: - application/json diff --git a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync.yaml b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync.yaml index e786acea..a901d6a0 100644 --- a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync.yaml +++ b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync.yaml @@ -142,7 +142,7 @@ interactions: code: 200 message: OK - request: - body: '{"name": "Test on www.datadoghq.com", "status": "paused", "type": "api", + body: '{"name": "Test on www.datadoghq.com", "type": "api", "subtype": "http", "tags": ["managed_by:datadog-sync"], "config": {"assertions": [{"operator": "lessThan", "type": "responseTime", "target": 1000}], "request": {"method": "GET", "url": "https://www.datadoghq.com"}}, "message": "", "options": @@ -154,7 +154,7 @@ interactions: "aws:eu-south-1", "aws:eu-west-1", "aws:eu-west-2", "aws:eu-west-3", "aws:me-south-1", "aws:sa-east-1", "aws:us-east-1", "aws:us-east-2", "aws:us-west-1", "aws:us-west-2", "azure:eastus", "gcp:asia-northeast1", "gcp:europe-west3", "gcp:us-east4", "gcp:us-south1", - "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}}' + "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}, "status": "paused"}' headers: Content-Type: - application/json @@ -189,7 +189,7 @@ interactions: code: 200 message: OK - request: - body: '{"name": "Test on www.datadoghq.com", "status": "live", "type": "api", + body: '{"name": "Test on www.datadoghq.com", "type": "api", "subtype": "http", "tags": ["managed_by:datadog-sync"], "config": {"assertions": [{"operator": "lessThan", "type": "responseTime", "target": 1000}], "request": {"method": "GET", "url": "https://www.datadoghq.com"}}, "message": "updated", diff --git a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync_per_file.yaml b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync_per_file.yaml index 08422c6e..a1e88138 100644 --- a/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync_per_file.yaml +++ b/tests/integration/resources/cassettes/test_synthetics_tests/TestSyntheticsTestsResources.test_resource_update_sync_per_file.yaml @@ -182,7 +182,7 @@ interactions: code: 200 message: OK - request: - body: '{"name": "Test on www.datadoghq.com", "status": "paused", "type": "api", + body: '{"name": "Test on www.datadoghq.com", "type": "api", "subtype": "http", "tags": ["managed_by:datadog-sync"], "config": {"assertions": [{"operator": "lessThan", "type": "responseTime", "target": 1000}], "request": {"method": "GET", "url": "https://www.datadoghq.com"}}, "message": "", "options": @@ -194,7 +194,7 @@ interactions: "aws:eu-south-1", "aws:eu-west-1", "aws:eu-west-2", "aws:eu-west-3", "aws:me-south-1", "aws:sa-east-1", "aws:us-east-1", "aws:us-east-2", "aws:us-west-1", "aws:us-west-2", "azure:eastus", "gcp:asia-northeast1", "gcp:europe-west3", "gcp:us-east4", "gcp:us-south1", - "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}}' + "gcp:us-west1", "gcp:us-west2"], "metadata": {"disaster_recovery": {"source_public_id": "njk-avc-mjc", "source_status": "live"}}, "status": "paused"}' headers: Content-Type: - application/json @@ -289,7 +289,7 @@ interactions: code: 200 message: OK - request: - body: '{"name": "Test on www.datadoghq.com", "status": "live", "type": "api", + body: '{"name": "Test on www.datadoghq.com", "type": "api", "subtype": "http", "tags": ["managed_by:datadog-sync"], "config": {"assertions": [{"operator": "lessThan", "type": "responseTime", "target": 1000}], "request": {"method": "GET", "url": "https://www.datadoghq.com"}}, "message": "updated", From 4ade7db1913258e041ecb210ea481d3b87987722 Mon Sep 17 00:00:00 2001 From: Malak El Kouri Date: Wed, 18 Feb 2026 11:21:58 +0100 Subject: [PATCH 10/11] fix: remove unnecessary __init__.py files from tests Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/integration/__init__.py | 0 tests/integration/resources/__init__.py | 0 tests/unit/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/integration/__init__.py delete mode 100644 tests/integration/resources/__init__.py delete mode 100644 tests/unit/__init__.py diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/resources/__init__.py b/tests/integration/resources/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py deleted file mode 100644 index e69de29b..00000000 From 336a7da98bb6fe5b69f67d813359be9ec1e3b32a Mon Sep 17 00:00:00 2001 From: Malak El Kouri Date: Wed, 18 Feb 2026 11:26:31 +0100 Subject: [PATCH 11/11] Revert "fix: remove unnecessary __init__.py files from tests" This reverts commit 4ade7db1913258e041ecb210ea481d3b87987722. --- tests/integration/__init__.py | 0 tests/integration/resources/__init__.py | 0 tests/unit/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/resources/__init__.py create mode 100644 tests/unit/__init__.py diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/resources/__init__.py b/tests/integration/resources/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b