@@ -780,3 +780,102 @@ def test_dynamodb_update_online_store_int_list(repo_config, dynamodb_online_stor
780780 assert len (result ) == 1
781781 scores = result [0 ][1 ]["scores" ]
782782 assert _extract_int32_list (scores ) == [10 , 20 , 30 ]
783+
784+
785+ @mock_dynamodb
786+ def test_dynamodb_online_store_online_read_empty_entities (
787+ repo_config , dynamodb_online_store
788+ ):
789+ """Test DynamoDBOnlineStore online_read with empty entity list."""
790+ db_table_name = f"{ TABLE_NAME } _empty_entities"
791+ create_test_table (PROJECT , db_table_name , REGION )
792+
793+ returned_items = dynamodb_online_store .online_read (
794+ config = repo_config ,
795+ table = MockFeatureView (name = db_table_name ),
796+ entity_keys = [],
797+ )
798+ assert returned_items == []
799+
800+
801+ @mock_dynamodb
802+ def test_dynamodb_online_store_online_read_parallel_batches (
803+ repo_config , dynamodb_online_store
804+ ):
805+ """Test DynamoDBOnlineStore online_read with multiple batches (parallel execution).
806+
807+ With batch_size=100 (default), 250 entities should create 3 batches
808+ that are executed in parallel via ThreadPoolExecutor.
809+ """
810+ n_samples = 250
811+ db_table_name = f"{ TABLE_NAME } _parallel_batches"
812+ create_test_table (PROJECT , db_table_name , REGION )
813+ data = create_n_customer_test_samples (n = n_samples )
814+ insert_data_test_table (data , PROJECT , db_table_name , REGION )
815+
816+ entity_keys , features , * rest = zip (* data )
817+ returned_items = dynamodb_online_store .online_read (
818+ config = repo_config ,
819+ table = MockFeatureView (name = db_table_name ),
820+ entity_keys = entity_keys ,
821+ )
822+
823+ # Verify all items returned
824+ assert len (returned_items ) == n_samples
825+ # Verify order is preserved
826+ assert [item [1 ] for item in returned_items ] == list (features )
827+
828+
829+ @mock_dynamodb
830+ def test_dynamodb_online_store_online_read_single_batch_no_parallel (
831+ repo_config , dynamodb_online_store
832+ ):
833+ """Test DynamoDBOnlineStore online_read with single batch (no parallelization).
834+
835+ With batch_size=100, 50 entities should use single batch path
836+ without ThreadPoolExecutor overhead.
837+ """
838+ n_samples = 50
839+ db_table_name = f"{ TABLE_NAME } _single_batch"
840+ create_test_table (PROJECT , db_table_name , REGION )
841+ data = create_n_customer_test_samples (n = n_samples )
842+ insert_data_test_table (data , PROJECT , db_table_name , REGION )
843+
844+ entity_keys , features , * rest = zip (* data )
845+ returned_items = dynamodb_online_store .online_read (
846+ config = repo_config ,
847+ table = MockFeatureView (name = db_table_name ),
848+ entity_keys = entity_keys ,
849+ )
850+
851+ assert len (returned_items ) == n_samples
852+ assert [item [1 ] for item in returned_items ] == list (features )
853+
854+
855+ @mock_dynamodb
856+ def test_dynamodb_online_store_online_read_order_preservation_across_batches (
857+ repo_config , dynamodb_online_store
858+ ):
859+ """Test that entity order is preserved across parallel batch reads.
860+
861+ This is critical: parallel execution must not change the order of results.
862+ """
863+ n_samples = 150 # 2 batches with batch_size=100
864+ db_table_name = f"{ TABLE_NAME } _order_preservation"
865+ create_test_table (PROJECT , db_table_name , REGION )
866+ data = create_n_customer_test_samples (n = n_samples )
867+ insert_data_test_table (data , PROJECT , db_table_name , REGION )
868+
869+ entity_keys , features , * rest = zip (* data )
870+
871+ # Read multiple times to verify consistent ordering
872+ for _ in range (3 ):
873+ returned_items = dynamodb_online_store .online_read (
874+ config = repo_config ,
875+ table = MockFeatureView (name = db_table_name ),
876+ entity_keys = entity_keys ,
877+ )
878+ assert len (returned_items ) == n_samples
879+ # Verify exact order matches
880+ for i , (returned , expected ) in enumerate (zip (returned_items , features )):
881+ assert returned [1 ] == expected , f"Mismatch at index { i } "
0 commit comments