@@ -701,6 +701,108 @@ TEST(BodyReaderTest, ReadWithoutStream) {
701701 auto n = reader.read (buf, sizeof (buf));
702702
703703 EXPECT_EQ (-1 , n);
704+ EXPECT_TRUE (reader.has_error ());
705+ EXPECT_EQ (httplib::Error::Connection, reader.last_error );
706+ }
707+
708+ // ------------------------------------------------------------------------------
709+ // Phase 2.10: Error handling tests
710+ // ------------------------------------------------------------------------------
711+
712+ // Mock stream that returns error after N bytes
713+ class ErrorAfterNBytesStream : public httplib ::Stream {
714+ public:
715+ ErrorAfterNBytesStream (const std::string &data, size_t error_after)
716+ : data_(data), pos_(0 ), error_after_(error_after) {}
717+
718+ bool is_readable () const override { return true ; }
719+ bool wait_readable () const override { return true ; }
720+ bool wait_writable () const override { return true ; }
721+
722+ ssize_t read (char *ptr, size_t size) override {
723+ if (pos_ >= error_after_) { return -1 ; } // Simulate read error
724+ if (pos_ >= data_.size ()) { return 0 ; }
725+ size_t to_read =
726+ std::min (size, std::min (data_.size () - pos_, error_after_ - pos_));
727+ std::memcpy (ptr, data_.data () + pos_, to_read);
728+ pos_ += to_read;
729+ return static_cast <ssize_t >(to_read);
730+ }
731+
732+ ssize_t write (const char *, size_t ) override { return -1 ; }
733+
734+ void get_remote_ip_and_port (std::string &ip, int &port) const override {
735+ ip = " 127.0.0.1" ;
736+ port = 0 ;
737+ }
738+
739+ void get_local_ip_and_port (std::string &ip, int &port) const override {
740+ ip = " 127.0.0.1" ;
741+ port = 0 ;
742+ }
743+
744+ socket_t socket () const override { return INVALID_SOCKET; }
745+ time_t duration () const override { return 0 ; }
746+
747+ private:
748+ std::string data_;
749+ size_t pos_;
750+ size_t error_after_;
751+ };
752+
753+ TEST (BodyReaderErrorTest, ReadErrorSetsLastError) {
754+ ErrorAfterNBytesStream stream (" Hello, World!" , 5 );
755+
756+ httplib::detail::BodyReader reader;
757+ reader.stream = &stream;
758+ reader.content_length = 13 ;
759+ reader.chunked = false ;
760+
761+ char buf[32 ];
762+
763+ // First read succeeds (5 bytes)
764+ auto n1 = reader.read (buf, sizeof (buf));
765+ EXPECT_EQ (5 , n1);
766+ EXPECT_FALSE (reader.has_error ());
767+
768+ // Second read fails
769+ auto n2 = reader.read (buf, sizeof (buf));
770+ EXPECT_EQ (-1 , n2);
771+ EXPECT_TRUE (reader.has_error ());
772+ EXPECT_EQ (httplib::Error::Read, reader.last_error );
773+ }
774+
775+ TEST (BodyReaderErrorTest, UnexpectedEOFSetsError) {
776+ // Data is shorter than content_length
777+ MockStream stream (" Short" );
778+
779+ httplib::detail::BodyReader reader;
780+ reader.stream = &stream;
781+ reader.content_length = 100 ; // Expect 100 bytes but only 5 available
782+ reader.chunked = false ;
783+
784+ char buf[32 ];
785+
786+ // First read gets the available data
787+ auto n1 = reader.read (buf, sizeof (buf));
788+ EXPECT_EQ (5 , n1);
789+ EXPECT_FALSE (reader.has_error ());
790+
791+ // Second read hits unexpected EOF
792+ auto n2 = reader.read (buf, sizeof (buf));
793+ EXPECT_EQ (0 , n2);
794+ EXPECT_TRUE (reader.has_error ());
795+ EXPECT_EQ (httplib::Error::Read, reader.last_error );
796+ }
797+
798+ TEST (BodyReaderErrorTest, HasErrorMethod) {
799+ httplib::detail::BodyReader reader;
800+
801+ EXPECT_FALSE (reader.has_error ());
802+ EXPECT_EQ (httplib::Error::Success, reader.last_error );
803+
804+ reader.last_error = httplib::Error::Read;
805+ EXPECT_TRUE (reader.has_error ());
704806}
705807
706808// =============================================================================
@@ -801,6 +903,46 @@ TEST_F(StreamHandleV2Test, ReadAllSocketDirect) {
801903 EXPECT_EQ (" Hello from socket!" , result);
802904}
803905
906+ TEST_F (StreamHandleV2Test, GetReadErrorReturnsSuccess) {
907+ httplib::ClientImpl::StreamHandle handle;
908+
909+ handle.response = std::make_unique<httplib::Response>();
910+ handle.response ->status = 200 ;
911+
912+ handle.stream_ = mock_stream_.get ();
913+ handle.body_reader_ .stream = mock_stream_.get ();
914+ handle.body_reader_ .content_length = 18 ;
915+
916+ // No error initially
917+ EXPECT_FALSE (handle.has_read_error ());
918+ EXPECT_EQ (httplib::Error::Success, handle.get_read_error ());
919+
920+ // Read successfully
921+ handle.read_all ();
922+ EXPECT_FALSE (handle.has_read_error ());
923+ }
924+
925+ TEST_F (StreamHandleV2Test, GetReadErrorAfterReadFailure) {
926+ auto error_stream =
927+ std::make_unique<ErrorAfterNBytesStream>(" Hello World" , 5 );
928+
929+ httplib::ClientImpl::StreamHandle handle;
930+ handle.response = std::make_unique<httplib::Response>();
931+ handle.response ->status = 200 ;
932+
933+ handle.stream_ = error_stream.get ();
934+ handle.body_reader_ .stream = error_stream.get ();
935+ handle.body_reader_ .content_length = 11 ;
936+
937+ // Read until error
938+ char buf[32 ];
939+ handle.read (buf, sizeof (buf)); // First read: 5 bytes OK
940+ handle.read (buf, sizeof (buf)); // Second read: error
941+
942+ EXPECT_TRUE (handle.has_read_error ());
943+ EXPECT_EQ (httplib::Error::Read, handle.get_read_error ());
944+ }
945+
804946// =============================================================================
805947// Phase 2.5: open_stream_direct() Tests (True Streaming)
806948// =============================================================================
0 commit comments