@@ -46,8 +46,17 @@ namespace Envoy {
4646namespace Extensions {
4747namespace HttpFilters {
4848namespace ExternalProcessing {
49+ // Accessor for private members of Filter to improve coverage
50+ class FilterAccessor {
51+ public:
52+ static DecodingProcessorState& decodingState (Filter& filter) { return filter.decoding_state_ ; }
53+ static EncodingProcessorState& encodingState (Filter& filter) { return filter.encoding_state_ ; }
54+ };
55+
4956namespace {
5057
58+ using ::testing::ReturnRef;
59+
5160using ::envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute;
5261using ::envoy::extensions::filters::http::ext_proc::v3::ProcessingMode;
5362using ::envoy::service::ext_proc::v3::BodyResponse;
@@ -106,9 +115,15 @@ class HttpFilterTest : public testing::Test {
106115 {{" envoy.reloadable_features.ext_proc_inject_data_with_state_update" , " true" }});
107116 scoped_runtime_.mergeValues (
108117 {{" envoy.reloadable_features.ext_proc_return_stop_iteration" , " true" }});
109- client_ = std::make_unique<MockClient>();
118+ if (!client_) {
119+ client_ = std::make_unique<MockClient>();
120+ }
121+ client_ptr_ = client_.get ();
110122 route_ = std::make_shared<NiceMock<Router::MockRoute>>();
111- EXPECT_CALL (*client_, start (_, _, _, _)).WillOnce (Invoke (this , &HttpFilterTest::doStart));
123+ EXPECT_CALL (*client_ptr_, cancel ()).Times (AnyNumber ());
124+ EXPECT_CALL (*client_ptr_, start (_, _, _, _))
125+ .Times (AnyNumber ())
126+ .WillRepeatedly (Invoke (this , &HttpFilterTest::doStart));
112127 EXPECT_CALL (encoder_callbacks_, dispatcher ()).WillRepeatedly (ReturnRef (dispatcher_));
113128 EXPECT_CALL (decoder_callbacks_, dispatcher ()).WillRepeatedly (ReturnRef (dispatcher_));
114129 EXPECT_CALL (decoder_callbacks_, route ())
@@ -680,6 +695,7 @@ class HttpFilterTest : public testing::Test {
680695 absl::optional<envoy::config::core::v3::GrpcService> final_expected_grpc_service_;
681696 Grpc::GrpcServiceConfigWithHashKey config_with_hash_key_;
682697 std::unique_ptr<MockClient> client_;
698+ MockClient* client_ptr_ = nullptr ;
683699 ExternalProcessorCallbacks* stream_callbacks_ = nullptr ;
684700 ProcessingRequest last_request_;
685701 bool server_closed_stream_ = false ;
@@ -6168,6 +6184,242 @@ TEST_F(HttpFilterTest, StreamingSendDataRandomGrpcLatencyReturnContinue) {
61686184 expectNoGrpcCall (envoy::config::core::v3::TrafficDirection::OUTBOUND);
61696185}
61706186
6187+ TEST_F (HttpFilterTest, ResponseCaseToStringCoverage) {
6188+ EXPECT_EQ (" request headers" ,
6189+ responseCaseToString (ProcessingResponse::ResponseCase::kRequestHeaders ));
6190+ EXPECT_EQ (" response headers" ,
6191+ responseCaseToString (ProcessingResponse::ResponseCase::kResponseHeaders ));
6192+ EXPECT_EQ (" request body" , responseCaseToString (ProcessingResponse::ResponseCase::kRequestBody ));
6193+ EXPECT_EQ (" response body" , responseCaseToString (ProcessingResponse::ResponseCase::kResponseBody ));
6194+ EXPECT_EQ (" request trailers" ,
6195+ responseCaseToString (ProcessingResponse::ResponseCase::kRequestTrailers ));
6196+ EXPECT_EQ (" response trailers" ,
6197+ responseCaseToString (ProcessingResponse::ResponseCase::kResponseTrailers ));
6198+ EXPECT_EQ (" immediate response" ,
6199+ responseCaseToString (ProcessingResponse::ResponseCase::kImmediateResponse ));
6200+ EXPECT_EQ (" streamed immediate response" ,
6201+ responseCaseToString (ProcessingResponse::ResponseCase::kStreamedImmediateResponse ));
6202+ EXPECT_EQ (" unknown" , responseCaseToString (ProcessingResponse::ResponseCase::RESPONSE_NOT_SET));
6203+ }
6204+
6205+ TEST_F (HttpFilterTest, OnErrorAfterProcessingComplete) {
6206+ initialize (" " );
6207+
6208+ filter_->onDestroy (); // Sets processing_complete_ = true
6209+
6210+ // This should hit the if (processing_complete_) block and return early.
6211+ filter_->onError ();
6212+ EXPECT_EQ (1 , config_->stats ().http_not_ok_resp_received_ .value ());
6213+ }
6214+
6215+ TEST_F (HttpFilterTest, OnTrailersOpenStreamFail) {
6216+ initialize (R"EOF(
6217+ grpc_service:
6218+ envoy_grpc:
6219+ cluster_name: "ext_proc_server"
6220+ processing_mode:
6221+ request_header_mode: "SEND"
6222+ request_body_mode: "BUFFERED"
6223+ )EOF" );
6224+
6225+ // Setting the client to return null stream will cause openStream to return IgnoreError
6226+ // We MUST do this after initialize() because initialize() might set its own expectation.
6227+ EXPECT_CALL (*client_ptr_, start (_, _, _, _))
6228+ .WillRepeatedly (
6229+ Invoke ([](ExternalProcessorCallbacks&, const Grpc::GrpcServiceConfigWithHashKey&,
6230+ const Envoy::Http::AsyncClient::StreamOptions&,
6231+ Envoy::Http::StreamFilterSidestreamWatermarkCallbacks&) { return nullptr ; }));
6232+
6233+ // decodeHeaders calls onHeaders which calls openStream which returns IgnoreError.
6234+ filter_->decodeHeaders (request_headers_, false );
6235+
6236+ // decodeTrailers calls onTrailers, which hits the BUFFERED mode check and calls openStream again.
6237+ filter_->decodeTrailers (request_trailers_);
6238+ }
6239+
6240+ TEST_F (HttpFilterTest, CloseGrpcStreamLastRespImmediate) {
6241+ initialize (R"EOF(
6242+ grpc_service:
6243+ envoy_grpc:
6244+ cluster_name: "ext_proc_server"
6245+ )EOF" );
6246+
6247+ auto response = std::make_unique<ProcessingResponse>();
6248+ response->mutable_immediate_response ();
6249+
6250+ // Start stream
6251+ filter_->decodeHeaders (request_headers_, false );
6252+
6253+ filter_->onReceiveMessage (std::move (response));
6254+ }
6255+
6256+ TEST_F (HttpFilterTest, HandleStreamingImmediateResponseDefault) {
6257+ initialize (R"EOF(
6258+ grpc_service:
6259+ envoy_grpc:
6260+ cluster_name: "ext_proc_server"
6261+ )EOF" );
6262+
6263+ auto response = std::make_unique<ProcessingResponse>();
6264+ response->mutable_streamed_immediate_response (); // response_case is NOT_SET by default inside it
6265+
6266+ // Start stream
6267+ filter_->decodeHeaders (request_headers_, false );
6268+
6269+ filter_->onReceiveMessage (std::move (response));
6270+ }
6271+
6272+ TEST_F (HttpFilterTest, DeferredCloseAlreadyClosed) {
6273+ initialize (" " );
6274+
6275+ auto stream = std::make_unique<NiceMock<MockStream>>();
6276+ DeferredDeletableStream wrapper (std::move (stream), config_->threadLocalStreamManager (),
6277+ config_->stats (), std::chrono::milliseconds (100 ));
6278+
6279+ // Null out the stream
6280+ wrapper.stream_ = nullptr ;
6281+ wrapper.closeStreamOnTimer ();
6282+ }
6283+
6284+ TEST_F (HttpFilterTest, BufferedPartialBodyMutationAndLeftover) {
6285+ initialize (R"EOF(
6286+ grpc_service:
6287+ envoy_grpc:
6288+ cluster_name: "ext_proc_server"
6289+ processing_mode:
6290+ request_header_mode: "SKIP"
6291+ request_body_mode: "BUFFERED_PARTIAL"
6292+ )EOF" );
6293+
6294+ EXPECT_CALL (decoder_callbacks_, bufferLimit ()).WillRepeatedly (Return (10 ));
6295+ EXPECT_EQ (FilterHeadersStatus::Continue, filter_->decodeHeaders (request_headers_, false ));
6296+
6297+ Buffer::OwnedImpl data1 (" this is more than 10 bytes" );
6298+ EXPECT_EQ (FilterDataStatus::StopIterationNoBuffer, filter_->decodeData (data1, false ));
6299+
6300+ Buffer::OwnedImpl data2 (" more data" );
6301+ EXPECT_EQ (FilterDataStatus::StopIterationNoBuffer, filter_->decodeData (data2, false ));
6302+
6303+ EXPECT_CALL (decoder_callbacks_, injectDecodedDataToFilterChain (_, _)).Times (2 );
6304+
6305+ processRequestBody ([](const envoy::service::ext_proc::v3::HttpBody&, ProcessingResponse&,
6306+ BodyResponse& body_resp) {
6307+ auto * common = body_resp.mutable_response ();
6308+ auto * mut = common->mutable_header_mutation ();
6309+ auto * h = mut->add_set_headers ();
6310+ h->mutable_header ()->set_key (" x-new-header" );
6311+ h->mutable_header ()->set_value (" foo" );
6312+ });
6313+
6314+ filter_->onDestroy ();
6315+ }
6316+
6317+ TEST_F (HttpFilterTest, ValidateContentLengthSimpleAtoiFail) {
6318+ initialize (R"EOF(
6319+ grpc_service:
6320+ envoy_grpc:
6321+ cluster_name: "ext_proc_server"
6322+ processing_mode:
6323+ request_header_mode: "SEND"
6324+ request_body_mode: "BUFFERED"
6325+ )EOF" );
6326+
6327+ request_headers_.addCopy (LowerCaseString (" content-length" ), " invalid" );
6328+ EXPECT_EQ (FilterHeadersStatus::StopIteration, filter_->decodeHeaders (request_headers_, false ));
6329+ processRequestHeaders (true , absl::nullopt );
6330+
6331+ Buffer::OwnedImpl req_data (" body" );
6332+ Buffer::OwnedImpl buffered_data;
6333+ setUpDecodingBuffering (buffered_data, true );
6334+
6335+ // This will call handleDataBufferedMode
6336+ EXPECT_EQ (FilterDataStatus::StopIterationNoBuffer, filter_->decodeData (req_data, true ));
6337+
6338+ EXPECT_TRUE (last_request_.has_request_body ());
6339+
6340+ processRequestBody ([](const HttpBody&, ProcessingResponse&, BodyResponse& body_resp) {
6341+ body_resp.mutable_response ()->mutable_body_mutation ()->set_body (" new body" );
6342+ });
6343+
6344+ filter_->onDestroy ();
6345+ }
6346+
6347+ TEST_F (HttpFilterTest, EncodingWatermark) {
6348+ initialize (R"EOF(
6349+ grpc_service:
6350+ envoy_grpc:
6351+ cluster_name: "ext_proc_server"
6352+ processing_mode:
6353+ request_header_mode: "SKIP"
6354+ response_header_mode: "SKIP"
6355+ response_body_mode: "STREAMED"
6356+ )EOF" );
6357+
6358+ EXPECT_EQ (FilterHeadersStatus::Continue, filter_->decodeHeaders (request_headers_, true ));
6359+ EXPECT_EQ (FilterHeadersStatus::Continue, filter_->encodeHeaders (response_headers_, false ));
6360+
6361+ EXPECT_CALL (encoder_callbacks_, bufferLimit ()).WillRepeatedly (Return (10 ));
6362+
6363+ Buffer::OwnedImpl data (" this is a long body that exceeds 10 bytes" );
6364+ EXPECT_CALL (encoder_callbacks_, onEncoderFilterAboveWriteBufferHighWatermark ());
6365+ EXPECT_EQ (FilterDataStatus::StopIterationNoBuffer, filter_->encodeData (data, false ));
6366+
6367+ EXPECT_CALL (encoder_callbacks_, onEncoderFilterBelowWriteBufferLowWatermark ());
6368+ processResponseBody (
6369+ [](const envoy::service::ext_proc::v3::HttpBody&, ProcessingResponse&, BodyResponse&) {},
6370+ /* should_continue=*/ false );
6371+
6372+ filter_->onDestroy ();
6373+ }
6374+
6375+ TEST_F (HttpFilterTest, TrafficDirectionDebugStrOutbound) {
6376+ initialize (R"EOF(
6377+ grpc_service:
6378+ envoy_grpc:
6379+ cluster_name: "ext_proc_server"
6380+ )EOF" );
6381+ EXPECT_EQ (FilterHeadersStatus::StopIteration, filter_->decodeHeaders (request_headers_, false ));
6382+ EXPECT_EQ (" OUTBOUND" , FilterAccessor::encodingState (*filter_).trafficDirectionDebugStr ());
6383+ filter_->onDestroy ();
6384+ }
6385+
6386+ TEST_F (HttpFilterTest, DecodingAddTrailers) {
6387+ initialize (R"EOF(
6388+ grpc_service:
6389+ envoy_grpc:
6390+ cluster_name: "ext_proc_server"
6391+ )EOF" );
6392+ EXPECT_EQ (FilterHeadersStatus::StopIteration, filter_->decodeHeaders (request_headers_, false ));
6393+ EXPECT_CALL (decoder_callbacks_, addDecodedTrailers ()).WillOnce (ReturnRef (request_trailers_));
6394+ auto * trailers = FilterAccessor::decodingState (*filter_).addTrailers ();
6395+ EXPECT_EQ (trailers, &request_trailers_);
6396+ filter_->onDestroy ();
6397+ }
6398+
6399+ TEST_F (HttpFilterTest, LocalResponseStarted) {
6400+ initialize (R"EOF(
6401+ grpc_service:
6402+ envoy_grpc:
6403+ cluster_name: "ext_proc_server"
6404+ )EOF" );
6405+ EXPECT_EQ (FilterHeadersStatus::StopIteration, filter_->decodeHeaders (request_headers_, false ));
6406+ EXPECT_FALSE (FilterAccessor::decodingState (*filter_).localResponseStarted ());
6407+ filter_->onDestroy ();
6408+ }
6409+
6410+ TEST_F (HttpFilterTest, EncodingAddTrailers) {
6411+ initialize (R"EOF(
6412+ grpc_service:
6413+ envoy_grpc:
6414+ cluster_name: "ext_proc_server"
6415+ )EOF" );
6416+ EXPECT_EQ (FilterHeadersStatus::StopIteration, filter_->decodeHeaders (request_headers_, false ));
6417+ EXPECT_CALL (encoder_callbacks_, addEncodedTrailers ()).WillOnce (ReturnRef (response_trailers_));
6418+ auto * trailers = FilterAccessor::encodingState (*filter_).addTrailers ();
6419+ EXPECT_EQ (trailers, &response_trailers_);
6420+ filter_->onDestroy ();
6421+ }
6422+
61716423} // namespace
61726424} // namespace ExternalProcessing
61736425} // namespace HttpFilters
0 commit comments