blob: bcb0439a1eb5eaecf2fbe0d891d90b1239409d06 [file] [log] [blame]
#include "proxy_redfishv1_impl.h"
#include <memory>
#include <string>
#include <utility>
#include "gmock.h"
#include "gunit.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "redfish_query_engine/protobuf/parse.h"
#include "redfish_query_engine/redfish_v1.grpc.pb.h"
#include "redfish_query_engine/redfish_v1.pb.h"
#include "grpcpp/channel.h"
#include "grpcpp/client_context.h"
#include "grpcpp/server.h"
#include "grpcpp/server_builder.h"
#include "grpcpp/support/channel_arguments.h"
#include "deferred_status.h"
#include "mock_redfish_plugin.h"
#include "proxy.h"
#include "proxy_config.pb.h"
#include "redfish_plugin.h"
#include "request_response.h"
#include "voyager/deferrable_priority_queue.hpp"
#include "util/task/status_macros.h"
// TODO: b/290052834 - Move some test coverage from redfish_test.cc, add more
// tests.
namespace {
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::DoAll;
using ::testing::EqualsProto;
using ::testing::Field;
using ::testing::Pair;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::SaveArgPointee;
using ::testing::status::StatusIs;
using ::milotic::Proxy;
using ::milotic::ProxyRequest;
using ::milotic::ProxyResponse;
using ::milotic::RedfishPlugin;
using MockPlugin = ::milotic::MockRedfishPlugin;
struct TestServer {
std::unique_ptr<Proxy> proxy;
MockPlugin& plugin;
std::shared_ptr<grpc::Channel> channel;
std::unique_ptr<redfish::v1::grpc_gen::RedfishV1::Stub> stub;
};
absl::StatusOr<TestServer> CreateProxyService(
absl::string_view endpoint, voyager::DeferrablePriorityQueue* queue,
milotic_grpc_proxy::AuthorizationPolicy authorization_policy = ecclesia::
ParseTextAsProtoOrDie<milotic_grpc_proxy::AuthorizationPolicy>(R"pb(
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
})pb")) {
std::unique_ptr<RedfishPlugin> plugins[] = {std::make_unique<MockPlugin>()};
auto& plugin = static_cast<MockPlugin&>(*plugins[0]);
EXPECT_CALL(plugin, Initialize).WillOnce(Return(absl::OkStatus()));
TestServer test_server{
.proxy = std::make_unique<Proxy>("test_endpoint", plugins, queue,
Proxy::Resources{},
std::move(authorization_policy)),
.plugin = plugin};
RETURN_IF_ERROR(test_server.proxy->AddService(
milotic_grpc_proxy::RedfishV1Options::default_instance()));
RETURN_IF_ERROR(test_server.proxy->ConfigGrpcAndStart(
milotic_grpc_proxy::GrpcConfiguration(),
[](const std::string&) { return true; }));
test_server.channel = test_server.proxy->grpc_server()->InProcessChannel(
grpc::ChannelArguments());
test_server.stub =
redfish::v1::grpc_gen::RedfishV1::NewStub(test_server.channel);
return test_server;
}
TEST(ProxyRedfishV1ImplTest, RequestHttpConversionWorks) {
voyager::DeferrablePriorityQueue queue(1);
ASSERT_OK_AND_ASSIGN(TestServer test_server,
CreateProxyService("test_endpoint", &queue));
EXPECT_CALL(test_server.plugin, PreprocessRequest)
.WillRepeatedly(Return(RedfishPlugin::RequestAction::kHandle));
ProxyRequest converted_http_request;
EXPECT_CALL(test_server.plugin,
HandleRequest(RedfishPlugin::RequestVerb::kGet,
Pointee(Field("uri", &ProxyRequest::uri,
"test_endpoint/some/resource"))))
.WillOnce(DoAll(SaveArgPointee<1>(&converted_http_request),
Return(ProxyResponse{
{.code = 200,
.body = R"json({"Name": "test json"})json",
.headers = {{"Content-Type", "application/json"},
{"OData-Version", "4.0"}}}})));
auto request = ecclesia::ParseTextAsProtoOrDie<redfish::v1::Request>(R"pb(
url: "/some/resource"
headers: { key: "X-Test-Header" value: "test_value" }
)pb");
grpc::ClientContext context;
redfish::v1::Response response;
milotic::DeferredStatus status;
test_server.stub->async()->Get(&context, &request, &response,
status.Setter());
ASSERT_OK(queue.ProcessQueue(1));
EXPECT_OK(status.AwaitStatus());
EXPECT_THAT(converted_http_request.headers,
Contains(Pair("X-Test-Header", "test_value")));
EXPECT_THAT(response, EqualsProto(R"pb(
code: 200
headers { key: "content-type", value: "application/json" }
headers { key: "odata-version", value: "4.0" }
json_str: '{"Name": "test json"}'
)pb"));
}
TEST(ProxyRedfishV1ImplTest, ContentTypeHeaderIsAdded) {
voyager::DeferrablePriorityQueue queue(1);
ASSERT_OK_AND_ASSIGN(TestServer test_server,
CreateProxyService("test_endpoint", &queue));
EXPECT_CALL(test_server.plugin, PreprocessRequest)
.WillRepeatedly(Return(RedfishPlugin::RequestAction::kHandle));
EXPECT_CALL(
test_server.plugin,
HandleRequest(
RedfishPlugin::RequestVerb::kPost,
Pointee(AllOf(
Field("uri", &ProxyRequest::uri, "test_endpoint/some/resource"),
Field("headers", &ProxyRequest::headers,
Contains(Pair("Content-Type", "application/json")))))))
.WillOnce(Return(
ProxyResponse{{.code = 200,
.body = R"json({"Name": "test json response"})json",
.headers = {{"Content-Type", "application/json"}}}}));
auto request = ecclesia::ParseTextAsProtoOrDie<redfish::v1::Request>(R"pb(
url: "/some/resource"
json_str: "{\"Name\": \"test json request\"}"
)pb");
grpc::ClientContext context;
redfish::v1::Response response;
milotic::DeferredStatus status;
test_server.stub->async()->Post(&context, &request, &response,
status.Setter());
ASSERT_OK(queue.ProcessQueue(1));
EXPECT_OK(status.AwaitStatus());
}
TEST(ProxyRedfishV1ImplTest, HostHeaderIsReplaced) {
voyager::DeferrablePriorityQueue queue(1);
ASSERT_OK_AND_ASSIGN(TestServer test_server,
CreateProxyService("test_endpoint", &queue));
EXPECT_CALL(test_server.plugin, PreprocessRequest)
.WillRepeatedly(Return(RedfishPlugin::RequestAction::kHandle));
EXPECT_CALL(
test_server.plugin,
HandleRequest(
RedfishPlugin::RequestVerb::kGet,
Pointee(AllOf(
Field("uri", &ProxyRequest::uri, "test_endpoint/some/resource"),
Field("headers", &ProxyRequest::headers,
Contains(Pair("Host", "test_endpoint")))))))
.WillOnce(Return(
ProxyResponse{{.code = 200,
.body = R"json({"Name": "test json"})json",
.headers = {{"Content-Type", "application/json"}}}}));
auto request = ecclesia::ParseTextAsProtoOrDie<redfish::v1::Request>(R"pb(
url: "/some/resource"
headers: { key: "host", value: "unused_host" }
)pb");
grpc::ClientContext context;
redfish::v1::Response response;
milotic::DeferredStatus status;
test_server.stub->async()->Get(&context, &request, &response,
status.Setter());
ASSERT_OK(queue.ProcessQueue(1));
EXPECT_OK(status.AwaitStatus());
}
TEST(ProxyRedfishV1ImplTest, ODataNonJsonIsParsedAsOctetStream) {
voyager::DeferrablePriorityQueue queue(1);
ASSERT_OK_AND_ASSIGN(TestServer test_server,
CreateProxyService("test_endpoint", &queue));
EXPECT_CALL(test_server.plugin, PreprocessRequest)
.WillRepeatedly(Return(RedfishPlugin::RequestAction::kHandle));
EXPECT_CALL(
test_server.plugin,
HandleRequest(
RedfishPlugin::RequestVerb::kGet,
Pointee(AllOf(
Field("uri", &ProxyRequest::uri, "test_endpoint/some/resource"),
Field("headers", &ProxyRequest::headers,
Contains(Pair("Host", "test_endpoint")))))))
.WillOnce(Return(
ProxyResponse{{.code = 200,
.body = "some data",
.headers = {
{"OData-Version", "4.0"},
{"Content-Type", "application/octet-stream"},
}}}));
auto request = ecclesia::ParseTextAsProtoOrDie<redfish::v1::Request>(R"pb(
url: "/some/resource"
headers: { key: "host", value: "unused_host" }
)pb");
grpc::ClientContext context;
redfish::v1::Response response;
milotic::DeferredStatus status;
test_server.stub->async()->Get(&context, &request, &response,
status.Setter());
ASSERT_OK(queue.ProcessQueue(1));
EXPECT_OK(status.AwaitStatus());
EXPECT_FALSE(response.has_json_str());
EXPECT_EQ(response.octet_stream(), "some data");
}
TEST(ProxyRedfishV1ImplTest, ODataNoContentTypeIsParsedAsJson) {
voyager::DeferrablePriorityQueue queue(1);
ASSERT_OK_AND_ASSIGN(TestServer test_server,
CreateProxyService("test_endpoint", &queue));
EXPECT_CALL(test_server.plugin, PreprocessRequest)
.WillRepeatedly(Return(RedfishPlugin::RequestAction::kHandle));
EXPECT_CALL(
test_server.plugin,
HandleRequest(
RedfishPlugin::RequestVerb::kGet,
Pointee(AllOf(
Field("uri", &ProxyRequest::uri, "test_endpoint/some/resource"),
Field("headers", &ProxyRequest::headers,
Contains(Pair("Host", "test_endpoint")))))))
.WillOnce(Return(
ProxyResponse{{.code = 200,
.body = R"json({"Name": "test json response"})json",
.headers = {
{"OData-Version", "4.0"},
}}}));
auto request = ecclesia::ParseTextAsProtoOrDie<redfish::v1::Request>(R"pb(
url: "/some/resource"
headers: { key: "host", value: "unused_host" }
)pb");
grpc::ClientContext context;
redfish::v1::Response response;
milotic::DeferredStatus status;
test_server.stub->async()->Get(&context, &request, &response,
status.Setter());
ASSERT_OK(queue.ProcessQueue(1));
EXPECT_OK(status.AwaitStatus());
EXPECT_FALSE(response.has_octet_stream());
EXPECT_EQ(response.json_str(), R"json({"Name": "test json response"})json");
}
TEST(ProxyRedfishV1ImplTest, UnauthorizedResourceAccessDenied) {
voyager::DeferrablePriorityQueue queue(1);
ASSERT_OK_AND_ASSIGN(
TestServer test_server,
CreateProxyService("test_endpoint", &queue,
ecclesia::ParseTextAsProtoOrDie<
milotic_grpc_proxy::AuthorizationPolicy>(R"pb(
mappings: {
name: "mapping1"
allow: {
key: "test_rule"
value: { permission_id: "test-permission" }
}
resource_path: "/some/protected/resource"
}
)pb")));
EXPECT_CALL(test_server.plugin, PreprocessRequest).Times(0);
auto request = ecclesia::ParseTextAsProtoOrDie<redfish::v1::Request>(R"pb(
url: "/some/protected/resource"
)pb");
grpc::ClientContext context;
redfish::v1::Response response;
milotic::DeferredStatus status;
test_server.stub->async()->Get(&context, &request, &response,
status.Setter());
EXPECT_THAT(status.AwaitStatus(),
StatusIs(absl::StatusCode::kPermissionDenied));
}
} // namespace