diff --git a/datadog_sync/model/synthetics_tests.py b/datadog_sync/model/synthetics_tests.py index 9d8e9c99..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 @@ -33,18 +34,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", ], @@ -68,10 +70,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"] @@ -80,21 +86,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 @@ -106,7 +130,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 @@ -115,16 +147,28 @@ 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) + # 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/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/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..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 @@ -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", "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"}}, "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 bb8cffa4..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 @@ -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", "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"}}, "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 65a7d5f1..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 @@ -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", "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"}}, "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", @@ -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..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 @@ -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", "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"}}, "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", @@ -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 diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/test_synthetics_tests.py b/tests/unit/test_synthetics_tests.py new file mode 100644 index 00000000..5c6a5930 --- /dev/null +++ b/tests/unit/test_synthetics_tests.py @@ -0,0 +1,164 @@ +# 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 asyncio +import copy + +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.""" + # 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" + + 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 = asyncio.run( + 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'" + + 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": [] + } + + asyncio.run( + 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 + + # 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 "root['monitor_id']" in excluded + assert "root['public_id']" in excluded + assert "root['created_at']" in excluded + assert "root['modified_at']" in excluded + + 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} + } + + resource_copy = copy.deepcopy(original_resource) + + asyncio.run( + 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"])