Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ bind(
git_repository(
name = "envoy",
remote = "https://github.com/lyft/envoy.git",
commit = "f10afbaaa6445862bc7db27f9c0d6c2914e8cfd7",
commit = "d0ff5a35e4ad844cd21446dab9701843490c5f01",
)

load("@envoy//bazel:repositories.bzl", "envoy_dependencies")
Expand Down
2 changes: 1 addition & 1 deletion src/envoy/transcoding/filter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class GrpcHttpJsonTranscodingFilterTest : public testing::Test {
filter_.setEncoderFilterCallbacks(encoder_callbacks_);
}

const Json::ObjectPtr bookstoreJson() {
const Json::ObjectSharedPtr bookstoreJson() {
std::string descriptor_path = TestEnvironment::runfilesPath(
"src/envoy/transcoding/test/bookstore.descriptor");
std::string json_string = "{\"proto_descriptor\": \"" + descriptor_path +
Expand Down
276 changes: 231 additions & 45 deletions src/envoy/transcoding/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,25 @@
* limitations under the License.
*/

#include "test/integration/integration.h"
#include "common/grpc/codec.h"
#include "common/grpc/common.h"
#include "common/http/message_impl.h"
#include "src/envoy/transcoding/test/bookstore.pb.h"

#include "google/protobuf/stubs/status.h"
#include "google/protobuf/text_format.h"
#include "google/protobuf/util/message_differencer.h"
#include "gtest/gtest.h"

#include "test/integration/integration.h"
#include "test/mocks/http/mocks.h"

using google::protobuf::util::MessageDifferencer;
using google::protobuf::util::Status;
using google::protobuf::Empty;
using google::protobuf::Message;
using google::protobuf::TextFormat;

namespace Envoy {
namespace {

Expand All @@ -44,64 +56,238 @@ class TranscodingIntegrationTest : public BaseIntegrationTest,
test_server_.reset();
fake_upstreams_.clear();
}
};

TEST_F(TranscodingIntegrationTest, BasicUnary) {
IntegrationCodecClientPtr codec_client;
FakeHttpConnectionPtr fake_upstream_connection;
IntegrationStreamDecoderPtr response(
new IntegrationStreamDecoder(*dispatcher_));
protected:
template <class RequestType, class ResponseType>
void testTranscoding(Http::HeaderMap&& request_headers,
const std::string& request_body,
const std::vector<std::string>& grpc_request_messages,
const std::vector<std::string>& grpc_response_messages,
const Status& grpc_status,
Http::HeaderMap&& response_headers,
const std::string& response_body) {
IntegrationCodecClientPtr codec_client;
FakeHttpConnectionPtr fake_upstream_connection;
IntegrationStreamDecoderPtr response(
new IntegrationStreamDecoder(*dispatcher_));
FakeStreamPtr request_stream;

codec_client =
makeHttpConnection(lookupPort("http"), Http::CodecClient::Type::HTTP1);

if (!request_body.empty()) {
Http::StreamEncoder& encoder =
codec_client->startRequest(request_headers, *response);
Buffer::OwnedImpl body(request_body);
codec_client->sendData(encoder, body, true);
} else {
codec_client->makeHeaderOnlyRequest(request_headers, *response);
}

fake_upstream_connection =
fake_upstreams_[0]->waitForHttpConnection(*dispatcher_);
if (!grpc_request_messages.empty()) {
request_stream = fake_upstream_connection->waitForNewStream();
request_stream->waitForEndStream(*dispatcher_);

Grpc::Decoder grpc_decoder;
std::vector<Grpc::Frame> frames;
EXPECT_TRUE(grpc_decoder.decode(request_stream->body(), frames));
EXPECT_EQ(grpc_request_messages.size(), frames.size());

for (size_t i = 0; i < grpc_request_messages.size(); ++i) {
RequestType actual_message;
if (frames[i].length_ > 0) {
EXPECT_TRUE(actual_message.ParseFromArray(
frames[i].data_->linearize(frames[i].length_),
frames[i].length_));
}
RequestType expected_message;
EXPECT_TRUE(TextFormat::ParseFromString(grpc_request_messages[i],
&expected_message));

EXPECT_TRUE(
MessageDifferencer::Equivalent(expected_message, actual_message));
}
}

if (request_stream) {
Http::TestHeaderMapImpl response_headers;
response_headers.insertStatus().value(200);
response_headers.insertContentType().value(
std::string("application/grpc"));
if (grpc_response_messages.empty()) {
response_headers.insertGrpcStatus().value(grpc_status.error_code());
response_headers.insertGrpcMessage().value(grpc_status.error_message());
request_stream->encodeHeaders(response_headers, true);
} else {
request_stream->encodeHeaders(response_headers, false);
for (const auto& response_message_str : grpc_response_messages) {
ResponseType response_message;
EXPECT_TRUE(TextFormat::ParseFromString(response_message_str,
&response_message));
auto buffer = Grpc::Common::serializeBody(response_message);
request_stream->encodeData(*buffer, false);
}
Http::TestHeaderMapImpl response_trailers;
response_trailers.insertGrpcStatus().value(grpc_status.error_code());
response_trailers.insertGrpcMessage().value(
grpc_status.error_message());
request_stream->encodeTrailers(response_trailers);
}
EXPECT_TRUE(request_stream->complete());
}

response->waitForEndStream();
EXPECT_TRUE(response->complete());
response_headers.iterate(
[](const Http::HeaderEntry& entry, void* context) -> void {
IntegrationStreamDecoder* response =
static_cast<IntegrationStreamDecoder*>(context);
Http::LowerCaseString lower_key{entry.key().c_str()};
EXPECT_STREQ(entry.value().c_str(),
response->headers().get(lower_key)->value().c_str());
},
response.get());
if (!response_body.empty()) {
EXPECT_EQ(response_body, response->body());
}

codec_client->close();
fake_upstream_connection->close();
fake_upstream_connection->waitForDisconnect();
}
};

codec_client =
makeHttpConnection(lookupPort("http"), Http::CodecClient::Type::HTTP1);
Http::StreamEncoder& encoder = codec_client->startRequest(
TEST_F(TranscodingIntegrationTest, UnaryPost) {
testTranscoding<bookstore::CreateShelfRequest, bookstore::Shelf>(
Http::TestHeaderMapImpl{{":method", "POST"},
{":path", "/shelf"},
{":authority", "host"},
{"content-type", "application/json"}},
*response);
Buffer::OwnedImpl request_data{"{\"theme\": \"Children\"}"};
codec_client->sendData(encoder, request_data, true);

fake_upstream_connection =
fake_upstreams_[0]->waitForHttpConnection(*dispatcher_);
FakeStreamPtr request_stream = fake_upstream_connection->waitForNewStream();

request_stream->waitForEndStream(*dispatcher_);
R"({"theme": "Children"})", {R"(shelf { theme: "Children" })"},
{R"(id: 20 theme: "Children" )"}, Status::OK,
Http::TestHeaderMapImpl{{":status", "200"},
{"content-type", "application/json"}},
R"({"id":"20","theme":"Children"})");
}

Grpc::Decoder grpc_decoder;
std::vector<Grpc::Frame> frames;
grpc_decoder.decode(request_stream->body(), frames);
EXPECT_EQ(1, frames.size());
TEST_F(TranscodingIntegrationTest, UnaryGet) {
testTranscoding<Empty, bookstore::ListShelvesResponse>(
Http::TestHeaderMapImpl{
{":method", "GET"}, {":path", "/shelves"}, {":authority", "host"}},
"", {""}, {R"(shelves { id: 20 theme: "Children" }
shelves { id: 1 theme: "Foo" } )"},
Status::OK, Http::TestHeaderMapImpl{{":status", "200"},
{"content-type", "application/json"}},
R"({"shelves":[{"id":"20","theme":"Children"},{"id":"1","theme":"Foo"}]})");
}

bookstore::CreateShelfRequest csr;
csr.ParseFromArray(frames[0].data_->linearize(frames[0].length_),
frames[0].length_);
EXPECT_EQ("Children", csr.shelf().theme());
TEST_F(TranscodingIntegrationTest, UnaryDelete) {
testTranscoding<bookstore::DeleteBookRequest, Empty>(
Http::TestHeaderMapImpl{{":method", "DELETE"},
{":path", "/shelves/456/books/123"},
{":authority", "host"}},
"", {"shelf: 456 book: 123"}, {""}, Status::OK,
Http::TestHeaderMapImpl{{":status", "200"},
{"content-type", "application/json"}},
"{}");
}

request_stream->encodeHeaders(
Http::TestHeaderMapImpl{{"content-type", "application/grpc"},
{":status", "200"}},
false);
TEST_F(TranscodingIntegrationTest, BindingAndBody) {
testTranscoding<bookstore::CreateBookRequest, bookstore::Book>(
Http::TestHeaderMapImpl{{":method", "POST"},
{":path", "/shelves/1/books"},
{":authority", "host"}},
R"({"author" : "Leo Tolstoy", "title" : "War and Peace"})",
{R"(shelf: 1 book { author: "Leo Tolstoy" title: "War and Peace" })"},
{
R"(id: 3 author: "Leo Tolstoy" title: "War and Peace")",
},
Status::OK, Http::TestHeaderMapImpl{{":status", "200"},
{"content-type", "application/json"}},
R"({"id":"3","author":"Leo Tolstoy","title":"War and Peace"})");
}

bookstore::Shelf response_pb;
response_pb.set_id(20);
response_pb.set_theme("Children");
TEST_F(TranscodingIntegrationTest, ServerStreamingGet) {
testTranscoding<bookstore::ListBooksRequest, bookstore::Book>(
Http::TestHeaderMapImpl{{":method", "GET"},
{":path", "/shelves/1/books"},
{":authority", "host"}},
"", {"shelf: 1"},
{R"(id: 1 author: "Neal Stephenson" title: "Readme")",
R"(id: 2 author: "George R.R. Martin" title: "A Game of Thrones")"},
Status::OK, Http::TestHeaderMapImpl{{":status", "200"},
{"content-type", "application/json"}},
R"([{"id":"1","author":"Neal Stephenson","title":"Readme"})"
R"(,{"id":"2","author":"George R.R. Martin","title":"A Game of Thrones"}])");
}

auto response_data = Grpc::Common::serializeBody(response_pb);
request_stream->encodeData(*response_data, false);
TEST_F(TranscodingIntegrationTest, StreamingPost) {
testTranscoding<bookstore::CreateShelfRequest, bookstore::Shelf>(
Http::TestHeaderMapImpl{{":method", "POST"},
{":path", "/bulk/shelves"},
{":authority", "host"}},
R"([
{ "theme" : "Classics" },
{ "theme" : "Satire" },
{ "theme" : "Russian" },
{ "theme" : "Children" },
{ "theme" : "Documentary" },
{ "theme" : "Mystery" },
])",
{R"(shelf { theme: "Classics" })",
R"(shelf { theme: "Satire" })",
R"(shelf { theme: "Russian" })",
R"(shelf { theme: "Children" })",
R"(shelf { theme: "Documentary" })",
R"(shelf { theme: "Mystery" })"},
{R"(id: 3 theme: "Classics")",
R"(id: 4 theme: "Satire")",
R"(id: 5 theme: "Russian")",
R"(id: 6 theme: "Children")",
R"(id: 7 theme: "Documentary")",
R"(id: 8 theme: "Mystery")"},
Status::OK, Http::TestHeaderMapImpl{{":status", "200"},
{"content-type", "application/json"}},
R"([{"id":"3","theme":"Classics"})"
R"(,{"id":"4","theme":"Satire"})"
R"(,{"id":"5","theme":"Russian"})"
R"(,{"id":"6","theme":"Children"})"
R"(,{"id":"7","theme":"Documentary"})"
R"(,{"id":"8","theme":"Mystery"}])");
}

request_stream->encodeTrailers(
Http::TestHeaderMapImpl{{"grpc-status", "0"}, {"grpc-message", ""}});
TEST_F(TranscodingIntegrationTest, InvalidJson) {
testTranscoding<bookstore::CreateShelfRequest, bookstore::Shelf>(
Http::TestHeaderMapImpl{
{":method", "POST"}, {":path", "/shelf"}, {":authority", "host"}},
R"(INVALID_JSON)", {}, {}, Status::OK,
Http::TestHeaderMapImpl{{":status", "400"},
{"content-type", "text/plain"}},
"Unexpected token.\n"
"INVALID_JSON\n"
"^");

response->waitForEndStream();
EXPECT_TRUE(request_stream->complete());
EXPECT_TRUE(response->complete());
EXPECT_EQ("{\"id\":\"20\",\"theme\":\"Children\"}", response->body());
testTranscoding<bookstore::CreateShelfRequest, bookstore::Shelf>(
Http::TestHeaderMapImpl{
{":method", "POST"}, {":path", "/shelf"}, {":authority", "host"}},
R"({ "theme" : "Children")", {}, {}, Status::OK,
Http::TestHeaderMapImpl{{":status", "400"},
{"content-type", "text/plain"}},
"Unexpected end of string. Expected , or } after key:value pair.\n"
"\n"
"^");

codec_client->close();
fake_upstream_connection->close();
fake_upstream_connection->waitForDisconnect();
testTranscoding<bookstore::CreateShelfRequest, bookstore::Shelf>(
Http::TestHeaderMapImpl{
{":method", "POST"}, {":path", "/shelf"}, {":authority", "host"}},
R"({ "theme" "Children" })", {}, {}, Status::OK,
Http::TestHeaderMapImpl{{":status", "400"},
{"content-type", "text/plain"}},
"Expected : between key:value pair.\n"
"{ \"theme\" \"Children\" }\n"
" ^");
}

} // namespace
Expand Down
19 changes: 18 additions & 1 deletion src/envoy/transcoding/test/bookstore.proto
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import "google/protobuf/empty.proto";
service Bookstore {
// Returns a list of all shelves in the bookstore.
rpc ListShelves(google.protobuf.Empty) returns (ListShelvesResponse) {
option (google.api.http) = {
get: "/shelves"
};
}
// Creates a new shelf in the bookstore.
rpc CreateShelf(CreateShelfRequest) returns (Shelf) {
Expand All @@ -37,6 +40,10 @@ service Bookstore {
}
// Creates multiple shelves with one streaming call
rpc BulkCreateShelf(stream CreateShelfRequest) returns (stream Shelf) {
option (google.api.http) = {
post: "/bulk/shelves"
body: "shelf"
};
}
// Returns a specific bookstore shelf.
rpc GetShelf(GetShelfRequest) returns (Shelf) {
Expand All @@ -46,15 +53,25 @@ service Bookstore {
}
// Returns a list of books on a shelf.
rpc ListBooks(ListBooksRequest) returns (stream Book) {
option (google.api.http) = {
get: "/shelves/{shelf}/books"
};
}
// Creates a new book.
rpc CreateBook(CreateBookRequest) returns (Book) {
option (google.api.http) = {
post: "/shelves/{shelf}/books"
body: "book"
};
}
// Returns a specific book.
rpc GetBook(GetBookRequest) returns (Book) {
}
// Deletes a book from a shelf.
rpc DeleteBook(stream DeleteBookRequest) returns (google.protobuf.Empty) {
rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete: "/shelves/{shelf}/books/{book}"
};
}
}

Expand Down