| #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 |