def do_test(self, conn_id): """Test we get a lot of data back based on the start date configured in base""" # Select all streams and all fields within streams found_catalogs = menagerie.get_catalogs(conn_id) incremental_streams = { key for key, value in self.expected_replication_method().items() if value == self.INCREMENTAL } # IF THERE ARE STREAMS THAT SHOULD NOT BE TESTED # REPLACE THE EMPTY SET BELOW WITH THOSE STREAMS untested_streams = self.child_streams().union( {'disputes', 'events', 'transfers', 'payout_transactions'}) our_catalogs = get_catalogs( conn_id, incremental_streams.difference(untested_streams)) self.select_all_streams_and_fields(conn_id, our_catalogs, select_all_fields=True) # Create a record for each stream under test prior to the first sync new_objects = { stream: create_object(stream) for stream in incremental_streams.difference(untested_streams) } # Run a sync job using orchestrator first_sync_record_count = self.run_sync(conn_id) first_total_records = reduce(lambda a, b: a + b, first_sync_record_count.values()) # Count actual rows synced first_sync_records = runner.get_records_from_target_output() # set the start date for a new connection based off bookmarks largest value first_max_bookmarks = self.max_bookmarks_by_stream(first_sync_records) bookmark_list = [ next(iter(book.values())) for stream, book in first_max_bookmarks.items() ] bookmark_dates = [] for bookmark in bookmark_list: try: bookmark_dates.append(parse(bookmark)) except (ValueError, OverflowError, TypeError): pass if not bookmark_dates: # THERE WERE NO BOOKMARKS THAT ARE DATES. # REMOVE CODE TO FIND A START DATE AND ENTER ONE MANUALLY raise ValueError # largest_bookmark = reduce(lambda a, b: a if a > b else b, bookmark_dates) # self.start_date = self.local_to_utc(largest_bookmark).strftime(self.START_DATE_FORMAT) self.start_date = dt.strftime(dt.today() - timedelta(days=1), self.START_DATE_FORMAT) # create a new connection with the new start_date conn_id = self.create_connection(original_properties=False) # Select all streams and all fields within streams found_catalogs = menagerie.get_catalogs(conn_id) our_catalogs = [ catalog for catalog in found_catalogs if catalog.get('tap_stream_id') in incremental_streams.difference( untested_streams) ] self.select_all_streams_and_fields(conn_id, our_catalogs, select_all_fields=True) # TODO remove the updates, this is unnecessary. Verify with Harvest # Update a record for each stream under test prior to the 2nd sync first_sync_created, _ = self.split_records_into_created_and_updated( first_sync_records) updated = {} # holds id for updated objects in each stream for stream in new_objects: # There needs to be some test data for each stream, otherwise this will break record = first_sync_created[stream]["messages"][0]["data"] update_object(stream, record["id"]) updated[stream] = record["id"] # Run a sync job using orchestrator second_sync_record_count = self.run_sync(conn_id) # tap-stripe uses events for updates, so these need filtered to validate bookmark second_sync_records = runner.get_records_from_target_output() second_sync_created, second_sync_updated = self.split_records_into_created_and_updated( second_sync_records) second_total_records = reduce(lambda a, b: a + b, second_sync_record_count.values(), 0) # Only examine bookmarks for "created" objects, not updates second_min_bookmarks = self.min_bookmarks_by_stream( second_sync_created) # verify that at least one record synced and less records synced than the 1st connection self.assertGreater(second_total_records, 0) self.assertLess(first_total_records, second_total_records) # validate that all newly created records are greater than the start_date for stream in incremental_streams.difference(untested_streams): with self.subTest(stream=stream): # verify that each stream has less records in the first sync than the second self.assertGreater( second_sync_record_count.get(stream, 0), first_sync_record_count.get(stream, 0), msg="first had more records, start_date usage not verified" ) # verify all data from 2nd sync >= start_date target_mark = second_min_bookmarks.get(stream, {"mark": None}) target_value = next(iter( target_mark.values())) # there should be only one if target_value: # it's okay if there isn't target data for a stream try: target_value = self.local_to_utc(parse(target_value)) expected_value = self.local_to_utc( parse(self.start_date)) # verify that the minimum bookmark sent to the target for the second sync # is greater than or equal to the start date self.assertGreaterEqual(target_value, expected_value) except (OverflowError, ValueError, TypeError): print("bookmarks cannot be converted to dates, " "can't test start_date for {}".format(stream)) if stream in updated: delete_object(stream, updated[stream])
def test_run(self): """ Verify that for each stream you can do a sync which records bookmarks. That the bookmark is the maximum value sent to the target for the replication key. That a second sync respects the bookmark All data of the second sync is >= the bookmark from the first sync The number of records in the 2nd sync is less then the first (This assumes that new data added to the stream is done at a rate slow enough that you haven't doubled the amount of data from the start date to the first sync between the first sync and second sync run in this test) Verify that only data for incremental streams is sent to the target PREREQUISITE For EACH stream that is incrementally replicated there are multiple rows of data with different values for the replication key """ conn_id = self.create_connection() expected_records = {stream: [] for stream in self.streams_to_create} # Create 3 records for each stream total for _ in range(2): for stream in self.streams_to_create: self.new_objects[stream].append(create_object(stream)) # Turn on tracking of hidden object creates in util_stripe # so that we can properly assert on 2nd sync records and expectations activate_tracking() # Create the 3rd record now that we are recording all created objects for stream in self.streams_to_create: self.new_objects[stream].append(create_object(stream)) expected_records[stream].append( {"id": self.new_objects[stream][-1]['id']}) # Select all streams and no fields within streams found_catalogs = menagerie.get_catalogs(conn_id) incremental_streams = { key for key, value in self.expected_replication_method().items() if value == self.INCREMENTAL } # IF THERE ARE STREAMS THAT SHOULD NOT BE TESTED # REPLACE THE EMPTY SET BELOW WITH THOSE STREAMS untested_streams = self.child_streams().union({ 'transfers', 'payout_transactions', # BUG see create test 'balance_transactions', # join stream, can't be updated 'disputes', }) our_catalogs = [ catalog for catalog in found_catalogs if catalog.get('tap_stream_id') in incremental_streams.difference( untested_streams) ] self.select_all_streams_and_fields(conn_id, our_catalogs, select_all_fields=True) # Run a sync job using orchestrator and track the sync's time window first_sync_start = self.local_to_utc(dt.utcnow()) first_sync_record_count = self.run_sync(conn_id) first_sync_end = self.local_to_utc(dt.utcnow()) # verify that the sync only sent records to the target for selected streams (catalogs) self.assertEqual(set(first_sync_record_count.keys()), incremental_streams.difference(untested_streams)) first_sync_state = menagerie.get_state(conn_id) # Get data about actual rows synced first_sync_records = runner.get_records_from_target_output() first_max_bookmarks = self.max_bookmarks_by_stream(first_sync_records) first_max_events = self.max_events_bookmarks_by_stream( first_sync_records) first_min_bookmarks = self.min_bookmarks_by_stream(first_sync_records) # Update one record from each stream prior to 2nd sync first_sync_created, _ = self.split_records_into_created_and_updated( first_sync_records) updated_objects = {stream: [] for stream in self.streams_to_create } # TODO Delete if never used for stream in self.streams_to_create: # There needs to be some test data for each stream, otherwise this will break record = expected_records[stream][0] # record = first_sync_created[stream]["messages"][0]["data"] updated_objects[stream].append(update_object(stream, record["id"])) expected_records[stream].append( {"id": updated_objects[stream][-1]['id']}) # Ensure different times between udpates and inserts sleep(2) # Insert (create) one record for each stream prior to 2nd sync for stream in self.streams_to_create: self.new_objects[stream].append(create_object(stream)) expected_records[stream].append( {"id": self.new_objects[stream][-1]['id']}) # Run a second sync job using orchestrator second_sync_start = self.local_to_utc(dt.utcnow()) second_sync_record_count = self.run_sync(conn_id) second_sync_end = self.local_to_utc(dt.utcnow()) second_sync_state = menagerie.get_state(conn_id) # Get data about rows synced second_sync_records = runner.get_records_from_target_output() second_sync_created, second_sync_updated = self.split_records_into_created_and_updated( second_sync_records) second_min_bookmarks = self.min_bookmarks_by_stream( second_sync_created) second_max_bookmarks = self.max_bookmarks_by_stream( second_sync_records) second_max_events = self.max_events_bookmarks_by_stream( second_sync_records) self.assertTrue(second_max_events) # Define bookmark keys events_bookmark_key = "updates_created" # secondary bookmark from the events stream target_events_bookmark_key = "updated" # the bookmark key for events # Adjust expectations to account for invoices created by the invoice_line_items calls for e in expected_records['invoice_line_items']: expected_records['invoices'].append(e) # THIS MAKES AN ASSUMPTION THAT CHILD STREAMS DO NOT HAVE BOOKMARKS. # ADJUST IF NECESSARY for stream in incremental_streams.difference(untested_streams): with self.subTest(stream=stream): # Get bookmark values from state and target data. # Recall that there are two bookmarks for every object stream: an events-based # bookmark for updates, and a standard repliction key-based bookmark for creates. stream_bookmark_key = self.expected_replication_keys().get( stream, set()) assert len( stream_bookmark_key ) == 1 # There shouldn't be a compound replication key stream_bookmark_key = stream_bookmark_key.pop() state_value = first_sync_state.get("bookmarks", {}).get( stream + "", {}).get(stream_bookmark_key) target_value = first_max_bookmarks.get( stream + "", {}).get(stream_bookmark_key) target_min_value = first_min_bookmarks.get( stream + "", {}).get(stream_bookmark_key) # the events stream should get an empty set as events_events does not exist state_value_events = first_sync_state.get("bookmarks", {}).get( stream + "_events", {}).get(events_bookmark_key) target_events_value = first_max_events.get( stream + "_events", {}).get(target_events_bookmark_key) try: # attempt to parse the bookmark as a date state_value = self.parse_bookmark_to_date(state_value) state_value_events = self.parse_bookmark_to_date( state_value_events) target_value = self.parse_bookmark_to_date(target_value) target_events_value = self.parse_bookmark_to_date( target_events_value) target_min_value = self.parse_bookmark_to_date( target_min_value) except (OverflowError, ValueError, TypeError): print( "bookmarks cannot be converted to dates, comparing values directly" ) ########################################################################## ### 1st Sync Tests ########################################################################## if stream != 'payout_transactions': # verify that there is data with different bookmark values - setup necessary self.assertGreaterEqual( target_value, target_min_value, msg="Data isn't set up to be able to test bookmarks") # TODO run test with our time = current_time + 1 hour and see if the events stream is able to cover for any potential # issues with our time window discrepancy capturing updates/creates # verify that 'created' state is saved for the first sync in the appropriate # time window, between (inclusive) the target value (max bookmark) for this stream and # the first sync end time self.assertGreaterEqual( state_value, target_value, msg="The bookmark value isn't correct based on target data" ) self.assertLessEqual( state_value, first_sync_end, msg="The bookmark value isn't correct based on " "the start sync") if state_value_events != set() and stream != 'events': if stream not in { 'invoices', 'customers' }: # BUG https://stitchdata.atlassian.net/browse/SUP-1320 # verify that 'updates' state matches the target (max bookmark) self.assertEqual( state_value_events, target_events_value, msg="The bookmark value isn't correct " "based on target data") # NOTE: This assertion is no longer valid. Some streams will create records for other streams # verify the last record has the max bookmark in the target (this should never fail) # last_created = self.parse_bookmark_to_date(self.new_objects[stream][2]['created']) # self.assertEqual(target_value, last_created, # msg="The last created record for the first sync should " # "be sthe max bookmark in the target and is not.") else: # We need to ensure no assertions are missed for object streams self.assertEqual( stream, 'events', msg= "An object stream is missing it's object_events bookmark" ) ########################################################################## ### 2nd Sync Tests ########################################################################## prev_target_value = target_value prev_target_events_value = target_events_value target_value = second_max_bookmarks.get( stream + "", {}).get(stream_bookmark_key) target_min_value = second_min_bookmarks.get( stream + "", {}).get(stream_bookmark_key) final_state_value = second_sync_state.get("bookmarks", {}).get( stream + "", {}).get(stream_bookmark_key) final_state_value_events = second_sync_state.get( "bookmarks", {}).get(stream + "_events", {}).get(events_bookmark_key) target_events_value = second_max_events.get( stream + "_events", {}).get(target_events_bookmark_key) try: final_state_value = self.parse_bookmark_to_date( final_state_value) final_state_value_events = self.parse_bookmark_to_date( final_state_value_events) target_value = self.parse_bookmark_to_date(target_value) target_events_value = self.parse_bookmark_to_date( target_events_value) target_min_value = self.parse_bookmark_to_date( target_min_value) except (OverflowError, ValueError, TypeError): print( "bookmarks cannot be converted to dates, comparing values directly" ) # verify that the 2nd sync gets more than zero records self.assertGreater(second_sync_record_count.get(stream, 0), 0, msg="second syc didn't have any records") # verify that you get less data the 2nd time around self.assertGreater( first_sync_record_count.get(stream, 0), second_sync_record_count.get(stream, 0), msg= "second syc didn't have less records, bookmark usage not verified" ) # verify 'created' state is at or after the target (max bookmark) and before # or equal to the second sync end self.assertGreaterEqual( final_state_value, target_value, msg="The bookmark value isn't correct based on target data" ) self.assertLessEqual( final_state_value, second_sync_end, msg="The bookmark value isn't correct based on " "the end sync") if final_state_value_events != set() and stream != 'events': if stream not in { 'invoices', 'customers' }: # BUG https://stitchdata.atlassian.net/browse/SUP-1320 # verify that 'updates' state matches the target (max bookmark) self.assertEqual( final_state_value_events, target_events_value, msg= "The bookmark value isn't correct based on target data" ) else: # We need to ensure no assertions are missed for object streams self.assertEqual( stream, 'events', msg= "An object stream is missing it's object_events bookmark" ) # verify that the second sync state is greater than or equal to the minimum target value self.assertGreaterEqual(target_value, target_min_value) # Verify that the minimum bookmark in the second sync is greater than or equal to state value from the first sync. self.assertGreaterEqual( target_min_value, prev_target_value, msg= "Minimum bookmark in 2nd sync does not match 1st sync state value" ) if stream != 'events': # verify that 2nd sync captures the expected records and nothing else expected = expected_records[stream] expected_set = set() for record in expected: # creating a set of expected ids for current stream for key, val in record.items(): if 'id' == key: expected_set.add(val) actual = [ item["data"] for item in second_sync_records.get( stream, { 'messages': [] }).get('messages') ] actual_set = set() for record in actual: for key, val in record.items( ): # creating set of actual ids if 'id' == key: actual_set.add(val) if not expected_set.issubset( actual_set ): # output with print in case next assertion fails print(stream + "\nE: " + str(expected_set) + "\nA:" + str(actual_set)) self.assertTrue( expected_set.issubset(actual_set), msg= "We expected records to be sent to the target and they were not" ) # To create an object, some streams require the creation of objects from different streams # As a result we create more than what is obvious in the test to capture all streams # The 'hidden_creates' set tracks those extra records, in order to verify we aren't # getting more records than expected hidden_creates = get_hidden_objects(stream) if stream in { 'plans', # BUG https://stitchdata.atlassian.net/browse/SUP-1303 'subscriptions', # BUG https://stitchdata.atlassian.net/browse/SUP-1304 'invoice_items', # BUG https://stitchdata.atlassian.net/browse/SUP-1316 'customers' }: # BUG https://stitchdata.atlassian.net/browse/SUP-1322 continue # if stream == 'invoice_items': # UNCOMMENT WHEN SUP-1316 is addressed # # verify everything we created was sent # # NOTE: we annot account for all hidden records created in the bg for this stream # self.assertEqual(expected_set.union(hidden_creates) - actual_set, set(), # msg="Failed to capture all records that were explicitly created.") # continue # Many records are created implicitly and there are too many to track. # For now we will just log if not actual_set.difference(expected_set).issubset( hidden_creates): logging.warning( "Some extra records sent to the target that were not in expected." "Unexpected records: {}".format( actual_set.difference(expected_set).difference( hidden_creates)))
def test_run(self): """ Verify for each stream that you can do a sync which records bookmarks. Verify that the bookmark is the max value sent to the target for the `date` PK field Verify that the 2nd sync respects the bookmark Verify that all data of the 2nd sync is >= the bookmark from the first sync Verify that the number of records in the 2nd sync is less then the first Verify inclusivivity of bookmarks PREREQUISITE For EACH stream that is incrementally replicated there are multiple rows of data with different values for the replication key """ untested_streams = self.child_streams().union({ 'transfers', 'payout_transactions', # BUG see create test 'balance_transactions', # join stream, can't be updated 'disputes', }) cannot_update_streams = { 'invoice_line_items', # updates not available via api } # Ensure tested streams have existing records expected_records_first_sync = {stream: [] for stream in self.streams_to_create} for _ in range(2): # create 3 records for each stream but only expect the 3rd for stream in self.streams_to_create: self.new_objects[stream].append(create_object(stream)) for stream in self.streams_to_create: self.new_objects[stream].append(create_object(stream)) expected_records_first_sync[stream].append({"id": self.new_objects[stream][-1]['id']}) self.START_DATE = self.get_properties().get('start_date') # Instantiate connection with default start conn_id = connections.ensure_connection(self) # run in check mode found_catalogs = self.run_and_verify_check_mode(conn_id) # Select all testable streams and all fields within streams streams_to_select = self.expected_incremental_streams().difference(untested_streams) our_catalogs = [catalog for catalog in found_catalogs if catalog.get('tap_stream_id') in streams_to_select] self.select_all_streams_and_fields(conn_id, our_catalogs, select_all_fields=True) # Run a sync job using orchestrator first_sync_start = self.local_to_utc(dt.utcnow()) first_sync_record_count = self.run_and_verify_sync(conn_id) first_sync_end = self.local_to_utc(dt.utcnow()) # verify that the sync only sent records to the target for selected streams (catalogs) self.assertEqual( streams_to_select, set(first_sync_record_count.keys()), msg="Expected only testable streams to be replicated: {}".format(first_sync_record_count) ) first_sync_state = menagerie.get_state(conn_id) # Get the set of records from a first sync first_sync_records = runner.get_records_from_target_output() # Add data before next sync via insert and update, and set expectations created_records = {x: [] for x in self.expected_streams()} updated_records = {x: [] for x in self.expected_streams()} expected_records_second_sync = {x: [] for x in self.expected_streams()} # Update one record from each stream prior to 2nd sync first_sync_created, _ = self.split_records_into_created_and_updated(first_sync_records) for stream in self.streams_to_create.difference(cannot_update_streams): # There needs to be some test data for each stream, otherwise this will break record = expected_records_first_sync[stream][0] updated_record = update_object(stream, record["id"]) updated_records[stream].append(updated_record) expected_records_second_sync[stream].append({"id": updated_record['id']}) # Ensure different times between udpates and inserts sleep(2) # Insert (create) one record for each stream prior to 2nd sync for stream in self.streams_to_create: created_record = create_object(stream) self.new_objects[stream].append(created_record) created_records[stream].append(created_record) expected_records_second_sync[stream].append({"id": created_record['id']}) # ensure validity of expected_records_second_sync for stream in self.streams_to_create: if stream in self.expected_incremental_streams(): if stream in cannot_update_streams: # Some streams will have only 1 record from the Insert self.assertEqual(1, len(expected_records_second_sync.get(stream)), msg="Expectations are invalid for incremental stream {}".format(stream) ) continue # Most streams will have 2 records from the Update and Insert self.assertEqual(2, len(expected_records_second_sync.get(stream)), msg="Expectations are invalid for incremental stream {}".format(stream) ) elif stream in self.expected_full_table_streams(): self.assertEqual( len(expected_records_second_sync.get(stream)), len(expected_records_first_sync.get(stream)) + len(created_records[stream]), msg="Expectations are invalid for full table stream {}".format(stream) ) # created_records[stream] = self.records_data_type_conversions(created_records.get(stream)) # updated_records[stream] = self.records_data_type_conversions(updated_records.get(stream)) # Run a second sync job using orchestrator second_sync_start = self.local_to_utc(dt.utcnow()) second_sync_record_count = self.run_and_verify_sync(conn_id) second_sync_end = self.local_to_utc(dt.utcnow()) second_sync_state = menagerie.get_state(conn_id) # Get the set of records from a second sync second_sync_records = runner.get_records_from_target_output() second_sync_created, second_sync_updated = self.split_records_into_created_and_updated(second_sync_records) # Loop first_sync_records and compare against second_sync_records for stream in self.streams_to_create.difference(untested_streams): with self.subTest(stream=stream): second_sync_data = [record.get("data") for record in second_sync_records.get(stream, {}).get("messages", [])] stream_replication_keys = self.expected_replication_keys() stream_primary_keys = self.expected_primary_keys() # TESTING INCREMENTAL STREAMS if stream in self.expected_incremental_streams(): replication_keys = stream_replication_keys.get(stream) # Verify both syncs write / keep the same bookmark keys self.assertEqual(set(first_sync_state.get('bookmarks', {}).keys()), set(second_sync_state.get('bookmarks', {}).keys())) # verify that there is more than 1 record of data - setup necessary self.assertGreater(first_sync_record_count.get(stream, 0), 1, msg="Data isn't set up to be able to test full sync") # verify that you get less data on the 2nd sync self.assertGreater( first_sync_record_count.get(stream, 0), second_sync_record_count.get(stream, 0), msg="first sync didn't have more records, bookmark usage not verified") if stream in self.streams_to_create: for replication_key in replication_keys: updates_replication_key = "updates_created" updates_stream = stream + "_events" # Verify second sync's bookmarks move past the first sync's self.assertGreater( second_sync_state.get('bookmarks', {updates_stream: {}}).get( updates_stream, {replication_key: -1}).get(updates_replication_key), first_sync_state.get('bookmarks', {updates_stream: {}}).get( updates_stream, {updates_replication_key: -1}).get(updates_replication_key) ) # Verify that all data of the 2nd sync is >= the bookmark from the first sync first_sync_bookmark = dt.fromtimestamp( first_sync_state.get('bookmarks').get(updates_stream).get(updates_replication_key) ) for record in second_sync_data: date_value = record["updated"] self.assertGreaterEqual(date_value, dt.strftime(first_sync_bookmark, self.COMPARISON_FORMAT), msg="A 2nd sync record has a replication-key that is less than or equal to the 1st sync bookmark.") elif stream in self.expected_full_table_streams(): raise Exception("Expectations changed, but this test was not updated to reflect them.") # TESTING APPLICABLE TO ALL STREAMS # Verify that the expected records are replicated in the 2nd sync # For incremental streams we should see at least 2 records (a new record and an updated record) # but we may see more as the bookmmark is inclusive and there are hidden creates/updates due to # dependencies between streams. # For full table streams we should see 1 more record than the first sync expected_records = expected_records_second_sync.get(stream) primary_keys = stream_primary_keys.get(stream) updated_pk_values = {tuple([record.get(pk) for pk in primary_keys]) for record in updated_records[stream]} self.assertLessEqual( len(expected_records), len(second_sync_data), msg="Expected number of records are not less than or equal to actual for 2nd sync.\n" + "Expected: {}\nActual: {}".format(len(expected_records), len(second_sync_data)) ) if (len(second_sync_data) - len(expected_records)) > 0: logging.warn('Second sync replicated %s records more than our create and update for %s', len(second_sync_data), stream) if not primary_keys: raise NotImplementedError("PKs are needed for comparing records") # Verify that the inserted and updated records are replicated by the 2nd sync for expected_record in expected_records: expected_pk_value = expected_record.get('id') sync_pk_values = [sync_record.get('id') for sync_record in second_sync_data if sync_record.get('id') == expected_pk_value] self.assertTrue( len(sync_pk_values) > 0, msg="A record is missing from our sync: \nSTREAM: {}\tPK: {}".format(stream, expected_pk_value) ) self.assertIn(expected_pk_value, sync_pk_values) # Verify updated fields are replicated as expected for updated_record in updated_records[stream]: expected_updated_key = 'metadata' expected_updated_value_substring = 'bob' updated_pk_value = updated_record.get('id') sync_records_metadata = [sync_record.get('metadata') for sync_record in second_sync_data if sync_record.get('id') == updated_pk_value] self.assertTrue(len(sync_records_metadata) == 1) self.assertIn(expected_updated_value_substring, sync_records_metadata[0].get('test_value'))
def test_run(self): """ Verify that the sync only sent records to the target for selected streams Update metadata[test] with a random number for each stream with event updates Verify that the second sync includes at least one update for each stream Verify that the second sync includes less records than the first sync Verify that the updated metadata was picked up on the second sync PREREQUISITE For EACH stream that gets updates through events stream, there's at least 1 row of data """ conn_id = connections.ensure_connection(self) event_update_streams = { # "balance_transactions" # Cannot be directly updated "charges", "coupons", "customers", # "disputes", # Cannot create directly with api "invoice_items", # "invoice_line_items", # Can't be updated via api "invoices", # "payout_transactions", # See bug in create_test "payouts", "plans", "products", # "subscription_items", # BUG_9916 | https://jira.talendforge.org/browse/TDL-9916 "subscriptions", # "transfers", # Cannot be updated directly via api } found_catalogs = self.run_and_verify_check_mode(conn_id) our_catalogs = [catalog for catalog in found_catalogs if catalog.get('tap_stream_id') in event_update_streams] self.select_all_streams_and_fields( conn_id, our_catalogs, select_all_fields=True ) # Ensure each stream under test has data to start new_objects = { stream: create_object(stream) for stream in event_update_streams } # Some streams will be updated implicitly streams_to_update = event_update_streams.difference({ "invoice_line_items", "subscription_items", }) # Run a sync job using orchestrator first_sync_record_count = self.run_and_verify_sync(conn_id) # verify that the sync only sent records to the target for selected streams (catalogs) self.assertEqual(set(first_sync_record_count.keys()), event_update_streams) # Get the set of records from a first sync first_sync_records = runner.get_records_from_target_output() first_sync_created, _ = self.split_records_into_created_and_updated( first_sync_records ) updated = {} # holds id for updated objects in each stream for stream in streams_to_update: # There needs to be some test data for each stream, otherwise this will break self.assertGreater(len(first_sync_created[stream]["messages"]), 0, msg='We did not get any new records from ' 'the first sync for {}'.format(stream)) record = first_sync_created[stream]["messages"][0]["data"] # We need to make sure the data actually changes, otherwise no event update # will get created update_object(stream, record["id"]) updated[stream] = record["id"] # Run a second sync job using orchestrator second_sync_record_count = self.run_and_verify_sync(conn_id) # Get the set of records from a second sync second_sync_records = runner.get_records_from_target_output() _, second_sync_updated = self.split_records_into_created_and_updated( second_sync_records ) # # THIS MAKES AN ASSUMPTION THAT CHILD STREAMS DO NOT NEED TESTING. # # ADJUST IF NECESSARY for stream in event_update_streams.difference(self.child_streams()): with self.subTest(stream=stream): # verify that there is more than 1 record of data - setup necessary self.assertGreater( first_sync_record_count.get(stream, 0), 1, msg="Data isn't set up to be able to test event updates", ) # verify that you get at least one updated record on the second sync self.assertGreaterEqual( len(second_sync_updated.get(stream, {}).get("messages", [])), 1, msg="second syc didn't have updates", ) # verify that you get less data the 2nd time around since only updates # should be picked up self.assertLess( second_sync_record_count.get(stream, 0), first_sync_record_count.get(stream, 0), msg="second syc had the same or more records", ) # verify all the updated records in the 2nd sync are different from # the first run first_data = next( record["data"] for record in first_sync_created.get(stream, {}).get("messages", []) if record.get("data", {}).get("id") == updated[stream] ) second_data = next( record["data"] for record in second_sync_updated.get(stream, {}).get( "messages", [] ) if record.get("data", {}).get("id") == updated[stream] ) # verify the updated timestamp is greater in the second sync self.assertGreater( second_data["updated"], first_data["updated"], "updated timestamp for second sync is not greater than first sync", ) # verify the metadata[test] value actually changed self.assertNotEqual( second_data["metadata"].get("test_value", 0), first_data["metadata"].get("test_value", 0), "the test metadata should be different", ) if stream in new_objects: delete_object(stream, new_objects[stream]["id"])