1010 Message ,
1111 Datum ,
1212 MapStreamAsyncServer ,
13+ NackOptions ,
1314)
15+ from pynumaflow ._constants import NACK
1416from pynumaflow .proto .mapper import map_pb2_grpc
1517from tests .mapstream .utils import request_generator
1618from tests .conftest import create_async_loop , start_async_server , teardown_async_server
2527raise_error_from_map = False
2628
2729SOCK_PATH = "unix:///tmp/async_map_stream.sock"
30+ NACK_SOCK_PATH = "unix:///tmp/async_map_stream_nack.sock"
31+
32+ NACK_TEST_OPTIONS = NackOptions (delay = 1000 , max_deliveries = 3 , reason = "retry" )
2833
2934
3035async def async_map_stream_handler (keys : list [str ], datum : Datum ) -> AsyncIterable [Message ]:
@@ -85,7 +90,7 @@ def test_map_stream(map_stream_stub):
8590
8691 # Prepare expected payload
8792 expected_payload = bytes (
88- "payload:test_mock_message " " event_time:2022-09-12 16:00:00 watermark:2022-09-12 16:01:00" ,
93+ "payload:test_mock_message event_time:2022-09-12 16:00:00 watermark:2022-09-12 16:01:00" ,
8994 encoding = "utf-8" ,
9095 )
9196
@@ -106,16 +111,63 @@ def test_map_stream(map_stream_stub):
106111 result_msg_count += 1
107112
108113 # Validate totals
109- assert (
110- result_msg_count == expected_result_msgs
111- ), f"Expected { expected_result_msgs } result messages, got { result_msg_count } "
114+ assert result_msg_count == expected_result_msgs , (
115+ f"Expected { expected_result_msgs } result messages, got { result_msg_count } "
116+ )
112117 assert eot_count == expected_eots , f"Expected { expected_eots } EOT messages, got { eot_count } "
113118
114119 # Validate 10 messages per request id: test-id-0..test-id-(req_count-1)
115120 for i in range (req_count ):
116- assert (
117- id_counter [f"test-id-{ i } " ] == 10
118- ), f"Expected 10 results for test-id-{ i } , got { id_counter [f'test-id-{ i } ' ]} "
121+ assert id_counter [f"test-id-{ i } " ] == 10 , (
122+ f"Expected 10 results for test-id-{ i } , got { id_counter [f'test-id-{ i } ' ]} "
123+ )
124+
125+
126+ async def async_nack_map_stream_handler (keys : list [str ], datum : Datum ) -> AsyncIterable [Message ]:
127+ for i in range (3 ):
128+ yield Message .to_nack (NACK_TEST_OPTIONS )
129+
130+
131+ async def _start_nack_server (udfs ):
132+ server = grpc .aio .server ()
133+ map_pb2_grpc .add_MapServicer_to_server (udfs , server )
134+ server .add_insecure_port (NACK_SOCK_PATH )
135+ logging .info ("Starting nack server on %s" , NACK_SOCK_PATH )
136+ await server .start ()
137+ return server , NACK_SOCK_PATH
138+
139+
140+ @pytest .fixture (scope = "module" )
141+ def async_nack_map_stream_server ():
142+ loop = create_async_loop ()
143+ server_obj = MapStreamAsyncServer (map_stream_instance = async_nack_map_stream_handler )
144+ udfs = server_obj .servicer
145+ server = start_async_server (loop , _start_nack_server (udfs ))
146+ yield loop
147+ teardown_async_server (loop , server )
148+
149+
150+ def test_map_stream_nack (async_nack_map_stream_server ):
151+ with grpc .insecure_channel (NACK_SOCK_PATH ) as channel :
152+ stub = map_pb2_grpc .MapStub (channel )
153+ generator_response = stub .MapFn (request_iterator = request_generator (count = 2 , session = 1 ))
154+
155+ handshake = next (generator_response )
156+ assert handshake .handshake .sot
157+
158+ result_count = 0
159+ for msg in generator_response :
160+ if hasattr (msg , "status" ) and msg .status .eot :
161+ continue
162+ result = msg .results [0 ]
163+ assert NACK in result .tags
164+ assert result .nack_options .delay == NACK_TEST_OPTIONS .delay
165+ assert result .nack_options .max_deliveries == NACK_TEST_OPTIONS .max_deliveries
166+ assert result .nack_options .reason == NACK_TEST_OPTIONS .reason
167+ result_count += 1
168+
169+ # 2 requests x 3 nack messages each
170+ assert result_count == 6
119171
120172
121173def test_is_ready (async_map_stream_server ):
0 commit comments