Skip to content

Commit 5614a91

Browse files
committed
task: add validation to DatetimeBasedCursor
1 parent c6052cc commit 5614a91

File tree

2 files changed

+71
-7
lines changed

2 files changed

+71
-7
lines changed

airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
)
2222
from airbyte_cdk.sources.message import MessageRepository
2323
from airbyte_cdk.sources.types import Config, Record, StreamSlice, StreamState
24+
from airbyte_cdk.utils.mapping_helpers import _validate_multiple_request_options
2425

2526

2627
@dataclass
@@ -121,6 +122,8 @@ def __post_init__(self, parameters: Mapping[str, Any]) -> None:
121122

122123
if not self.cursor_datetime_formats:
123124
self.cursor_datetime_formats = [self.datetime_format]
125+
126+
_validate_multiple_request_options(self.config, self.start_time_option, self.end_time_option)
124127

125128
def get_stream_state(self) -> StreamState:
126129
return {self.cursor_field.eval(self.config): self._cursor} if self._cursor else {} # type: ignore # cursor_field is converted to an InterpolatedString in __post_init__

unit_tests/utils/test_mapping_helpers.py

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
from airbyte_cdk.utils.mapping_helpers import combine_mappings
3+
from airbyte_cdk.utils.mapping_helpers import combine_mappings, _validate_multiple_request_options, RequestOption, RequestOptionType
44

55

66
@pytest.mark.parametrize(
@@ -46,14 +46,14 @@ def test_string_handling(test_name, mappings, expected_result, expected_error):
4646
@pytest.mark.parametrize(
4747
"test_name, mappings, expected_error",
4848
[
49-
("duplicate_keys_same_value", [{"a": 1}, {"a": 1}], "Duplicate keys found"),
50-
("duplicate_keys_different_value", [{"a": 1}, {"a": 2}], "Duplicate keys found"),
49+
("duplicate_keys_same_value", [{"a": 1}, {"a": 1}], "duplicate keys detected"),
50+
("duplicate_keys_different_value", [{"a": 1}, {"a": 2}], "duplicate keys detected"),
5151
(
5252
"nested_structure_not_allowed",
5353
[{"a": {"b": 1}}, {"a": {"c": 2}}],
54-
"Duplicate keys found",
54+
"duplicate keys detected",
5555
),
56-
("any_nesting_not_allowed", [{"a": {"b": 1}}, {"a": {"d": 2}}], "Duplicate keys found"),
56+
("any_nesting_not_allowed", [{"a": {"b": 1}}, {"a": {"d": 2}}], "duplicate keys detected"),
5757
],
5858
)
5959
def test_non_body_json_requests(test_name, mappings, expected_error):
@@ -96,13 +96,13 @@ def test_non_body_json_requests(test_name, mappings, expected_error):
9696
"nested_conflict",
9797
[{"a": {"b": 1}}, {"a": {"b": 2}}],
9898
None,
99-
"Duplicate keys found",
99+
"duplicate keys detected",
100100
),
101101
(
102102
"type_conflict",
103103
[{"a": 1}, {"a": {"b": 2}}],
104104
None,
105-
"Duplicate keys found",
105+
"duplicate keys detected",
106106
),
107107
],
108108
)
@@ -113,3 +113,64 @@ def test_body_json_requests(test_name, mappings, expected_result, expected_error
113113
combine_mappings(mappings, allow_same_value_merge=True)
114114
else:
115115
assert combine_mappings(mappings, allow_same_value_merge=True) == expected_result
116+
117+
118+
@pytest.fixture
119+
def mock_config() -> dict[str, str]:
120+
return {"test": "config"}
121+
122+
@pytest.mark.parametrize(
123+
"test_name, option1, option2, should_raise",
124+
[
125+
(
126+
"different_fields",
127+
RequestOption(field_name="field1", inject_into=RequestOptionType.body_json, parameters={}),
128+
RequestOption(field_name="field2", inject_into=RequestOptionType.body_json, parameters={}),
129+
False,
130+
),
131+
(
132+
"same_field_name_header",
133+
RequestOption(field_name="field", inject_into=RequestOptionType.header, parameters={}),
134+
RequestOption(field_name="field", inject_into=RequestOptionType.header, parameters={}),
135+
True,
136+
),
137+
(
138+
"different_nested_paths",
139+
RequestOption(field_path=["data", "query1", "limit"], inject_into=RequestOptionType.body_json, parameters={}),
140+
RequestOption(field_path=["data", "query2", "limit"], inject_into=RequestOptionType.body_json, parameters={}),
141+
False,
142+
),
143+
(
144+
"same_nested_paths",
145+
RequestOption(field_path=["data", "query", "limit"], inject_into=RequestOptionType.body_json, parameters={}),
146+
RequestOption(field_path=["data", "query", "limit"], inject_into=RequestOptionType.body_json, parameters={}),
147+
True,
148+
),
149+
(
150+
"different_inject_types",
151+
RequestOption(field_name="field", inject_into=RequestOptionType.header, parameters={}),
152+
RequestOption(field_name="field", inject_into=RequestOptionType.body_json, parameters={}),
153+
False,
154+
),
155+
]
156+
)
157+
def test_request_option_validation(test_name, option1, option2, should_raise, mock_config):
158+
"""Test various combinations of request option validation"""
159+
if should_raise:
160+
with pytest.raises(ValueError, match="duplicate keys detected"):
161+
_validate_multiple_request_options(mock_config, option1, option2)
162+
else:
163+
_validate_multiple_request_options(mock_config, option1, option2)
164+
165+
@pytest.mark.parametrize(
166+
"test_name, options",
167+
[
168+
("none_options", [None, RequestOption(field_name="field", inject_into=RequestOptionType.header, parameters={}), None]),
169+
("single_option", [RequestOption(field_name="field", inject_into=RequestOptionType.header, parameters={})]),
170+
("all_none", [None, None, None]),
171+
("empty_list", []),
172+
]
173+
)
174+
def test_edge_cases(test_name, options, mock_config):
175+
"""Test edge cases like None values and single options"""
176+
_validate_multiple_request_options(mock_config, *options)

0 commit comments

Comments
 (0)