@@ -5403,7 +5403,8 @@ H1_CLIENT_TEST_CASE(h1_client_write_data_less_than_content_length) {
54035403 return AWS_OP_SUCCESS ;
54045404}
54055405
5406- /* Test write_data with chunked encoding - single write */
5406+ /* Test write_data with chunked encoding - single write with end_stream=true
5407+ * should automatically send the termination chunk (0\r\n\r\n) */
54075408H1_CLIENT_TEST_CASE (h1_client_write_data_chunked_single ) {
54085409 (void )ctx ;
54095410 struct write_data_test_fixture fixture ;
@@ -5413,13 +5414,15 @@ H1_CLIENT_TEST_CASE(h1_client_write_data_chunked_single) {
54135414 };
54145415 ASSERT_SUCCESS (s_write_data_test_setup (& fixture , allocator , headers , AWS_ARRAY_SIZE (headers ), true));
54155416
5416- /* Single write with end_stream=true, termination chunk should be sent automatically */
54175417 struct aws_byte_cursor data = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("write more tests" );
54185418 struct aws_input_stream * input_stream = aws_input_stream_new_from_cursor (allocator , & data );
54195419
5420+ struct write_data_callback_tester callback_tester = {0 };
54205421 struct aws_http_stream_write_data_options write_options = {
54215422 .data = input_stream ,
54225423 .end_stream = true,
5424+ .on_complete = s_on_write_data_complete ,
5425+ .user_data = & callback_tester ,
54235426 };
54245427 ASSERT_SUCCESS (aws_http_stream_write_data (fixture .stream_tester .stream , & write_options ));
54255428
@@ -5435,6 +5438,15 @@ H1_CLIENT_TEST_CASE(h1_client_write_data_chunked_single) {
54355438 "\r\n" ;
54365439 ASSERT_SUCCESS (testing_channel_check_written_messages_str (& fixture .tester .testing_channel , allocator , expected ));
54375440
5441+ ASSERT_INT_EQUALS (1 , callback_tester .num_callbacks );
5442+ ASSERT_INT_EQUALS (AWS_ERROR_SUCCESS , callback_tester .last_error_code );
5443+
5444+ ASSERT_SUCCESS (testing_channel_push_read_str (& fixture .tester .testing_channel , "HTTP/1.1 200 OK\r\n\r\n" ));
5445+ testing_channel_drain_queued_tasks (& fixture .tester .testing_channel );
5446+
5447+ ASSERT_TRUE (fixture .stream_tester .complete );
5448+ ASSERT_INT_EQUALS (200 , fixture .stream_tester .response_status );
5449+
54385450 aws_input_stream_release (input_stream );
54395451 ASSERT_SUCCESS (s_write_data_test_teardown (& fixture ));
54405452 return AWS_OP_SUCCESS ;
@@ -5483,65 +5495,44 @@ H1_CLIENT_TEST_CASE(h1_client_write_data_chunked_multiple) {
54835495 return AWS_OP_SUCCESS ;
54845496}
54855497
5486- /* Test: write_data with end_stream=true and non-zero data on chunked stream
5487- * should automatically send the termination chunk (0\r\n\r\n) */
5488- H1_CLIENT_TEST_CASE (h1_client_write_data_chunked_end_stream_with_data ) {
5498+ /* Test: write_data with NULL data and end_stream=true on Content-Length stream */
5499+ H1_CLIENT_TEST_CASE (h1_client_write_data_null_data_content_length ) {
54895500 (void )ctx ;
54905501 struct write_data_test_fixture fixture ;
54915502 struct aws_http_header headers [] = {
5492- {.name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("Transfer-Encoding " ),
5493- .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("chunked " )},
5503+ {.name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("Content-Length " ),
5504+ .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("0 " )},
54945505 };
54955506 ASSERT_SUCCESS (s_write_data_test_setup (& fixture , allocator , headers , AWS_ARRAY_SIZE (headers ), true));
54965507
5497- /* Single write with end_stream=true and non-zero data */
5498- struct aws_byte_cursor data = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("hello" );
5499- struct aws_input_stream * input_stream = aws_input_stream_new_from_cursor (allocator , & data );
5500-
5501- struct write_data_callback_tester callback_tester = {0 };
55025508 struct aws_http_stream_write_data_options write_options = {
5503- .data = input_stream ,
5509+ .data = NULL ,
55045510 .end_stream = true,
5505- .on_complete = s_on_write_data_complete ,
5506- .user_data = & callback_tester ,
55075511 };
55085512 ASSERT_SUCCESS (aws_http_stream_write_data (fixture .stream_tester .stream , & write_options ));
55095513
55105514 testing_channel_drain_queued_tasks (& fixture .tester .testing_channel );
55115515
5512- /* The stream should send the data chunk AND the termination chunk automatically */
5513- const char * expected = "POST /upload HTTP/1.1\r\n"
5514- "Transfer-Encoding: chunked\r\n"
5515- "\r\n"
5516- "5\r\n"
5517- "hello"
5518- "\r\n"
5519- "0\r\n"
5520- "\r\n" ;
5521- ASSERT_SUCCESS (testing_channel_check_written_messages_str (& fixture .tester .testing_channel , allocator , expected ));
5522-
5523- ASSERT_INT_EQUALS (1 , callback_tester .num_callbacks );
5524- ASSERT_INT_EQUALS (AWS_ERROR_SUCCESS , callback_tester .last_error_code );
5516+ ASSERT_SUCCESS (testing_channel_check_written_messages_str (
5517+ & fixture .tester .testing_channel , allocator , "POST /upload HTTP/1.1\r\nContent-Length: 0\r\n\r\n" ));
55255518
5526- /* Send response so stream completes */
55275519 ASSERT_SUCCESS (testing_channel_push_read_str (& fixture .tester .testing_channel , "HTTP/1.1 200 OK\r\n\r\n" ));
55285520 testing_channel_drain_queued_tasks (& fixture .tester .testing_channel );
55295521
55305522 ASSERT_TRUE (fixture .stream_tester .complete );
55315523 ASSERT_INT_EQUALS (200 , fixture .stream_tester .response_status );
55325524
5533- aws_input_stream_release (input_stream );
55345525 ASSERT_SUCCESS (s_write_data_test_teardown (& fixture ));
55355526 return AWS_OP_SUCCESS ;
55365527}
55375528
5538- /* Test: write_data with NULL data and end_stream=true on Content-Length stream */
5539- H1_CLIENT_TEST_CASE (h1_client_write_data_null_data_content_length ) {
5529+ /* Test: write_data with NULL data and end_stream=true on chunked stream */
5530+ H1_CLIENT_TEST_CASE (h1_client_write_data_null_data_chunked ) {
55405531 (void )ctx ;
55415532 struct write_data_test_fixture fixture ;
55425533 struct aws_http_header headers [] = {
5543- {.name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("Content-Length " ),
5544- .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("0 " )},
5534+ {.name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("Transfer-Encoding " ),
5535+ .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("chunked " )},
55455536 };
55465537 ASSERT_SUCCESS (s_write_data_test_setup (& fixture , allocator , headers , AWS_ARRAY_SIZE (headers ), true));
55475538
@@ -5553,8 +5544,12 @@ H1_CLIENT_TEST_CASE(h1_client_write_data_null_data_content_length) {
55535544
55545545 testing_channel_drain_queued_tasks (& fixture .tester .testing_channel );
55555546
5556- ASSERT_SUCCESS (testing_channel_check_written_messages_str (
5557- & fixture .tester .testing_channel , allocator , "POST /upload HTTP/1.1\r\nContent-Length: 0\r\n\r\n" ));
5547+ const char * expected = "POST /upload HTTP/1.1\r\n"
5548+ "Transfer-Encoding: chunked\r\n"
5549+ "\r\n"
5550+ "0\r\n"
5551+ "\r\n" ;
5552+ ASSERT_SUCCESS (testing_channel_check_written_messages_str (& fixture .tester .testing_channel , allocator , expected ));
55585553
55595554 ASSERT_SUCCESS (testing_channel_push_read_str (& fixture .tester .testing_channel , "HTTP/1.1 200 OK\r\n\r\n" ));
55605555 testing_channel_drain_queued_tasks (& fixture .tester .testing_channel );
@@ -5566,37 +5561,107 @@ H1_CLIENT_TEST_CASE(h1_client_write_data_null_data_content_length) {
55665561 return AWS_OP_SUCCESS ;
55675562}
55685563
5569- /* Test: write_data with NULL data and end_stream=true on chunked stream */
5570- H1_CLIENT_TEST_CASE (h1_client_write_data_null_data_chunked ) {
5564+ /* Test: write_data with NULL data and end_stream=false is a no-op */
5565+ H1_CLIENT_TEST_CASE (h1_client_write_data_null_data_no_end_stream ) {
55715566 (void )ctx ;
55725567 struct write_data_test_fixture fixture ;
55735568 struct aws_http_header headers [] = {
5574- {.name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("Transfer-Encoding " ),
5575- .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("chunked " )},
5569+ {.name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("Content-Length " ),
5570+ .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("5 " )},
55765571 };
55775572 ASSERT_SUCCESS (s_write_data_test_setup (& fixture , allocator , headers , AWS_ARRAY_SIZE (headers ), true));
55785573
5574+ /* NULL data without end_stream should be a no-op (return success, do nothing) */
55795575 struct aws_http_stream_write_data_options write_options = {
55805576 .data = NULL ,
5581- .end_stream = true ,
5577+ .end_stream = false ,
55825578 };
55835579 ASSERT_SUCCESS (aws_http_stream_write_data (fixture .stream_tester .stream , & write_options ));
55845580
5581+ /* Should still be able to write real data after the no-op */
5582+ struct aws_byte_cursor data = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("hello" );
5583+ struct aws_input_stream * input_stream = aws_input_stream_new_from_cursor (allocator , & data );
5584+ struct aws_http_stream_write_data_options real_write = {
5585+ .data = input_stream ,
5586+ .end_stream = true,
5587+ };
5588+ ASSERT_SUCCESS (aws_http_stream_write_data (fixture .stream_tester .stream , & real_write ));
5589+ testing_channel_drain_queued_tasks (& fixture .tester .testing_channel );
5590+
5591+ ASSERT_SUCCESS (testing_channel_check_written_messages_str (
5592+ & fixture .tester .testing_channel , allocator , "POST /upload HTTP/1.1\r\nContent-Length: 5\r\n\r\nhello" ));
5593+
5594+ aws_input_stream_release (input_stream );
5595+ ASSERT_SUCCESS (s_write_data_test_teardown (& fixture ));
5596+ return AWS_OP_SUCCESS ;
5597+ }
5598+
5599+ /* Test: auto-add Transfer-Encoding: chunked when no Content-Length or Transfer-Encoding is set */
5600+ H1_CLIENT_TEST_CASE (h1_client_write_data_auto_chunked ) {
5601+ (void )ctx ;
5602+ struct write_data_test_fixture fixture ;
5603+ /* No Content-Length or Transfer-Encoding header */
5604+ ASSERT_SUCCESS (s_write_data_test_setup (& fixture , allocator , NULL , 0 , true));
5605+
5606+ struct aws_byte_cursor data = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("hello" );
5607+ struct aws_input_stream * input_stream = aws_input_stream_new_from_cursor (allocator , & data );
5608+
5609+ struct aws_http_stream_write_data_options write_options = {
5610+ .data = input_stream ,
5611+ .end_stream = true,
5612+ };
5613+ ASSERT_SUCCESS (aws_http_stream_write_data (fixture .stream_tester .stream , & write_options ));
55855614 testing_channel_drain_queued_tasks (& fixture .tester .testing_channel );
55865615
5616+ /* Transfer-Encoding: chunked should have been auto-added */
55875617 const char * expected = "POST /upload HTTP/1.1\r\n"
55885618 "Transfer-Encoding: chunked\r\n"
55895619 "\r\n"
5620+ "5\r\n"
5621+ "hello"
5622+ "\r\n"
55905623 "0\r\n"
55915624 "\r\n" ;
55925625 ASSERT_SUCCESS (testing_channel_check_written_messages_str (& fixture .tester .testing_channel , allocator , expected ));
55935626
5594- ASSERT_SUCCESS (testing_channel_push_read_str (& fixture .tester .testing_channel , "HTTP/1.1 200 OK\r\n\r\n" ));
5595- testing_channel_drain_queued_tasks (& fixture .tester .testing_channel );
5627+ aws_input_stream_release (input_stream );
5628+ ASSERT_SUCCESS (s_write_data_test_teardown (& fixture ));
5629+ return AWS_OP_SUCCESS ;
5630+ }
55965631
5597- ASSERT_TRUE (fixture .stream_tester .complete );
5598- ASSERT_INT_EQUALS (200 , fixture .stream_tester .response_status );
5632+ /* Test: body stream + use_manual_data_writes is rejected */
5633+ H1_CLIENT_TEST_CASE (h1_client_write_data_body_stream_conflict ) {
5634+ (void )ctx ;
5635+ struct tester tester ;
5636+ ASSERT_SUCCESS (s_tester_init (& tester , allocator ));
55995637
5600- ASSERT_SUCCESS (s_write_data_test_teardown (& fixture ));
5638+ struct aws_http_message * request = aws_http_message_new_request (allocator );
5639+ ASSERT_NOT_NULL (request );
5640+ ASSERT_SUCCESS (aws_http_message_set_request_method (request , aws_byte_cursor_from_c_str ("POST" )));
5641+ ASSERT_SUCCESS (aws_http_message_set_request_path (request , aws_byte_cursor_from_c_str ("/upload" )));
5642+
5643+ struct aws_http_header headers [] = {
5644+ {.name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("Content-Length" ),
5645+ .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("5" )},
5646+ };
5647+ ASSERT_SUCCESS (aws_http_message_add_header_array (request , headers , AWS_ARRAY_SIZE (headers )));
5648+
5649+ /* Set a body stream AND use_manual_data_writes — should conflict */
5650+ struct aws_byte_cursor body = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL ("hello" );
5651+ struct aws_input_stream * body_stream = aws_input_stream_new_from_cursor (allocator , & body );
5652+ aws_http_message_set_body_stream (request , body_stream );
5653+
5654+ struct aws_http_make_request_options request_options = {
5655+ .self_size = sizeof (request_options ),
5656+ .request = request ,
5657+ .use_manual_data_writes = true,
5658+ };
5659+ struct aws_http_stream * stream = aws_http_connection_make_request (tester .connection , & request_options );
5660+ ASSERT_NULL (stream );
5661+ ASSERT_INT_EQUALS (AWS_ERROR_HTTP_INVALID_HEADER_FIELD , aws_last_error ());
5662+
5663+ aws_input_stream_release (body_stream );
5664+ aws_http_message_destroy (request );
5665+ ASSERT_SUCCESS (s_tester_clean_up (& tester ));
56015666 return AWS_OP_SUCCESS ;
56025667}
0 commit comments