Skip to content

Commit f05ab63

Browse files
committed
Further improve ext_proc code coverage
Signed-off-by: Ethan Truong <ethantruong@google.com>
1 parent bec407b commit f05ab63

4 files changed

Lines changed: 315 additions & 2 deletions

File tree

source/extensions/filters/http/ext_proc/ext_proc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig {
490490
class Filter : public Logger::Loggable<Logger::Id::ext_proc>,
491491
public Http::PassThroughFilter,
492492
public ExternalProcessorCallbacks {
493+
friend class FilterAccessor;
493494
// The result of an attempt to open the stream
494495
enum class StreamOpenState {
495496
// The stream was opened successfully

test/extensions/filters/http/ext_proc/filter_local_reply_streaming_test.cc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,56 @@ TEST_F(StreamingLocalReplyTest, SaveStreamingLocalResponse) {
13291329
filter_->onDestroy();
13301330
}
13311331

1332+
TEST_F(StreamingLocalReplyTest, StartLocalResponseInvalidHeaders) {
1333+
initialize(R"EOF(
1334+
grpc_service:
1335+
envoy_grpc:
1336+
cluster_name: "ext_proc_server"
1337+
)EOF");
1338+
1339+
EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false));
1340+
1341+
auto response = std::make_unique<ProcessingResponse>();
1342+
auto* headers_response =
1343+
response->mutable_streamed_immediate_response()->mutable_headers_response();
1344+
auto* h = headers_response->mutable_headers()->add_headers();
1345+
h->set_key(":status");
1346+
h->set_raw_value(std::string(1, '\0')); // Invalid header value
1347+
1348+
stream_callbacks_->onReceiveMessage(std::move(response));
1349+
}
1350+
1351+
TEST_F(StreamingLocalReplyTest, ProcessLocalTrailersInvalid) {
1352+
initialize(R"EOF(
1353+
grpc_service:
1354+
envoy_grpc:
1355+
cluster_name: "ext_proc_server"
1356+
)EOF");
1357+
1358+
EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, true));
1359+
1360+
// Start local response
1361+
auto response1 = std::make_unique<ProcessingResponse>();
1362+
auto* headers_response =
1363+
response1->mutable_streamed_immediate_response()->mutable_headers_response();
1364+
auto* h1 = headers_response->mutable_headers()->add_headers();
1365+
h1->set_key(":status");
1366+
h1->set_raw_value("200");
1367+
headers_response->set_end_of_stream(false);
1368+
1369+
stream_callbacks_->onReceiveMessage(std::move(response1));
1370+
1371+
// Send invalid trailers
1372+
auto response2 = std::make_unique<ProcessingResponse>();
1373+
auto* trailers_response =
1374+
response2->mutable_streamed_immediate_response()->mutable_trailers_response();
1375+
auto* h2 = trailers_response->add_headers();
1376+
h2->set_key("invalid name");
1377+
h2->set_raw_value("value");
1378+
1379+
stream_callbacks_->onReceiveMessage(std::move(response2));
1380+
}
1381+
13321382
} // namespace
13331383
} // namespace ExternalProcessing
13341384
} // namespace HttpFilters

test/extensions/filters/http/ext_proc/filter_test.cc

Lines changed: 254 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,17 @@ namespace Envoy {
4646
namespace Extensions {
4747
namespace HttpFilters {
4848
namespace 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+
4956
namespace {
5057

58+
using ::testing::ReturnRef;
59+
5160
using ::envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute;
5261
using ::envoy::extensions::filters::http::ext_proc::v3::ProcessingMode;
5362
using ::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

test/extensions/filters/http/ext_proc/logging_info_test.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ TEST_F(ExtProcLoggingInfoTest, GetField) {
108108
EXPECT_TRUE(absl::holds_alternative<absl::monostate>(logging_info_.getField("non_existent")));
109109
}
110110

111+
TEST_F(ExtProcLoggingInfoTest, ProcessingEffectsConst) {
112+
const auto& const_logging_info = logging_info_;
113+
114+
// Exercise both directions
115+
EXPECT_EQ(Effect::None,
116+
const_logging_info.processingEffects(TrafficDirection::INBOUND).header_effect_);
117+
EXPECT_EQ(Effect::None,
118+
const_logging_info.processingEffects(TrafficDirection::OUTBOUND).header_effect_);
119+
}
120+
111121
} // namespace
112122
} // namespace ExternalProcessing
113123
} // namespace HttpFilters

0 commit comments

Comments
 (0)