Skip to content

Commit 59939ef

Browse files
authored
[Core] Added metrics to the unit tests (#1688)
# Description What - added metrics to the tests Why - want to test the metrics as well How - removed the mocks ## Type of change Please leave one option from the following and delete the rest: - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] New Integration (non-breaking change which adds a new integration) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Non-breaking change (fix of existing functionality that will not change current behavior) - [ ] Documentation (added/updated documentation) <h4> All tests should be run against the port production environment(using a testing org). </h4> ### Core testing checklist - [ ] Integration able to create all default resources from scratch - [ ] Resync finishes successfully - [ ] Resync able to create entities - [ ] Resync able to update entities - [ ] Resync able to detect and delete entities - [ ] Scheduled resync able to abort existing resync and start a new one - [ ] Tested with at least 2 integrations from scratch - [ ] Tested with Kafka and Polling event listeners - [ ] Tested deletion of entities that don't pass the selector ### Integration testing checklist - [ ] Integration able to create all default resources from scratch - [ ] Resync able to create entities - [ ] Resync able to update entities - [ ] Resync able to detect and delete entities - [ ] Resync finishes successfully - [ ] If new resource kind is added or updated in the integration, add example raw data, mapping and expected result to the `examples` folder in the integration directory. - [ ] If resource kind is updated, run the integration with the example data and check if the expected result is achieved - [ ] If new resource kind is added or updated, validate that live-events for that resource are working as expected - [ ] Docs PR link [here](#) ### Preflight checklist - [ ] Handled rate limiting - [ ] Handled pagination - [ ] Implemented the code in async - [ ] Support Multi account ## Screenshots Include screenshots from your environment showing how the resources of the integration will look. ## API Documentation Provide links to the API documentation used for this integration.
1 parent dc3fc66 commit 59939ef

File tree

1 file changed

+209
-20
lines changed

1 file changed

+209
-20
lines changed

port_ocean/tests/core/handlers/mixins/test_sync_raw.py

Lines changed: 209 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from graphlib import CycleError
2-
from typing import Any
2+
from typing import Any, AsyncGenerator
33

44
from port_ocean.clients.port.client import PortClient
55
from port_ocean.core.utils.entity_topological_sorter import EntityTopologicalSorter
@@ -25,6 +25,8 @@
2525
from dataclasses import dataclass
2626
from typing import List, Optional
2727
from port_ocean.tests.core.conftest import create_entity, no_op_event_context
28+
from port_ocean.helpers.metric.metric import Metrics
29+
from port_ocean.config.settings import MetricsSettings, IntegrationSettings
2830

2931

3032
@pytest.fixture
@@ -37,7 +39,47 @@ def mock_sync_raw_mixin(
3739
sync_raw_mixin._entity_processor = mock_entity_processor
3840
sync_raw_mixin._entities_state_applier = mock_entities_state_applier
3941
sync_raw_mixin._port_app_config_handler = mock_port_app_config_handler
40-
sync_raw_mixin._get_resource_raw_results = AsyncMock(return_value=([{}], [])) # type: ignore
42+
43+
# Create raw data that matches the entity structure
44+
raw_data = [
45+
{
46+
"id": "entity_1",
47+
"name": "Entity 1",
48+
"service": "entity_3",
49+
"web_url": "https://example.com/entity1",
50+
},
51+
{
52+
"id": "entity_2",
53+
"name": "Entity 2",
54+
"service": "entity_4",
55+
"web_url": "https://example.com/entity2",
56+
},
57+
{
58+
"id": "entity_3",
59+
"name": "Entity 3",
60+
"service": "",
61+
"web_url": "https://example.com/entity3",
62+
},
63+
{
64+
"id": "entity_4",
65+
"name": "Entity 4",
66+
"service": "entity_3",
67+
"web_url": "https://example.com/entity4",
68+
},
69+
{
70+
"id": "entity_5",
71+
"name": "Entity 5",
72+
"service": "entity_1",
73+
"web_url": "https://example.com/entity5",
74+
},
75+
]
76+
77+
# Create an async generator that yields the raw data
78+
async def raw_results_generator() -> AsyncGenerator[list[dict[str, Any]], None]:
79+
yield raw_data
80+
81+
# Return a list containing the async generator and an empty error list
82+
sync_raw_mixin._get_resource_raw_results = AsyncMock(return_value=([raw_results_generator()], [])) # type: ignore
4183
sync_raw_mixin._entity_processor.parse_items = AsyncMock(return_value=MagicMock()) # type: ignore
4284

4385
return sync_raw_mixin
@@ -62,9 +104,15 @@ def mock_ocean(mock_port_client: PortClient) -> Ocean:
62104
ocean_mock.config.port = MagicMock()
63105
ocean_mock.config.port.port_app_config_cache_ttl = 60
64106
ocean_mock.port_client = mock_port_client
65-
ocean_mock.metrics = MagicMock()
66-
ocean_mock.metrics.post_metrics = AsyncMock()
67-
ocean_mock.metrics.flush = AsyncMock()
107+
108+
# Create real metrics instance
109+
metrics_settings = MetricsSettings(enabled=True)
110+
integration_settings = IntegrationSettings(type="test", identifier="test")
111+
ocean_mock.metrics = Metrics(
112+
metrics_settings=metrics_settings,
113+
integration_configuration=integration_settings,
114+
port_client=mock_port_client,
115+
)
68116

69117
return ocean_mock
70118

@@ -81,13 +129,17 @@ async def test_sync_raw_mixin_self_dependency(
81129
entities = [create_entity(*entity_param) for entity_param in entities_params]
82130

83131
calc_result_mock = MagicMock()
84-
calc_result_mock.entity_selector_diff.passed = entities
85-
calc_result_mock.errors = []
132+
calc_result_mock.entity_selector_diff = EntitySelectorDiff(
133+
passed=entities, failed=[] # No failed entities in this test case
134+
)
135+
calc_result_mock.errors = [] # No errors in this test case
136+
calc_result_mock.number_of_transformed_entities = len(
137+
entities
138+
) # Add this to match real behavior
139+
calc_result_mock.misonfigured_entity_keys = {} # Add this to match real behavior
86140

87141
mock_sync_raw_mixin.entity_processor.parse_items = AsyncMock(return_value=calc_result_mock) # type: ignore
88-
mock_ocean.metrics.report_sync_metrics = AsyncMock(return_value=None) # type: ignore
89-
mock_ocean.metrics.report_kind_sync_metrics = AsyncMock(return_value=None) # type: ignore
90-
mock_ocean.metrics.send_metrics_to_webhook = AsyncMock(return_value=None) # type: ignore
142+
91143
mock_order_by_entities_dependencies = MagicMock(
92144
side_effect=EntityTopologicalSorter.order_by_entities_dependencies
93145
)
@@ -126,6 +178,50 @@ async def test_sync_raw_mixin_self_dependency(
126178
for call in mock_order_by_entities_dependencies.call_args_list
127179
] == [entity for entity in entities if entity.identifier == "entity_1"]
128180

181+
# Add assertions for actual metrics
182+
metrics = mock_ocean.metrics.generate_metrics()
183+
assert len(metrics) == 2
184+
185+
# Verify object counts
186+
for metric in metrics:
187+
if metric["kind"] == "project":
188+
assert (
189+
metric["metrics"]["phase"]["extract"]["object_count_type"][
190+
"raw_extracted"
191+
]["object_count"]
192+
== 5
193+
)
194+
assert (
195+
metric["metrics"]["phase"]["transform"][
196+
"object_count_type"
197+
]["transformed"]["object_count"]
198+
== 2
199+
)
200+
assert (
201+
metric["metrics"]["phase"]["transform"][
202+
"object_count_type"
203+
]["filtered_out"]["object_count"]
204+
== 3
205+
)
206+
assert (
207+
metric["metrics"]["phase"]["load"]["object_count_type"][
208+
"failed"
209+
]["object_count"]
210+
== 1
211+
)
212+
assert (
213+
metric["metrics"]["phase"]["load"]["object_count_type"][
214+
"loaded"
215+
]["object_count"]
216+
== 1
217+
)
218+
219+
# Verify success
220+
assert metric["metrics"]["phase"]["resync"]["success"] == 1
221+
222+
# Verify sync state
223+
assert metric["syncState"] == "completed"
224+
129225

130226
@pytest.mark.asyncio
131227
async def test_sync_raw_mixin_circular_dependency(
@@ -138,13 +234,17 @@ async def test_sync_raw_mixin_circular_dependency(
138234
entities = [create_entity(*entity_param) for entity_param in entities_params]
139235

140236
calc_result_mock = MagicMock()
141-
calc_result_mock.entity_selector_diff.passed = entities
142-
calc_result_mock.errors = []
237+
calc_result_mock.entity_selector_diff = EntitySelectorDiff(
238+
passed=entities, failed=[] # No failed entities in this test case
239+
)
240+
calc_result_mock.errors = [] # No errors in this test case
241+
calc_result_mock.number_of_transformed_entities = len(
242+
entities
243+
) # Add this to match real behavior
244+
calc_result_mock.misonfigured_entity_keys = {} # Add this to match real behavior
143245

144246
mock_sync_raw_mixin.entity_processor.parse_items = AsyncMock(return_value=calc_result_mock) # type: ignore
145-
mock_ocean.metrics.report_sync_metrics = AsyncMock(return_value=None) # type: ignore
146-
mock_ocean.metrics.report_kind_sync_metrics = AsyncMock(return_value=None) # type: ignore
147-
mock_ocean.metrics.send_metrics_to_webhook = AsyncMock(return_value=None) # type: ignore
247+
148248
mock_order_by_entities_dependencies = MagicMock(
149249
side_effect=EntityTopologicalSorter.order_by_entities_dependencies
150250
)
@@ -206,11 +306,56 @@ def handle_failed_wrapper(*args: Any, **kwargs: Any) -> Any:
206306
== 2
207307
)
208308

309+
# Add assertions for actual metrics
310+
metrics = mock_ocean.metrics.generate_metrics()
311+
assert len(metrics) == 2
312+
313+
# Verify object counts
314+
for metric in metrics:
315+
if metric["kind"] == "project":
316+
assert (
317+
metric["metrics"]["phase"]["extract"]["object_count_type"][
318+
"raw_extracted"
319+
]["object_count"]
320+
== 5
321+
)
322+
assert (
323+
metric["metrics"]["phase"]["transform"][
324+
"object_count_type"
325+
]["transformed"]["object_count"]
326+
== 2
327+
)
328+
assert (
329+
metric["metrics"]["phase"]["transform"][
330+
"object_count_type"
331+
]["filtered_out"]["object_count"]
332+
== 3
333+
)
334+
assert (
335+
metric["metrics"]["phase"]["load"]["object_count_type"][
336+
"failed"
337+
]["object_count"]
338+
== 2
339+
)
340+
assert (
341+
metric["metrics"]["phase"]["load"]["object_count_type"][
342+
"loaded"
343+
]["object_count"]
344+
== 0
345+
)
346+
347+
# Verify success
348+
assert metric["metrics"]["phase"]["resync"]["success"] == 1
349+
350+
# Verify sync state
351+
assert metric["syncState"] == "completed"
352+
209353

210354
@pytest.mark.asyncio
211355
async def test_sync_raw_mixin_dependency(
212356
mock_sync_raw_mixin: SyncRawMixin, mock_ocean: Ocean
213357
) -> None:
358+
# Create entities with more realistic data
214359
entities_params = [
215360
("entity_1", "service", {"service": "entity_3"}, True),
216361
("entity_2", "service", {"service": "entity_4"}, True),
@@ -219,13 +364,19 @@ async def test_sync_raw_mixin_dependency(
219364
("entity_5", "service", {"service": "entity_1"}, True),
220365
]
221366
entities = [create_entity(*entity_param) for entity_param in entities_params]
222-
mock_ocean.metrics.report_sync_metrics = AsyncMock(return_value=None) # type: ignore
223-
mock_ocean.metrics.report_kind_sync_metrics = AsyncMock(return_value=None) # type: ignore
224-
mock_ocean.metrics.send_metrics_to_webhook = AsyncMock(return_value=None) # type: ignore
367+
368+
# Create a more realistic CalculationResult mock
225369
calc_result_mock = MagicMock()
226-
calc_result_mock.entity_selector_diff.passed = entities
227-
calc_result_mock.errors = []
370+
calc_result_mock.entity_selector_diff = EntitySelectorDiff(
371+
passed=entities, failed=[] # No failed entities in this test case
372+
)
373+
calc_result_mock.errors = [] # No errors in this test case
374+
calc_result_mock.number_of_transformed_entities = len(
375+
entities
376+
) # Add this to match real behavior
377+
calc_result_mock.misonfigured_entity_keys = {} # Add this to match real behavior
228378

379+
# Mock the parse_items method to return our realistic mock
229380
mock_sync_raw_mixin.entity_processor.parse_items = AsyncMock(return_value=calc_result_mock) # type: ignore
230381

231382
mock_order_by_entities_dependencies = MagicMock(
@@ -295,6 +446,44 @@ def get_entities_wrapper(*args: Any, **kwargs: Any) -> Any:
295446
"entity_3-entity_1-entity_4-entity_5-entity_2",
296447
)
297448

449+
# Add assertions for actual metrics
450+
metrics = mock_ocean.metrics.generate_metrics()
451+
assert len(metrics) == 2
452+
453+
# Verify object counts
454+
for metric in metrics:
455+
if metric["kind"] == "project":
456+
assert (
457+
metric["metrics"]["phase"]["extract"]["object_count_type"][
458+
"raw_extracted"
459+
]["object_count"]
460+
== 5
461+
)
462+
assert (
463+
metric["metrics"]["phase"]["transform"][
464+
"object_count_type"
465+
]["transformed"]["object_count"]
466+
== 5
467+
)
468+
assert (
469+
metric["metrics"]["phase"]["transform"][
470+
"object_count_type"
471+
]["filtered_out"]["object_count"]
472+
== 0
473+
)
474+
assert (
475+
metric["metrics"]["phase"]["load"]["object_count_type"][
476+
"failed"
477+
]["object_count"]
478+
== 5
479+
)
480+
481+
# Verify success
482+
assert metric["metrics"]["phase"]["resync"]["success"] == 1
483+
484+
# Verify sync state
485+
assert metric["syncState"] == "completed"
486+
298487

299488
@pytest.mark.asyncio
300489
async def test_register_raw(

0 commit comments

Comments
 (0)