Skip to content

Commit 4965fca

Browse files
authored
fix(source-hubspot): bump to latest version of CDK to get the bug fix for missing custom properties during incremental syncs (#68159)
## What We had a bug where during incremental syncs Hubspot CRM Search streams were not including the custom properties in the json body of the POST request so they were not getting received and emitted with records. ## How The bug was in the CDK and it was fixed in version `7.3.7` in this airbytehq/airbyte-python-cdk#797 We need to bump the version of SDM to get the fix, but in addition, we need to upgrade the unit_test `pyproject.toml` which is still on v6. I've also added a new test that validates that properties are indeed populated in the outbound request. And with the bump from v6 to v7 I fixed the tests which have now changed. **Note**: It does feel like we have something of a gap where our unit tests don't properly test CDK changes since the two are independently versioned... This is something we may want to investigate and solve so these types of things don't happen again ## Can this PR be safely reverted and rolled back? - [ ] YES 💚 - [ ] NO ❌ Kind of... If we do this wrong then we have to reset customers back to their previous state, but this is no different than the state we were previously in
1 parent 9b68e05 commit 4965fca

File tree

12 files changed

+957
-492
lines changed

12 files changed

+957
-492
lines changed

airbyte-integrations/connectors/source-hubspot/metadata.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ data:
66
hosts:
77
- api.hubapi.com
88
connectorBuildOptions:
9-
baseImage: docker.io/airbyte/source-declarative-manifest:7.3.4@sha256:ebf5c159101614ec9ab3a5183c92781c397b998d15c0b7e2e561ec1e00397fa4
9+
baseImage: docker.io/airbyte/source-declarative-manifest:7.3.7@sha256:7832dad42cabf1fb0c0c3f5239f256d3e4eca6191d3c8d0bff3b5b708dac46d4
1010
connectorSubtype: api
1111
connectorType: source
1212
definitionId: 36c891d9-4bd9-43ac-bad2-10e12756272c
13-
dockerImageTag: 6.0.6
13+
dockerImageTag: 6.0.7-rc.1
1414
dockerRepository: airbyte/source-hubspot
1515
documentationUrl: https://docs.airbyte.com/integrations/sources/hubspot
1616
resourceRequirements:
@@ -43,7 +43,7 @@ data:
4343
releaseStage: generally_available
4444
releases:
4545
rolloutConfiguration:
46-
enableProgressiveRollout: false
46+
enableProgressiveRollout: true
4747
breakingChanges:
4848
6.0.0:
4949
message: >-

airbyte-integrations/connectors/source-hubspot/unit_tests/integrations/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def mock_response(cls, http_mocker: HttpMocker, request, responses, method: str
145145

146146
@classmethod
147147
def mock_dynamic_schema_requests(cls, http_mocker: HttpMocker, entities: Optional[List[str]] = None):
148-
entities = entities or OBJECTS_WITH_DYNAMIC_SCHEMA
148+
entities = entities if entities is not None else OBJECTS_WITH_DYNAMIC_SCHEMA
149149

150150
# figure out which entities are already mocked
151151
existing = set()

airbyte-integrations/connectors/source-hubspot/unit_tests/integrations/test_engagements_calls.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def test_given_oauth_authentication_when_read_then_perform_authenticated_queries
6868
http_mocker,
6969
with_oauth=True,
7070
with_dynamic_schemas=True,
71-
entities=OBJECTS_WITH_DYNAMIC_SCHEMA,
71+
entities=["calls"],
7272
)
7373
self.mock_response(http_mocker, self.request(), self.response())
7474
self.read_from_stream(self.oauth_config(), self.STREAM_NAME, SyncMode.full_refresh)
@@ -79,30 +79,30 @@ def test_given_records_when_read_extract_desired_records(self, http_mocker: Http
7979
http_mocker,
8080
with_oauth=True,
8181
with_dynamic_schemas=True,
82-
entities=OBJECTS_WITH_DYNAMIC_SCHEMA,
82+
entities=["calls"],
8383
)
8484
self.mock_response(http_mocker, self.request(), self.response())
8585
output = self.read_from_stream(self.oauth_config(), self.STREAM_NAME, SyncMode.full_refresh)
8686
assert len(output.records) == 1
8787

8888
@HttpMocker()
8989
def test_given_one_page_when_read_stream_private_token_then_return_records(self, http_mocker: HttpMocker):
90-
self._set_up_requests(http_mocker)
90+
self._set_up_requests(http_mocker, with_dynamic_schemas=True, entities=["calls"])
9191
self.mock_response(http_mocker, self.request(), self.response())
9292
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
9393
assert len(output.records) == 1
9494

9595
@HttpMocker()
9696
def test_given_two_pages_when_read_then_return_records(self, http_mocker: HttpMocker):
97-
self._set_up_requests(http_mocker)
97+
self._set_up_requests(http_mocker, with_dynamic_schemas=True, entities=["calls"])
9898
self.mock_response(http_mocker, self.request(), self.response(with_pagination=True))
9999
self.mock_response(http_mocker, self.request(page_token=self.response_builder.pagination_strategy.NEXT_PAGE_TOKEN), self.response())
100100
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
101101
assert len(output.records) == 2
102102

103103
@HttpMocker()
104104
def test_given_error_response_when_read_analytics_then_get_trace_message(self, http_mocker: HttpMocker):
105-
self._set_up_requests(http_mocker)
105+
self._set_up_requests(http_mocker, with_dynamic_schemas=True, entities=["calls"])
106106
self.mock_response(http_mocker, self.request(), HttpResponse(status_code=500, body="{}"))
107107
with mock.patch("time.sleep"):
108108
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
@@ -112,7 +112,7 @@ def test_given_error_response_when_read_analytics_then_get_trace_message(self, h
112112

113113
@HttpMocker()
114114
def test_given_500_then_200_when_read_then_return_records(self, http_mocker: HttpMocker):
115-
self._set_up_requests(http_mocker)
115+
self._set_up_requests(http_mocker, with_dynamic_schemas=True, entities=["calls"])
116116
self.mock_response(http_mocker, self.request(), [HttpResponse(status_code=500, body="{}"), self.response()])
117117
with mock.patch("time.sleep"):
118118
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
@@ -128,7 +128,7 @@ def test_given_missing_scopes_error_when_read_then_stop_sync(self, http_mocker:
128128

129129
@HttpMocker()
130130
def test_given_unauthorized_error_when_read_then_stop_sync(self, http_mocker: HttpMocker):
131-
self._set_up_requests(http_mocker)
131+
self._set_up_requests(http_mocker, with_dynamic_schemas=True, entities=["calls"])
132132
self.mock_response(http_mocker, self.request(), HttpResponse(status_code=http.HTTPStatus.UNAUTHORIZED, body="{}"))
133133
with mock.patch("time.sleep"):
134134
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
@@ -138,7 +138,7 @@ def test_given_unauthorized_error_when_read_then_stop_sync(self, http_mocker: Ht
138138

139139
@HttpMocker()
140140
def test_given_one_page_when_read_then_get_records_with_flattened_properties(self, http_mocker: HttpMocker):
141-
self._set_up_requests(http_mocker)
141+
self._set_up_requests(http_mocker, with_dynamic_schemas=True, entities=["calls"])
142142
self.mock_response(http_mocker, self.request(), self.response())
143143
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
144144
record = output.records[0].record.data
@@ -148,7 +148,7 @@ def test_given_one_page_when_read_then_get_records_with_flattened_properties(sel
148148

149149
@HttpMocker()
150150
def test_given_incremental_sync_when_read_then_state_message_produced_and_state_match_latest_record(self, http_mocker: HttpMocker):
151-
self._set_up_requests(http_mocker)
151+
self._set_up_requests(http_mocker, with_dynamic_schemas=True, entities=["calls"])
152152
self.mock_response(http_mocker, self.request(), self.response())
153153
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.incremental)
154154
assert len(output.state_messages) == 2

airbyte-integrations/connectors/source-hubspot/unit_tests/integrations/test_leads.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
22

33
import http
4-
from typing import Dict, Optional
4+
from typing import Dict, List, Optional
55

66
import freezegun
77
import mock
@@ -52,44 +52,46 @@ def response(self, with_pagination: bool = False):
5252
def _set_up_oauth(self, http_mocker: HttpMocker):
5353
self.mock_oauth(http_mocker, self.ACCESS_TOKEN)
5454

55-
def _set_up_requests(self, http_mocker: HttpMocker, with_oauth: bool = False, with_dynamic_schema: bool = True):
55+
def _set_up_requests(
56+
self, http_mocker: HttpMocker, with_oauth: bool = False, with_dynamic_schema: bool = True, entities: Optional[List[str]] = None
57+
):
5658
if with_oauth:
5759
self._set_up_oauth(http_mocker)
5860
self.mock_custom_objects(http_mocker)
5961
self.mock_properties(http_mocker, self.OBJECT_TYPE, self.MOCK_PROPERTIES_FOR_SCHEMA_LOADER)
6062
if with_dynamic_schema:
61-
self.mock_dynamic_schema_requests(http_mocker)
63+
self.mock_dynamic_schema_requests(http_mocker, entities)
6264

6365
@HttpMocker()
6466
def test_given_oauth_authentication_when_read_then_perform_authenticated_queries(self, http_mocker: HttpMocker):
65-
self._set_up_requests(http_mocker, with_oauth=True, with_dynamic_schema=True)
67+
self._set_up_requests(http_mocker, with_oauth=True, with_dynamic_schema=True, entities=["leads"])
6668
self.read_from_stream(self.oauth_config(), self.STREAM_NAME, SyncMode.full_refresh)
6769

6870
@HttpMocker()
6971
def test_given_records_when_read_extract_desired_records(self, http_mocker: HttpMocker):
70-
self._set_up_requests(http_mocker, with_oauth=True, with_dynamic_schema=True)
72+
self._set_up_requests(http_mocker, with_oauth=True, with_dynamic_schema=True, entities=["leads"])
7173
self.mock_response(http_mocker, self.request(), self.response())
7274
output = self.read_from_stream(self.oauth_config(), self.STREAM_NAME, SyncMode.full_refresh)
7375
assert len(output.records) == 1
7476

7577
@HttpMocker()
7678
def test_given_one_page_when_read_stream_private_token_then_return_records(self, http_mocker: HttpMocker):
77-
self._set_up_requests(http_mocker)
79+
self._set_up_requests(http_mocker, with_dynamic_schema=True, entities=["leads"])
7880
self.mock_response(http_mocker, self.request(), self.response())
7981
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
8082
assert len(output.records) == 1
8183

8284
@HttpMocker()
8385
def test_given_two_pages_when_read_then_return_records(self, http_mocker: HttpMocker):
84-
self._set_up_requests(http_mocker)
86+
self._set_up_requests(http_mocker, with_dynamic_schema=True, entities=["leads"])
8587
self.mock_response(http_mocker, self.request(), self.response(with_pagination=True))
8688
self.mock_response(http_mocker, self.request(page_token=self.response_builder.pagination_strategy.NEXT_PAGE_TOKEN), self.response())
8789
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
8890
assert len(output.records) == 2
8991

9092
@HttpMocker()
9193
def test_given_error_response_when_read_analytics_then_get_trace_message(self, http_mocker: HttpMocker):
92-
self._set_up_requests(http_mocker)
94+
self._set_up_requests(http_mocker, with_dynamic_schema=True, entities=["leads"])
9395
self.mock_response(http_mocker, self.request(), HttpResponse(status_code=500, body="{}"))
9496
with mock.patch("time.sleep"):
9597
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
@@ -99,7 +101,7 @@ def test_given_error_response_when_read_analytics_then_get_trace_message(self, h
99101

100102
@HttpMocker()
101103
def test_given_500_then_200_when_read_then_return_records(self, http_mocker: HttpMocker):
102-
self._set_up_requests(http_mocker)
104+
self._set_up_requests(http_mocker, with_dynamic_schema=True, entities=["leads"])
103105
self.mock_response(http_mocker, self.request(), [HttpResponse(status_code=500, body="{}"), self.response()])
104106
with mock.patch("time.sleep"):
105107
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
@@ -115,7 +117,7 @@ def test_given_missing_scopes_error_when_read_then_stop_sync(self, http_mocker:
115117

116118
@HttpMocker()
117119
def test_given_unauthorized_error_when_read_then_stop_sync(self, http_mocker: HttpMocker):
118-
self._set_up_requests(http_mocker)
120+
self._set_up_requests(http_mocker, with_dynamic_schema=True, entities=["leads"])
119121
self.mock_response(http_mocker, self.request(), HttpResponse(status_code=http.HTTPStatus.UNAUTHORIZED, body="{}"))
120122
with mock.patch("time.sleep"):
121123
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
@@ -125,7 +127,7 @@ def test_given_unauthorized_error_when_read_then_stop_sync(self, http_mocker: Ht
125127

126128
@HttpMocker()
127129
def test_given_one_page_when_read_then_get_records_with_flattened_properties(self, http_mocker: HttpMocker):
128-
self._set_up_requests(http_mocker)
130+
self._set_up_requests(http_mocker, with_dynamic_schema=True, entities=["leads"])
129131
self.mock_response(http_mocker, self.request(), self.response())
130132
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.full_refresh)
131133
record = output.records[0].record.data
@@ -135,7 +137,7 @@ def test_given_one_page_when_read_then_get_records_with_flattened_properties(sel
135137

136138
@HttpMocker()
137139
def test_given_incremental_sync_when_read_then_state_message_produced_and_state_match_latest_record(self, http_mocker: HttpMocker):
138-
self._set_up_requests(http_mocker)
140+
self._set_up_requests(http_mocker, with_dynamic_schema=True, entities=["leads"])
139141
self.mock_response(http_mocker, self.request(), self.response())
140142
output = self.read_from_stream(self.private_token_config(self.ACCESS_TOKEN), self.STREAM_NAME, SyncMode.incremental)
141143
assert len(output.state_messages) == 2

airbyte-integrations/connectors/source-hubspot/unit_tests/integrations/test_owners_archived.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ def response(self, with_pagination: bool = False):
4444
def test_given_one_page_when_read_stream_oauth_then_return_records(self, http_mocker: HttpMocker):
4545
self.mock_oauth(http_mocker, self.ACCESS_TOKEN)
4646
self.mock_custom_objects(http_mocker)
47-
self.mock_dynamic_schema_requests(http_mocker)
47+
self.mock_dynamic_schema_requests(http_mocker, entities=[])
4848
self.mock_response(http_mocker, self.request().build(), self.response().build())
4949
output = self.read_from_stream(self.oauth_config(), self.STREAM_NAME, SyncMode.full_refresh)
5050
assert len(output.records) == 1
5151

5252
@HttpMocker()
5353
def test_given_two_pages_when_read_stream_private_token_then_return_records(self, http_mocker: HttpMocker):
5454
self.mock_custom_objects(http_mocker)
55-
self.mock_dynamic_schema_requests(http_mocker)
55+
self.mock_dynamic_schema_requests(http_mocker, entities=[])
5656
self.mock_response(http_mocker, self.request().build(), self.response(with_pagination=True).build())
5757
self.mock_response(
5858
http_mocker,

0 commit comments

Comments
 (0)