1
0
mirror of synced 2025-12-25 02:09:19 -05:00

feat(source-stripe): migrate application_fees, authorizations, cardholders, cards, early_fraud_warnings, external_account_bank_accounts and external_account_cards to low-code (#58060)

Co-authored-by: Octavia Squidington III <octavia-squidington-iii@users.noreply.github.com>
This commit is contained in:
Serhii Lazebnyi
2025-04-15 15:58:31 +02:00
committed by GitHub
parent 0729ed85bd
commit 0e0f2dc9f7
14 changed files with 556 additions and 389 deletions

View File

@@ -10,7 +10,7 @@ data:
connectorSubtype: api
connectorType: source
definitionId: e094cb9a-26de-4645-8761-65c0c425d1de
dockerImageTag: 5.9.2
dockerImageTag: 5.10.0-rc.1
dockerRepository: airbyte/source-stripe
documentationUrl: https://docs.airbyte.com/integrations/sources/stripe
erdUrl: https://dbdocs.io/airbyteio/source-stripe?view=relationships
@@ -31,7 +31,7 @@ data:
releaseStage: generally_available
releases:
rolloutConfiguration:
enableProgressiveRollout: false
enableProgressiveRollout: true
breakingChanges:
4.0.0:
message: Version 4.0.0 changes the cursors in most of the Stripe streams that support incremental sync mode. This is done to not only sync the data that was created since previous sync, but also the data that was modified. A schema refresh of all effected streams is required to use the new cursor format.

View File

@@ -2,14 +2,14 @@
[[package]]
name = "airbyte-cdk"
version = "6.42.1"
version = "6.45.1"
description = "A framework for writing Airbyte Connectors."
optional = false
python-versions = "<3.13,>=3.10"
groups = ["main"]
files = [
{file = "airbyte_cdk-6.42.1-py3-none-any.whl", hash = "sha256:7a23fa4d3711173b7dbc1f20d94c8f10bb3fae3f164464be63cf60ad631782ab"},
{file = "airbyte_cdk-6.42.1.tar.gz", hash = "sha256:c8fd4760cd68f8a93ea32bdafadd0d436f8274ee01ca316ab574f99433d4684b"},
{file = "airbyte_cdk-6.45.1-py3-none-any.whl", hash = "sha256:bd79306da8b8c6c2e7100c407872845e734a82ad5814c0899ac1c33c7b292780"},
{file = "airbyte_cdk-6.45.1.tar.gz", hash = "sha256:a9e5ea9c57080604716a9f1d148a8703688ce05bac68dd0a71a4ad8c38afd05d"},
]
[package.dependencies]
@@ -572,14 +572,14 @@ files = [
[[package]]
name = "httpcore"
version = "1.0.7"
version = "1.0.8"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
{file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
{file = "httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be"},
{file = "httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad"},
]
[package.dependencies]

View File

@@ -3,7 +3,7 @@ requires = [ "poetry-core>=1.0.0",]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
version = "5.9.2"
version = "5.10.0-rc.1"
name = "source-stripe"
description = "Source implementation for Stripe."
authors = [ "Airbyte <contact@airbyte.io>",]

View File

@@ -1,4 +1,4 @@
version: 6.33.0
version: 6.42.1
type: DeclarativeSource
@@ -8,11 +8,9 @@ check:
- events
definitions:
# base components
# Base components
base_stream:
type: DeclarativeStream
primary_key:
- id
retriever:
$ref: "#/definitions/base_retriever"
schema_loader:
@@ -29,6 +27,7 @@ definitions:
type: DpathExtractor
field_path:
- data
transform_before_filtering: true
schema_normalization: Default
paginator:
$ref: "#/definitions/base_paginator"
@@ -42,7 +41,35 @@ definitions:
request_headers:
Stripe-Version: "2022-11-15"
Stripe-Account: '{{ config.get("account_id") }}'
error_handler:
type: CompositeErrorHandler
error_handlers:
- type: DefaultErrorHandler
response_filters:
- type: HttpResponseFilter
action: IGNORE
http_codes:
- 403
error_message: >-
{{ response['error']['message'] }}
- type: DefaultErrorHandler
response_filters:
- type: HttpResponseFilter
action: IGNORE
http_codes:
- 400
error_message: >-
{{ response['error']['message'] }}
- type: DefaultErrorHandler
response_filters:
- type: HttpResponseFilter
action: IGNORE
http_codes:
- 404
error_message: >-
Data was not found. Error message: {{ response['error']['message'] }} If this is a path for getting
child attributes like /v1/checkout/sessions/<session_id>/line_items when running the incremental sync,
you may safely ignore this warning.
bearer_authenticator:
type: BearerAuthenticator
api_token: "{{ config['client_secret'] }}"
@@ -88,7 +115,7 @@ definitions:
field_name: "created[lte]"
inject_into: "request_parameter"
# created flow component
# Regular flow components
created_stream:
$ref: "#/definitions/base_stream"
incremental_sync:
@@ -98,6 +125,143 @@ definitions:
$ref: "#/definitions/base_incremental_sync"
cursor_field: created
# Entity flow components
entity_stream:
$ref: "#/definitions/base_stream"
primary_key:
- id
incremental_sync:
$ref: "#/definitions/entity_with_slice_cursor"
transformations:
- type: AddFields
fields:
- path:
- updated
value: "{{ record.get('updated', record.get('created', now_utc().timestamp())) | int }}"
value_type: integer
entity_restricted_stream:
$ref: "#/definitions/base_stream"
primary_key:
- id
incremental_sync:
$ref: "#/definitions/entity_single_slice_cursor"
transformations:
- type: AddFields
fields:
- path:
- updated
value: "{{ record.get('updated', record.get('created', now_utc().timestamp())) | int }}"
value_type: integer
entity_with_slice_cursor:
type: DatetimeBasedCursor
cursor_field: updated
cursor_datetime_formats:
- "%s"
datetime_format: "%s"
step: P{{ config.get('slice_range', 365) }}D
cursor_granularity: PT1S
lookback_window: P{{ config.get('lookback_window_days', 0) }}D
start_datetime:
type: MinMaxDatetime
datetime: "{{ format_datetime(config.get('start_date', '2017-01-25T00:00:00Z'), '%Y-%m-%dT%H:%M:%S%z') }}"
datetime_format: "%Y-%m-%dT%H:%M:%S%z"
end_datetime:
type: MinMaxDatetime
datetime: "{{ now_utc().strftime('%Y-%m-%dT%H:%M:%SZ') }}"
datetime_format: "%Y-%m-%dT%H:%M:%SZ"
start_time_option:
type: RequestOption
field_name: "created[gte]"
inject_into: "request_parameter"
end_time_option:
type: RequestOption
field_name: "created[lte]"
inject_into: "request_parameter"
entity_single_slice_cursor:
type: DatetimeBasedCursor
cursor_field: updated
cursor_datetime_formats:
- "%s"
datetime_format: "%s"
lookback_window: P{{ config.get('lookback_window_days', 0) }}D
start_datetime:
type: MinMaxDatetime
datetime: "{{ format_datetime(config.get('start_date', '2017-01-25T00:00:00Z'), '%Y-%m-%dT%H:%M:%S%z') }}"
datetime_format: "%Y-%m-%dT%H:%M:%S%z"
end_datetime:
type: MinMaxDatetime
datetime: "{{ now_utc().strftime('%Y-%m-%dT%H:%M:%SZ') }}"
datetime_format: "%Y-%m-%dT%H:%M:%SZ"
# Events flow components
events_based_stream:
$ref: "#/definitions/base_stream"
primary_key:
- id
incremental_sync:
$ref: "#/definitions/events_read_slice_cursor"
retriever:
$ref: "#/definitions/events_objects_retriever"
transformations:
- type: AddFields
fields:
- path:
- data
- object
- is_deleted
value: "{{ True }}"
condition: "{{ record.get('data', {}).get('object', False) and record.get('type', '').endswith('.deleted') }}"
- type: AddFields
fields:
- path:
- data
- object
- updated
value: "{{ record.get('updated', record.get('created', now_utc().timestamp())) | int }}"
value_type: integer
condition: "{{ record.get('data', {}).get('object', False) }}"
- type: DpathFlattenFields
field_path:
- data
- object
replace_record: true
events_objects_retriever:
$ref: "#/definitions/base_retriever"
requester:
$ref: "#/definitions/base_requester"
path: events
events_read_slice_cursor:
type: DatetimeBasedCursor
cursor_field: updated
cursor_datetime_formats:
- "%s"
datetime_format: "%s"
step: P{{ config.get('slice_range', 365) }}D
cursor_granularity: PT1S
lookback_window: P{{ config.get('lookback_window_days', 0) }}D
start_datetime:
type: MinMaxDatetime
datetime: "{{ format_datetime(config.get('start_date', '2017-01-25T00:00:00Z'), '%Y-%m-%dT%H:%M:%S%z') }}"
datetime_format: "%Y-%m-%dT%H:%M:%S%z"
min_datetime: "{{ (now_utc() - duration('P30D')).strftime('%Y-%m-%dT%H:%M:%SZ') }}"
end_datetime:
type: MinMaxDatetime
datetime: "{{ now_utc().strftime('%Y-%m-%dT%H:%M:%SZ') }}"
datetime_format: "%Y-%m-%dT%H:%M:%SZ"
start_time_option:
type: RequestOption
field_name: "created[gte]"
inject_into: "request_parameter"
end_time_option:
type: RequestOption
field_name: "created[lte]"
inject_into: "request_parameter"
streams:
events:
$ref: "#/definitions/created_stream"
@@ -125,13 +289,167 @@ definitions:
path: file_links
name: file_links
authorizations:
type: StateDelegatingStream
$parameters:
name: authorizations
full_refresh_stream:
$ref: "#/definitions/entity_stream"
retriever:
$ref: "#/definitions/base_retriever"
$parameters:
path: issuing/authorizations
incremental_stream:
$ref: "#/definitions/events_based_stream"
retriever:
$ref: "#/definitions/events_objects_retriever"
$parameters:
request_parameters:
types[]: '{{["issuing_authorization.created", "issuing_authorization.request", "issuing_authorization.updated"]}}'
cardholders:
type: StateDelegatingStream
$parameters:
name: cardholders
full_refresh_stream:
$ref: "#/definitions/entity_stream"
retriever:
$ref: "#/definitions/base_retriever"
$parameters:
path: issuing/cardholders
incremental_stream:
$ref: "#/definitions/events_based_stream"
retriever:
$ref: "#/definitions/events_objects_retriever"
$parameters:
request_parameters:
types[]: '{{["issuing_cardholder.created", "issuing_cardholder.updated"]}}'
application_fees:
type: StateDelegatingStream
$parameters:
name: application_fees
full_refresh_stream:
$ref: "#/definitions/entity_stream"
retriever:
$ref: "#/definitions/base_retriever"
$parameters:
path: application_fees
incremental_stream:
$ref: "#/definitions/events_based_stream"
retriever:
$ref: "#/definitions/events_objects_retriever"
$parameters:
request_parameters:
types[]: '{{["application_fee.created", "application_fee.refunded"]}}'
cards:
type: StateDelegatingStream
$parameters:
name: cards
full_refresh_stream:
$ref: "#/definitions/entity_stream"
retriever:
$ref: "#/definitions/base_retriever"
$parameters:
path: issuing/cards
incremental_stream:
$ref: "#/definitions/events_based_stream"
retriever:
$ref: "#/definitions/events_objects_retriever"
$parameters:
request_parameters:
types[]: '{{["issuing_card.created", "issuing_card.updated"]}}'
external_account_cards:
type: StateDelegatingStream
$parameters:
name: external_account_cards
full_refresh_stream:
$ref: "#/definitions/entity_restricted_stream"
retriever:
$ref: "#/definitions/base_retriever"
$parameters:
path: accounts/{{ config["account_id"] }}/external_accounts
requester:
$ref: "#/definitions/base_requester"
request_parameters:
object: "card"
incremental_stream:
$ref: "#/definitions/events_based_stream"
retriever:
$ref: "#/definitions/events_objects_retriever"
$parameters:
request_parameters:
types[]: '{{["account.external_account.created", "account.external_account.updated", "account.external_account.deleted"]}}'
record_filter:
condition: "{{ record['object'] == 'card' }}"
external_account_bank_accounts:
type: StateDelegatingStream
$parameters:
name: external_account_bank_accounts
full_refresh_stream:
$ref: "#/definitions/entity_restricted_stream"
retriever:
$ref: "#/definitions/base_retriever"
$parameters:
path: accounts/{{ config["account_id"] }}/external_accounts
requester:
$ref: "#/definitions/base_requester"
request_parameters:
object: "bank_account"
incremental_stream:
$ref: "#/definitions/events_based_stream"
retriever:
$ref: "#/definitions/events_objects_retriever"
$parameters:
request_parameters:
types[]: '{{["account.external_account.created", "account.external_account.updated", "account.external_account.deleted"]}}'
record_filter:
condition: "{{ record['object'] == 'bank_account' }}"
early_fraud_warnings:
type: StateDelegatingStream
$parameters:
name: early_fraud_warnings
full_refresh_stream:
$ref: "#/definitions/entity_restricted_stream"
retriever:
$ref: "#/definitions/base_retriever"
$parameters:
path: radar/early_fraud_warnings
incremental_stream:
$ref: "#/definitions/events_based_stream"
retriever:
$ref: "#/definitions/events_objects_retriever"
$parameters:
request_parameters:
types[]: '{{["radar.early_fraud_warning.created", "radar.early_fraud_warning.updated"]}}'
streams:
# These streams are base incremental streams
- $ref: "#/definitions/streams/events"
- $ref: "#/definitions/streams/shipping_rates"
- $ref: "#/definitions/streams/balance_transactions"
- $ref: "#/definitions/streams/files"
- $ref: "#/definitions/streams/file_links"
## These streams are state condition streams and have two behaviors depending on whether the state is set:
# - No State: Runs the base stream.
# - State: Uses the event stream to call events related to streams (event types provided by params).
- $ref: "#/definitions/streams/application_fees"
- $ref: "#/definitions/streams/authorizations"
- $ref: "#/definitions/streams/cardholders"
- $ref: "#/definitions/streams/cards"
## These streams are state condition streams with stricter behavior:
# - No State: Runs the base stream (endpoints strictly define allowed query params and return an error if unexpected params are present).
# - State: Uses the event stream to call events related to streams (event types provided by params).
- $ref: "#/definitions/streams/early_fraud_warnings"
- $ref: "#/definitions/streams/external_account_bank_accounts"
- $ref: "#/definitions/streams/external_account_cards"
spec:
type: Spec
documentation_url: https://docs.airbyte.com/integrations/sources/stripe

View File

@@ -284,24 +284,6 @@ class SourceStripe(YamlDeclarativeSource):
streams = [
checkout_sessions,
UpdatedCursorIncrementalStripeStream(
name="external_account_cards",
path=lambda self, *args, **kwargs: f"accounts/{self.account_id}/external_accounts",
event_types=["account.external_account.created", "account.external_account.updated", "account.external_account.deleted"],
legacy_cursor_field=None,
extra_request_params={"object": "card"},
response_filter=lambda record: record["object"] == "card",
**args,
),
UpdatedCursorIncrementalStripeStream(
name="external_account_bank_accounts",
path=lambda self, *args, **kwargs: f"accounts/{self.account_id}/external_accounts",
event_types=["account.external_account.created", "account.external_account.updated", "account.external_account.deleted"],
legacy_cursor_field=None,
extra_request_params={"object": "bank_account"},
response_filter=lambda record: record["object"] == "bank_account",
**args,
),
UpdatedCursorIncrementalStripeSubStream(
name="persons",
path=lambda self, stream_slice, *args, **kwargs: f"accounts/{stream_slice['parent']['id']}/persons",
@@ -340,25 +322,7 @@ class SourceStripe(YamlDeclarativeSource):
event_types=["credit_note.created", "credit_note.updated", "credit_note.voided"],
**args,
),
UpdatedCursorIncrementalStripeStream(
name="early_fraud_warnings",
path="radar/early_fraud_warnings",
event_types=["radar.early_fraud_warning.created", "radar.early_fraud_warning.updated"],
**args,
),
IncrementalStripeStream(
name="authorizations",
path="issuing/authorizations",
event_types=["issuing_authorization.created", "issuing_authorization.request", "issuing_authorization.updated"],
**args,
),
self.customers(**args),
IncrementalStripeStream(
name="cardholders",
path="issuing/cardholders",
event_types=["issuing_cardholder.created", "issuing_cardholder.updated"],
**args,
),
IncrementalStripeStream(
name="charges",
path="charges",
@@ -390,7 +354,6 @@ class SourceStripe(YamlDeclarativeSource):
],
**args,
),
application_fees,
invoices,
IncrementalStripeStream(
name="invoice_items",
@@ -478,9 +441,6 @@ class SourceStripe(YamlDeclarativeSource):
],
**args,
),
IncrementalStripeStream(
name="cards", path="issuing/cards", event_types=["issuing_card.created", "issuing_card.updated"], **args
),
IncrementalStripeStream(
name="transactions",
path="issuing/transactions",

View File

@@ -25,22 +25,3 @@ def assert_stream_did_not_run(output, stream_name: str, expected_error_message_p
# Use any to check if any message contains the substring
found = any(contains_substring(message, expected_error_message_pattern) for message in output.logs)
assert found, f"Expected message '{expected_error_message_pattern}' not found in logs."
def assert_stream_incomplete(output, stream_name: str, expected_error_message_pattern: Optional[str] = None):
expected = [
AirbyteStreamStatus.STARTED,
AirbyteStreamStatus.INCOMPLETE,
]
assert output.get_stream_statuses(stream_name) == expected
assert output.records == []
if expected_error_message_pattern:
def contains_substring(message, expected_message_pattern):
return expected_message_pattern in message.log.message
# Use any to check if any message contains the substring
found = any(contains_substring(message, expected_error_message_pattern) for message in output.logs)
assert found, f"Expected message '{expected_error_message_pattern}' not found in logs."

View File

@@ -166,13 +166,13 @@ class FullRefreshTest(TestCase):
slice_datetime = start_date + slice_range
http_mocker.get(
_application_fees_request().with_created_gte(start_date).with_created_lte(slice_datetime).with_limit(100).build(),
_application_fees_request().with_created_gte(slice_datetime).with_created_lte(_NOW).with_limit(100).build(),
_application_fees_response().build(),
)
http_mocker.get(
_application_fees_request()
.with_created_gte(slice_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_created_gte(start_date)
.with_created_lte(slice_datetime - _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_limit(100)
.build(),
_application_fees_response().build(),
@@ -180,10 +180,8 @@ class FullRefreshTest(TestCase):
self._read(_config().with_start_date(start_date).with_slice_range_in_days(slice_range.days))
# request matched http_mocker
@HttpMocker()
def test_given_http_status_400_when_read_then_stream_did_not_run(self, http_mocker: HttpMocker) -> None:
def test_given_http_status_400_when_read_then_stream_incomplete(self, http_mocker: HttpMocker) -> None:
http_mocker.get(
_application_fees_request().with_any_query_params().build(),
a_response_with_status(400),
@@ -250,21 +248,16 @@ class IncrementalTest(TestCase):
output = self._read(_config().with_start_date(_A_START_DATE), _NO_STATE)
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=cursor_value)
assert most_recent_state.stream_state.updated == str(cursor_value)
@HttpMocker()
def test_given_state_when_read_then_query_events_using_types_and_state_value_plus_1(self, http_mocker: HttpMocker) -> None:
start_date = _NOW - timedelta(days=40)
state_datetime = _NOW - timedelta(days=5)
cursor_value = int(state_datetime.timestamp()) + 1
cursor_value = int(state_datetime.timestamp())
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response()
.with_record(_an_event().with_cursor(cursor_value).with_field(_DATA_FIELD, _an_application_fee().build()))
.build(),
@@ -277,18 +270,13 @@ class IncrementalTest(TestCase):
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=cursor_value)
assert most_recent_state.stream_state.updated == str(cursor_value)
@HttpMocker()
def test_given_state_and_pagination_when_read_then_return_records(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response()
.with_pagination()
.with_record(_an_event().with_id("last_record_id_from_first_page").with_field(_DATA_FIELD, _an_application_fee().build()))
@@ -297,7 +285,7 @@ class IncrementalTest(TestCase):
http_mocker.get(
_events_request()
.with_starting_after("last_record_id_from_first_page")
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_gte(state_datetime)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
@@ -316,24 +304,19 @@ class IncrementalTest(TestCase):
def test_given_state_and_small_slice_range_when_read_then_perform_multiple_queries(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
slice_range = timedelta(days=3)
slice_datetime = state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES + slice_range
slice_datetime = state_datetime + slice_range
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(slice_datetime)
.with_created_gte(state_datetime)
.with_created_lte(slice_datetime - _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_response().with_record(self._an_application_fee_event()).build(),
)
http_mocker.get(
_events_request()
.with_created_gte(slice_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(slice_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response().with_record(self._an_application_fee_event()).with_record(self._an_application_fee_event()).build(),
)

View File

@@ -164,15 +164,15 @@ class FullRefreshTest(TestCase):
slice_datetime = start_date + slice_range
http_mocker.get(
_authorizations_request().with_created_gte(start_date).with_created_lte(slice_datetime).with_limit(100).build(),
_authorizations_request()
.with_created_gte(start_date)
.with_created_lte(slice_datetime - _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_limit(100)
.build(),
_authorizations_response().build(),
)
http_mocker.get(
_authorizations_request()
.with_created_gte(slice_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.build(),
_authorizations_request().with_created_gte(slice_datetime).with_created_lte(_NOW).with_limit(100).build(),
_authorizations_response().build(),
)
@@ -245,7 +245,7 @@ class IncrementalTest(TestCase):
output = self._read(_config().with_start_date(_A_START_DATE), _NO_STATE)
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=cursor_value)
assert most_recent_state.stream_state.updated == str(cursor_value)
@HttpMocker()
def test_given_state_when_read_then_query_events_using_types_and_state_value_plus_1(self, http_mocker: HttpMocker) -> None:
@@ -254,12 +254,7 @@ class IncrementalTest(TestCase):
cursor_value = int(state_datetime.timestamp()) + 1
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response()
.with_record(_an_event().with_cursor(cursor_value).with_field(_DATA_FIELD, _an_authorization().build()))
.build(),
@@ -272,18 +267,13 @@ class IncrementalTest(TestCase):
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=cursor_value)
assert most_recent_state.stream_state.updated == str(cursor_value)
@HttpMocker()
def test_given_state_and_pagination_when_read_then_return_records(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response()
.with_pagination()
.with_record(_an_event().with_id("last_record_id_from_first_page").with_field(_DATA_FIELD, _an_authorization().build()))
@@ -292,7 +282,7 @@ class IncrementalTest(TestCase):
http_mocker.get(
_events_request()
.with_starting_after("last_record_id_from_first_page")
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_gte(state_datetime)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
@@ -311,24 +301,19 @@ class IncrementalTest(TestCase):
def test_given_state_and_small_slice_range_when_read_then_perform_multiple_queries(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
slice_range = timedelta(days=3)
slice_datetime = state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES + slice_range
slice_datetime = state_datetime + slice_range
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(slice_datetime)
.with_created_gte(state_datetime)
.with_created_lte(slice_datetime - _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_response().with_record(self._an_authorization_event()).build(),
)
http_mocker.get(
_events_request()
.with_created_gte(slice_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(slice_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response().with_record(self._an_authorization_event()).with_record(self._an_authorization_event()).build(),
)

View File

@@ -164,15 +164,15 @@ class FullRefreshTest(TestCase):
slice_datetime = start_date + slice_range
http_mocker.get(
_cards_request().with_created_gte(start_date).with_created_lte(slice_datetime).with_limit(100).build(),
_cards_request()
.with_created_gte(start_date)
.with_created_lte(slice_datetime - _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_limit(100)
.build(),
_cards_response().build(),
)
http_mocker.get(
_cards_request()
.with_created_gte(slice_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.build(),
_cards_request().with_created_gte(slice_datetime).with_created_lte(_NOW).with_limit(100).build(),
_cards_response().build(),
)
@@ -189,6 +189,7 @@ class FullRefreshTest(TestCase):
output = self._read(_config())
assert_stream_did_not_run(output, _STREAM_NAME, "Your account is not set up to use Issuing")
@HttpMocker()
@HttpMocker()
def test_given_http_status_401_when_read_then_config_error(self, http_mocker: HttpMocker) -> None:
http_mocker.get(
@@ -245,7 +246,7 @@ class IncrementalTest(TestCase):
output = self._read(_config().with_start_date(_A_START_DATE), _NO_STATE)
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=cursor_value)
assert most_recent_state.stream_state.updated == str(cursor_value)
@HttpMocker()
def test_given_state_when_read_then_query_events_using_types_and_state_value_plus_1(self, http_mocker: HttpMocker) -> None:
@@ -254,12 +255,7 @@ class IncrementalTest(TestCase):
cursor_value = int(state_datetime.timestamp()) + 1
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response().with_record(_an_event().with_cursor(cursor_value).with_field(_DATA_FIELD, _a_card().build())).build(),
)
@@ -270,18 +266,13 @@ class IncrementalTest(TestCase):
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=cursor_value)
assert most_recent_state.stream_state.updated == str(cursor_value)
@HttpMocker()
def test_given_state_and_pagination_when_read_then_return_records(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response()
.with_pagination()
.with_record(_an_event().with_id("last_record_id_from_first_page").with_field(_DATA_FIELD, _a_card().build()))
@@ -290,7 +281,7 @@ class IncrementalTest(TestCase):
http_mocker.get(
_events_request()
.with_starting_after("last_record_id_from_first_page")
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_gte(state_datetime)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
@@ -309,24 +300,19 @@ class IncrementalTest(TestCase):
def test_given_state_and_small_slice_range_when_read_then_perform_multiple_queries(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
slice_range = timedelta(days=3)
slice_datetime = state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES + slice_range
slice_datetime = state_datetime + slice_range
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(slice_datetime)
.with_created_gte(state_datetime)
.with_created_lte(slice_datetime - _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_response().with_record(self._a_card_event()).build(),
)
http_mocker.get(
_events_request()
.with_created_gte(slice_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(slice_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response().with_record(self._a_card_event()).with_record(self._a_card_event()).build(),
)

View File

@@ -1,4 +1,3 @@
#
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
#
@@ -209,7 +208,7 @@ class IncrementalTest(TestCase):
output = self._read(_config().with_start_date(_A_START_DATE), _NO_STATE)
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=cursor_value)
assert most_recent_state.stream_state.updated == str(cursor_value)
@HttpMocker()
def test_given_state_when_read_then_query_events_using_types_and_state_value_plus_1(self, http_mocker: HttpMocker) -> None:
@@ -218,12 +217,7 @@ class IncrementalTest(TestCase):
cursor_value = int(state_datetime.timestamp()) + 1
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response()
.with_record(_an_event().with_cursor(cursor_value).with_field(_DATA_FIELD, _an_early_fraud_warning().build()))
.build(),
@@ -236,18 +230,13 @@ class IncrementalTest(TestCase):
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=cursor_value)
assert most_recent_state.stream_state.updated == str(cursor_value)
@HttpMocker()
def test_given_state_and_pagination_when_read_then_return_records(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response()
.with_pagination()
.with_record(_an_event().with_id("last_record_id_from_first_page").with_field(_DATA_FIELD, _an_early_fraud_warning().build()))
@@ -256,7 +245,7 @@ class IncrementalTest(TestCase):
http_mocker.get(
_events_request()
.with_starting_after("last_record_id_from_first_page")
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_gte(state_datetime)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
@@ -275,24 +264,19 @@ class IncrementalTest(TestCase):
def test_given_state_and_small_slice_range_when_read_then_perform_multiple_queries(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
slice_range = timedelta(days=3)
slice_datetime = state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES + slice_range
slice_datetime = state_datetime + slice_range
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(slice_datetime)
.with_created_gte(state_datetime)
.with_created_lte(slice_datetime - _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_response().with_record(self._an_early_fraud_warning_event()).build(),
)
http_mocker.get(
_events_request()
.with_created_gte(slice_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(slice_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response().with_record(self._an_early_fraud_warning_event()).with_record(self._an_early_fraud_warning_event()).build(),
)

View File

@@ -26,7 +26,7 @@ from airbyte_cdk.test.mock_http.response_builder import (
)
from airbyte_cdk.test.state_builder import StateBuilder
from integration.config import ConfigBuilder
from integration.helpers import assert_stream_incomplete
from integration.helpers import assert_stream_did_not_run
from integration.pagination import StripePaginationStrategy
from integration.request_builder import StripeRequestBuilder
from integration.response_builder import a_response_with_status
@@ -184,7 +184,7 @@ class FullRefreshTest(TestCase):
a_response_with_status(400),
)
output = self._read(_config())
assert_stream_incomplete(output, _STREAM_NAME, "Your account is not set up to use Issuing")
assert_stream_did_not_run(output, _STREAM_NAME, "Your account is not set up to use Issuing")
@HttpMocker()
def test_given_http_status_401_when_read_then_stream_is_incomplete(self, http_mocker: HttpMocker) -> None:

View File

@@ -208,7 +208,7 @@ class IncrementalTest(TestCase):
output = self._read(_config().with_start_date(_A_START_DATE), _NO_STATE)
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=int(_NOW.timestamp()))
assert int(most_recent_state.stream_state.updated) == int(_NOW.timestamp())
@HttpMocker()
def test_given_state_when_read_then_query_events_using_types_and_state_value_plus_1(self, http_mocker: HttpMocker) -> None:
@@ -217,12 +217,7 @@ class IncrementalTest(TestCase):
cursor_value = int(state_datetime.timestamp()) + 1
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response()
.with_record(_an_event().with_cursor(cursor_value).with_field(_DATA_FIELD, _an_external_bank_account().build()))
.build(),
@@ -235,7 +230,7 @@ class IncrementalTest(TestCase):
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=cursor_value)
assert most_recent_state.stream_state.updated == str(cursor_value)
@HttpMocker()
def test_given_object_is_not_back_account_when_read_then_filter_out(self, http_mocker: HttpMocker) -> None:
@@ -258,12 +253,7 @@ class IncrementalTest(TestCase):
def test_given_state_and_pagination_when_read_then_return_records(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response()
.with_pagination()
.with_record(_an_event().with_id("last_record_id_from_first_page").with_field(_DATA_FIELD, _an_external_bank_account().build()))
@@ -272,7 +262,7 @@ class IncrementalTest(TestCase):
http_mocker.get(
_events_request()
.with_starting_after("last_record_id_from_first_page")
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_gte(state_datetime)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
@@ -291,24 +281,19 @@ class IncrementalTest(TestCase):
def test_given_state_and_small_slice_range_when_read_then_perform_multiple_queries(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
slice_range = timedelta(days=3)
slice_datetime = state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES + slice_range
slice_datetime = state_datetime + slice_range
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(slice_datetime)
.with_created_gte(state_datetime)
.with_created_lte(slice_datetime - _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_response().with_record(self._an_external_account_event()).build(),
)
http_mocker.get(
_events_request()
.with_created_gte(slice_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(slice_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response().with_record(self._an_external_account_event()).with_record(self._an_external_account_event()).build(),
)

View File

@@ -146,7 +146,6 @@ class FullRefreshTest(TestCase):
)
output = self._read(_config().with_start_date(_A_START_DATE).with_lookback_window_in_days(10))
assert output.records[0].record.data["updated"] == int(_NOW.timestamp())
@HttpMocker()
@@ -213,7 +212,7 @@ class IncrementalTest(TestCase):
output = self._read(_config().with_start_date(_A_START_DATE), _NO_STATE)
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=int(_NOW.timestamp()))
assert int(most_recent_state.stream_state.updated) == int(_NOW.timestamp())
@HttpMocker()
def test_given_state_when_read_then_query_events_using_types_and_state_value_plus_1(self, http_mocker: HttpMocker) -> None:
@@ -222,12 +221,7 @@ class IncrementalTest(TestCase):
cursor_value = int(state_datetime.timestamp()) + 1
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response()
.with_record(_an_event().with_cursor(cursor_value).with_field(_DATA_FIELD, _an_external_account_card().build()))
.build(),
@@ -240,7 +234,7 @@ class IncrementalTest(TestCase):
most_recent_state = output.most_recent_state
assert most_recent_state.stream_descriptor == StreamDescriptor(name=_STREAM_NAME)
assert most_recent_state.stream_state == AirbyteStateBlob(updated=cursor_value)
assert most_recent_state.stream_state.updated == str(cursor_value)
@HttpMocker()
def test_given_object_is_not_back_account_when_read_then_filter_out(self, http_mocker: HttpMocker) -> None:
@@ -263,12 +257,7 @@ class IncrementalTest(TestCase):
def test_given_state_and_pagination_when_read_then_return_records(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(state_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response()
.with_pagination()
.with_record(_an_event().with_id("last_record_id_from_first_page").with_field(_DATA_FIELD, _an_external_account_card().build()))
@@ -277,7 +266,7 @@ class IncrementalTest(TestCase):
http_mocker.get(
_events_request()
.with_starting_after("last_record_id_from_first_page")
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_gte(state_datetime)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
@@ -296,24 +285,19 @@ class IncrementalTest(TestCase):
def test_given_state_and_small_slice_range_when_read_then_perform_multiple_queries(self, http_mocker: HttpMocker) -> None:
state_datetime = _NOW - timedelta(days=5)
slice_range = timedelta(days=3)
slice_datetime = state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES + slice_range
slice_datetime = state_datetime + slice_range
http_mocker.get(
_events_request()
.with_created_gte(state_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(slice_datetime)
.with_created_gte(state_datetime)
.with_created_lte(slice_datetime - _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_response().with_record(self._an_external_account_event()).build(),
)
http_mocker.get(
_events_request()
.with_created_gte(slice_datetime + _AVOIDING_INCLUSIVE_BOUNDARIES)
.with_created_lte(_NOW)
.with_limit(100)
.with_types(_EVENT_TYPES)
.build(),
_events_request().with_created_gte(slice_datetime).with_created_lte(_NOW).with_limit(100).with_types(_EVENT_TYPES).build(),
_events_response().with_record(self._an_external_account_event()).with_record(self._an_external_account_event()).build(),
)