//
// Copyright 2022 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include <google/protobuf/any.pb.h>
#include <google/protobuf/duration.pb.h>
#include <google/protobuf/wrappers.pb.h>
#include <grpc/grpc.h>
#include <grpc/status.h>
#include <grpc/support/json.h>
#include <grpcpp/impl/codegen/config_protobuf.h>

#include <string>
#include <utility>
#include <variant>
#include <vector>

#include "envoy/config/core/v3/address.pb.h"
#include "envoy/config/core/v3/base.pb.h"
#include "envoy/config/core/v3/extension.pb.h"
#include "envoy/config/rbac/v3/rbac.pb.h"
#include "envoy/config/route/v3/route.pb.h"
#include "envoy/extensions/common/matching/v3/extension_matcher.pb.h"
#include "envoy/extensions/filters/common/fault/v3/fault.pb.h"
#include "envoy/extensions/filters/common/matcher/action/v3/skip_action.pb.h"
#include "envoy/extensions/filters/http/composite/v3/composite.pb.h"
#include "envoy/extensions/filters/http/fault/v3/fault.pb.h"
#include "envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.pb.h"
#include "envoy/extensions/filters/http/rbac/v3/rbac.pb.h"
#include "envoy/extensions/filters/http/router/v3/router.pb.h"
#include "envoy/extensions/filters/http/stateful_session/v3/stateful_session.pb.h"
#include "envoy/extensions/http/stateful_session/cookie/v3/cookie.pb.h"
#include "envoy/type/http/v3/cookie.pb.h"
#include "envoy/type/matcher/v3/http_inputs.pb.h"
#include "envoy/type/matcher/v3/path.pb.h"
#include "envoy/type/matcher/v3/regex.pb.h"
#include "envoy/type/matcher/v3/string.pb.h"
#include "envoy/type/v3/percent.pb.h"
#include "envoy/type/v3/range.pb.h"
#include "src/core/ext/filters/fault_injection/fault_injection_filter.h"
#include "src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h"
#include "src/core/ext/filters/rbac/rbac_filter.h"
#include "src/core/ext/filters/rbac/rbac_service_config_parser.h"
#include "src/core/ext/filters/stateful_session/stateful_session_filter.h"
#include "src/core/filter/composite/composite_filter.h"
#include "src/core/lib/iomgr/error.h"
#include "src/core/util/crash.h"
#include "src/core/util/grpc_check.h"
#include "src/core/util/json/json_writer.h"
#include "src/core/util/ref_counted_ptr.h"
#include "src/core/xds/grpc/xds_bootstrap_grpc.h"
#include "src/core/xds/grpc/xds_http_filter.h"
#include "src/core/xds/grpc/xds_http_filter_registry.h"
#include "src/core/xds/xds_client/xds_client.h"
#include "test/core/test_util/scoped_env_var.h"
#include "test/core/test_util/test_config.h"
#include "upb/mem/arena.hpp"
#include "upb/reflection/def.hpp"
#include "xds/type/v3/typed_struct.pb.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "absl/strings/strip.h"

// IWYU pragma: no_include <google/protobuf/message.h>

namespace grpc_core {
namespace testing {
namespace {

using ::envoy::extensions::common::matching::v3::ExtensionWithMatcher;
using ::envoy::extensions::common::matching::v3::ExtensionWithMatcherPerRoute;
using ::envoy::extensions::filters::common::matcher::action::v3::SkipFilter;
using ::envoy::extensions::filters::http::composite::v3::Composite;
using ::envoy::extensions::filters::http::composite::v3::ExecuteFilterAction;
using ::envoy::extensions::filters::http::fault::v3::HTTPFault;
using ::envoy::extensions::filters::http::gcp_authn::v3::GcpAuthnFilterConfig;
using ::envoy::extensions::filters::http::rbac::v3::RBAC;
using ::envoy::extensions::filters::http::rbac::v3::RBACPerRoute;
using ::envoy::extensions::filters::http::router::v3::Router;
using ::envoy::extensions::filters::http::stateful_session::v3::StatefulSession;
using ::envoy::extensions::filters::http::stateful_session::v3::
    StatefulSessionPerRoute;
using ::envoy::extensions::http::stateful_session::cookie::v3::
    CookieBasedSessionState;
using ::envoy::type::matcher::v3::HttpRequestHeaderMatchInput;

//
// base class for filter tests
//

class XdsHttpFilterTest : public ::testing::Test {
 protected:
  XdsHttpFilterTest()
      : xds_client_(MakeXdsClient()),
        decode_context_{xds_client_.get(), xds_server_, upb_def_pool_.ptr(),
                        upb_arena_.ptr()} {}

  static RefCountedPtr<XdsClient> MakeXdsClient() {
    grpc_error_handle error;
    auto bootstrap = GrpcXdsBootstrap::Create(
        "{\n"
        "  \"xds_servers\": [\n"
        "    {\n"
        "      \"server_uri\": \"xds.example.com\",\n"
        "      \"channel_creds\": [\n"
        "        {\"type\": \"google_default\"}\n"
        "      ]\n"
        "    }\n"
        "  ]\n"
        "}");
    if (!bootstrap.ok()) {
      Crash(absl::StrFormat("Error parsing bootstrap: %s",
                            bootstrap.status().ToString().c_str()));
    }
    return MakeRefCounted<XdsClient>(std::move(*bootstrap),
                                     /*transport_factory=*/nullptr,
                                     /*event_engine=*/nullptr,
                                     /*metrics_reporter=*/nullptr, "foo agent",
                                     "foo version");
  }

  XdsExtension MakeXdsExtension(const grpc::protobuf::Message& message) {
    google::protobuf::Any any;
    any.PackFrom(message);
    type_url_storage_ =
        std::string(absl::StripPrefix(any.type_url(), "type.googleapis.com/"));
    serialized_storage_ = std::string(any.value());
    ValidationErrors::ScopedField field(
        &errors_, absl::StrCat("http_filter.value[", type_url_storage_, "]"));
    XdsExtension extension;
    extension.type = absl::string_view(type_url_storage_);
    extension.value = absl::string_view(serialized_storage_);
    extension.validation_fields.push_back(std::move(field));
    return extension;
  }

  const XdsHttpFilterImpl* GetFilter(absl::string_view type) {
    return registry_.GetFilterForTopLevelType(
        absl::StripPrefix(type, "type.googleapis.com/"));
  }

  GrpcXdsServer xds_server_;
  RefCountedPtr<XdsClient> xds_client_;
  upb::DefPool upb_def_pool_;
  upb::Arena upb_arena_;
  XdsResourceType::DecodeContext decode_context_;
  XdsHttpFilterRegistry registry_;
  ValidationErrors errors_;
  std::string type_url_storage_;
  std::string serialized_storage_;
};

//
// XdsHttpFilterRegistry tests
//

using XdsHttpFilterRegistryTest = XdsHttpFilterTest;

TEST_F(XdsHttpFilterRegistryTest, Basic) {
  // Start with an empty registry.
  registry_ = XdsHttpFilterRegistry(/*register_builtins=*/false);
  // Returns null when a filter has not yet been registered.
  XdsExtension extension = MakeXdsExtension(Router());
  EXPECT_EQ(GetFilter(extension.type), nullptr);
  // Now register the filter.
  auto filter = std::make_unique<XdsHttpRouterFilter>();
  auto* filter_ptr = filter.get();
  registry_.RegisterFilter(std::move(filter));
  // And check that it is now present.
  EXPECT_EQ(GetFilter(extension.type), filter_ptr);
}

using XdsHttpFilterRegistryDeathTest = XdsHttpFilterTest;

TEST_F(XdsHttpFilterRegistryDeathTest, DuplicateRegistryFails) {
  GTEST_FLAG_SET(death_test_style, "threadsafe");
  ASSERT_DEATH(
      // The router filter is already in the registry.
      registry_.RegisterFilter(std::make_unique<XdsHttpRouterFilter>()), "");
}

//
// Router filter tests
//

class XdsRouterFilterTest : public XdsHttpFilterTest {
 protected:
  XdsRouterFilterTest() {
    XdsExtension extension = MakeXdsExtension(Router());
    filter_ = GetFilter(extension.type);
    GRPC_CHECK_NE(filter_, nullptr);
  }

  const XdsHttpFilterImpl* filter_;
};

TEST_F(XdsRouterFilterTest, Accessors) {
  EXPECT_EQ(filter_->ConfigProtoName(),
            "envoy.extensions.filters.http.router.v3.Router");
  EXPECT_EQ(filter_->OverrideConfigProtoName(), "");
  EXPECT_EQ(filter_->channel_filter(), nullptr);
  EXPECT_TRUE(filter_->IsSupportedOnClients());
  EXPECT_TRUE(filter_->IsSupportedOnServers());
  EXPECT_TRUE(filter_->IsTerminalFilter());
}

TEST_F(XdsRouterFilterTest, GenerateFilterConfig) {
  XdsExtension extension = MakeXdsExtension(Router());
  auto config =
      filter_->GenerateFilterConfig("", decode_context_, extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_TRUE(config.has_value());
  EXPECT_EQ(*config, Json()) << JsonDump(*config);
}

TEST_F(XdsRouterFilterTest, GenerateFilterConfigTypedStruct) {
  XdsExtension extension = MakeXdsExtension(Router());
  extension.value = Json();
  auto config =
      filter_->GenerateFilterConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      "errors validating filter config: ["
      "field:http_filter.value[envoy.extensions.filters.http.router.v3.Router] "
      "error:could not parse router filter config]")
      << status;
}

TEST_F(XdsRouterFilterTest, GenerateFilterConfigUnparsable) {
  XdsExtension extension = MakeXdsExtension(Router());
  std::string serialized_resource("\0", 1);
  extension.value = absl::string_view(serialized_resource);
  auto config =
      filter_->GenerateFilterConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      "errors validating filter config: ["
      "field:http_filter.value[envoy.extensions.filters.http.router.v3.Router] "
      "error:could not parse router filter config]")
      << status;
}

TEST_F(XdsRouterFilterTest, GenerateFilterConfigOverride) {
  XdsExtension extension = MakeXdsExtension(Router());
  auto config = filter_->GenerateFilterConfigOverride("", decode_context_,
                                                      extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      "errors validating filter config: ["
      "field:http_filter.value[envoy.extensions.filters.http.router.v3.Router] "
      "error:router filter does not support config override]")
      << status;
}

TEST_F(XdsRouterFilterTest, ParseTopLevelConfig) {
  XdsExtension extension = MakeXdsExtension(Router());
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  EXPECT_EQ(config, nullptr);
}

TEST_F(XdsRouterFilterTest, ParseTopLevelConfigTypedStruct) {
  XdsExtension extension = MakeXdsExtension(Router());
  extension.value = Json();
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      "errors validating filter config: ["
      "field:http_filter.value[envoy.extensions.filters.http.router.v3.Router] "
      "error:could not parse router filter config]")
      << status;
}

TEST_F(XdsRouterFilterTest, ParseTopLevelConfigUnparsable) {
  XdsExtension extension = MakeXdsExtension(Router());
  std::string serialized_resource("\0", 1);
  extension.value = absl::string_view(serialized_resource);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      "errors validating filter config: ["
      "field:http_filter.value[envoy.extensions.filters.http.router.v3.Router] "
      "error:could not parse router filter config]")
      << status;
}

TEST_F(XdsRouterFilterTest, ParseOverrideConfig) {
  XdsExtension extension = MakeXdsExtension(Router());
  auto config =
      filter_->ParseOverrideConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      "errors validating filter config: ["
      "field:http_filter.value[envoy.extensions.filters.http.router.v3.Router] "
      "error:router filter does not support config override]")
      << status;
}

//
// Fault injection filter tests
//

class XdsFaultInjectionFilterTest : public XdsHttpFilterTest {
 protected:
  XdsFaultInjectionFilterTest() {
    XdsExtension extension = MakeXdsExtension(HTTPFault());
    filter_ = GetFilter(extension.type);
    GRPC_CHECK_NE(filter_, nullptr);
  }

  const XdsHttpFilterImpl* filter_;
};

TEST_F(XdsFaultInjectionFilterTest, Accessors) {
  EXPECT_EQ(filter_->ConfigProtoName(),
            "envoy.extensions.filters.http.fault.v3.HTTPFault");
  EXPECT_EQ(filter_->OverrideConfigProtoName(),
            "envoy.extensions.filters.http.fault.v3.HTTPFault");
  EXPECT_EQ(filter_->channel_filter(), &FaultInjectionFilter::kFilterVtable);
  EXPECT_TRUE(filter_->IsSupportedOnClients());
  EXPECT_FALSE(filter_->IsSupportedOnServers());
  EXPECT_FALSE(filter_->IsTerminalFilter());
}

// The fault injection filter accepts the same config proto as both the
// top-level config and the override config, so we want to run all tests
// for both.
class XdsFaultInjectionFilterConfigTest
    : public XdsFaultInjectionFilterTest,
      public ::testing::WithParamInterface<bool> {
 protected:
  RefCountedPtr<const FilterConfig> ParseConfig(XdsExtension extension) {
    if (GetParam()) {
      return filter_->ParseOverrideConfig("", decode_context_, extension,
                                          &errors_);
    }
    return filter_->ParseTopLevelConfig("", decode_context_, extension,
                                        &errors_);
  }
};

INSTANTIATE_TEST_SUITE_P(XdsFaultFilter, XdsFaultInjectionFilterConfigTest,
                         ::testing::Bool());

TEST_P(XdsFaultInjectionFilterConfigTest, ParseEmptyConfig) {
  XdsExtension extension = MakeXdsExtension(HTTPFault());
  auto config = ParseConfig(std::move(extension));
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), FaultInjectionFilter::Config::Type());
  EXPECT_EQ(config->ToString(), "{max_faults=4294967295}");
}

TEST_P(XdsFaultInjectionFilterConfigTest, ParseBasicConfig) {
  HTTPFault fault;
  auto* abort = fault.mutable_abort();
  abort->set_grpc_status(GRPC_STATUS_UNAVAILABLE);
  abort->mutable_percentage()->set_numerator(75);
  auto* delay = fault.mutable_delay();
  auto* fixed_delay = delay->mutable_fixed_delay();
  fixed_delay->set_seconds(1);
  fixed_delay->set_nanos(500000000);
  delay->mutable_percentage()->set_numerator(25);
  fault.mutable_max_active_faults()->set_value(10);
  XdsExtension extension = MakeXdsExtension(fault);
  auto config = ParseConfig(std::move(extension));
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), FaultInjectionFilter::Config::Type());
  EXPECT_EQ(config->ToString(),
            "{abort_code=UNAVAILABLE, "
            "abort_message=\"Fault injected\", "
            "abort_percentage_numerator=75, "
            "abort_percentage_denominator=100, "
            "delay=1500ms, "
            "delay_percentage_numerator=25, "
            "delay_percentage_denominator=100, "
            "max_faults=10}");
}

TEST_P(XdsFaultInjectionFilterConfigTest, ParseHttpAbortCode) {
  HTTPFault fault;
  auto* abort = fault.mutable_abort();
  abort->set_http_status(404);
  XdsExtension extension = MakeXdsExtension(fault);
  auto config = ParseConfig(std::move(extension));
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), FaultInjectionFilter::Config::Type());
  EXPECT_EQ(config->ToString(),
            "{abort_code=UNIMPLEMENTED, "
            "abort_message=\"Fault injected\", "
            "max_faults=4294967295}");
}

TEST_P(XdsFaultInjectionFilterConfigTest, ParseHeaderAbortAndDelay) {
  HTTPFault fault;
  fault.mutable_abort()->mutable_header_abort();
  fault.mutable_delay()->mutable_header_delay();
  XdsExtension extension = MakeXdsExtension(fault);
  auto config = ParseConfig(std::move(extension));
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), FaultInjectionFilter::Config::Type());
  EXPECT_EQ(
      config->ToString(),
      "{abort_code_header=\"x-envoy-fault-abort-grpc-request\", "
      "abort_message=\"Fault injected\", "
      "abort_percentage_header=\"x-envoy-fault-abort-percentage\", "
      "delay_header=\"x-envoy-fault-delay-request\", "
      "delay_percentage_header=\"x-envoy-fault-delay-request-percentage\", "
      "max_faults=4294967295}");
}

TEST_P(XdsFaultInjectionFilterConfigTest, ParseInvalidGrpcStatusCode) {
  HTTPFault fault;
  fault.mutable_abort()->set_grpc_status(17);
  XdsExtension extension = MakeXdsExtension(fault);
  auto config = ParseConfig(std::move(extension));
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value[envoy.extensions.filters.http.fault.v3"
            ".HTTPFault].abort.grpc_status "
            "error:invalid gRPC status code: 17]")
      << status;
}

TEST_P(XdsFaultInjectionFilterConfigTest, ParseInvalidDuration) {
  HTTPFault fault;
  fault.mutable_delay()->mutable_fixed_delay()->set_seconds(315576000001);
  XdsExtension extension = MakeXdsExtension(fault);
  auto config = ParseConfig(std::move(extension));
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value[envoy.extensions.filters.http.fault.v3"
            ".HTTPFault].delay.fixed_delay.seconds "
            "error:value must be in the range [0, 315576000000]]")
      << status;
}

TEST_P(XdsFaultInjectionFilterConfigTest, ParseTypedStruct) {
  XdsExtension extension = MakeXdsExtension(HTTPFault());
  extension.value = Json();
  auto config = ParseConfig(std::move(extension));
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value[envoy.extensions.filters.http.fault.v3"
            ".HTTPFault] error:could not parse fault injection filter config]")
      << status;
}

TEST_P(XdsFaultInjectionFilterConfigTest, ParseUnparsable) {
  XdsExtension extension = MakeXdsExtension(HTTPFault());
  std::string serialized_resource("\0", 1);
  extension.value = absl::string_view(serialized_resource);
  auto config = ParseConfig(std::move(extension));
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value[envoy.extensions.filters.http.fault.v3"
            ".HTTPFault] error:could not parse fault injection filter config]")
      << status;
}

//
// RBAC filter tests
//

class XdsRbacFilterTest : public XdsHttpFilterTest {
 protected:
  XdsRbacFilterTest() {
    XdsExtension extension = MakeXdsExtension(RBAC());
    filter_ = GetFilter(extension.type);
    GRPC_CHECK_NE(filter_, nullptr);
  }

  const XdsHttpFilterImpl* filter_;
};

TEST_F(XdsRbacFilterTest, Accessors) {
  EXPECT_EQ(filter_->ConfigProtoName(),
            "envoy.extensions.filters.http.rbac.v3.RBAC");
  EXPECT_EQ(filter_->OverrideConfigProtoName(),
            "envoy.extensions.filters.http.rbac.v3.RBACPerRoute");
  EXPECT_EQ(filter_->channel_filter(), &RbacFilter::kFilterVtable);
  EXPECT_FALSE(filter_->IsSupportedOnClients());
  EXPECT_TRUE(filter_->IsSupportedOnServers());
  EXPECT_FALSE(filter_->IsTerminalFilter());
}

TEST_F(XdsRbacFilterTest, ModifyChannelArgs) {
  ChannelArgs args = filter_->ModifyChannelArgs(ChannelArgs());
  auto value = args.GetInt(GRPC_ARG_PARSE_RBAC_METHOD_CONFIG);
  ASSERT_TRUE(value.has_value());
  EXPECT_EQ(*value, 1);
}

TEST_F(XdsRbacFilterTest, GenerateFilterConfig) {
  XdsExtension extension = MakeXdsExtension(RBAC());
  auto config =
      filter_->GenerateFilterConfig("", decode_context_, extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_TRUE(config.has_value());
  EXPECT_EQ(*config, Json::FromObject({})) << JsonDump(*config);
}

TEST_F(XdsRbacFilterTest, GenerateFilterConfigTypedStruct) {
  XdsExtension extension = MakeXdsExtension(RBAC());
  extension.value = Json();
  auto config =
      filter_->GenerateFilterConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      "errors validating filter config: ["
      "field:http_filter.value[envoy.extensions.filters.http.rbac.v3.RBAC] "
      "error:could not parse HTTP RBAC filter config]")
      << status;
}

TEST_F(XdsRbacFilterTest, GenerateFilterConfigUnparsable) {
  XdsExtension extension = MakeXdsExtension(RBAC());
  std::string serialized_resource("\0", 1);
  extension.value = absl::string_view(serialized_resource);
  auto config =
      filter_->GenerateFilterConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      "errors validating filter config: ["
      "field:http_filter.value[envoy.extensions.filters.http.rbac.v3.RBAC] "
      "error:could not parse HTTP RBAC filter config]")
      << status;
}

TEST_F(XdsRbacFilterTest, GenerateFilterConfigOverride) {
  XdsExtension extension = MakeXdsExtension(RBACPerRoute());
  auto config = filter_->GenerateFilterConfigOverride("", decode_context_,
                                                      extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_TRUE(config.has_value());
  EXPECT_EQ(*config, Json::FromObject({})) << JsonDump(*config);
}

TEST_F(XdsRbacFilterTest, GenerateFilterConfigOverrideTypedStruct) {
  XdsExtension extension = MakeXdsExtension(RBACPerRoute());
  extension.value = Json();
  auto config = filter_->GenerateFilterConfigOverride("", decode_context_,
                                                      extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value[envoy.extensions.filters.http.rbac.v3"
            ".RBACPerRoute] error:could not parse RBACPerRoute]")
      << status;
}

TEST_F(XdsRbacFilterTest, GenerateFilterConfigOverrideUnparsable) {
  XdsExtension extension = MakeXdsExtension(RBACPerRoute());
  std::string serialized_resource("\0", 1);
  extension.value = absl::string_view(serialized_resource);
  auto config = filter_->GenerateFilterConfigOverride("", decode_context_,
                                                      extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value[envoy.extensions.filters.http.rbac.v3"
            ".RBACPerRoute] error:could not parse RBACPerRoute]")
      << status;
}

TEST_F(XdsRbacFilterTest, GenerateMethodConfig) {
  Json hcm_config = Json::FromObject({{"name", Json::FromString("foo")}});
  auto config = filter_->GenerateMethodConfig(hcm_config, nullptr);
  ASSERT_TRUE(config.ok()) << config.status();
  EXPECT_EQ(config->service_config_field_name, "rbacPolicy");
  EXPECT_EQ(config->element,
            JsonDump(Json::FromObject({{"name", Json::FromString("foo")}})));
}

TEST_F(XdsRbacFilterTest, GenerateServiceConfig) {
  Json config = Json::FromObject({{"foo", Json::FromString("bar")}});
  auto service_config = filter_->GenerateServiceConfig(config);
  ASSERT_TRUE(service_config.ok()) << service_config.status();
  EXPECT_EQ(service_config->service_config_field_name, "");
  EXPECT_EQ(service_config->element, "");
}

// For the RBAC filter, the override config is a superset of the
// top-level config, so we test all of the common fields as input for
// both GenerateFilterConfig() and GenerateFilterConfigOverride().
class XdsRbacFilterConfigTest : public XdsRbacFilterTest,
                                public ::testing::WithParamInterface<bool> {
 protected:
  std::optional<Json> GenerateConfig(RBAC rbac) {
    if (GetParam()) {
      RBACPerRoute rbac_per_route;
      *rbac_per_route.mutable_rbac() = rbac;
      XdsExtension extension = MakeXdsExtension(rbac_per_route);
      return filter_->GenerateFilterConfigOverride("", decode_context_,
                                                   extension, &errors_);
    }
    XdsExtension extension = MakeXdsExtension(rbac);
    return filter_->GenerateFilterConfig("", decode_context_, extension,
                                         &errors_);
  }

  std::string FieldPrefix() {
    return absl::StrCat("http_filter.value[",
                        (GetParam() ? filter_->OverrideConfigProtoName()
                                    : filter_->ConfigProtoName()),
                        "]", (GetParam() ? ".rbac" : ""));
  }
};

INSTANTIATE_TEST_SUITE_P(XdsRbacFilter, XdsRbacFilterConfigTest,
                         ::testing::Bool());

TEST_P(XdsRbacFilterConfigTest, EmptyConfig) {
  auto config = GenerateConfig(RBAC());
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_TRUE(config.has_value());
  EXPECT_EQ(*config, Json::FromObject({})) << JsonDump(*config);
}

TEST_P(XdsRbacFilterConfigTest, AllPermissionTypes) {
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto& policy = (*rules->mutable_policies())["policy_name"];
  // any
  policy.add_permissions()->set_any(true);
  // header exact match with invert
  auto* header = policy.add_permissions()->mutable_header();
  header->set_name("header_name1");
  header->set_exact_match("exact_match");
  header->set_invert_match(true);
  // header regex match
  header = policy.add_permissions()->mutable_header();
  header->set_name("header_name2");
  header->mutable_safe_regex_match()->set_regex("regex_match");
  // header range match
  header = policy.add_permissions()->mutable_header();
  header->set_name("header_name3");
  auto* range = header->mutable_range_match();
  range->set_start(1);
  range->set_end(3);
  // header present match
  header = policy.add_permissions()->mutable_header();
  header->set_name("header_name4");
  header->set_present_match(true);
  // header prefix match
  header = policy.add_permissions()->mutable_header();
  header->set_name("header_name5");
  header->set_prefix_match("prefix_match");
  // header suffix match
  header = policy.add_permissions()->mutable_header();
  header->set_name("header_name6");
  header->set_suffix_match("suffix_match");
  // header contains match
  header = policy.add_permissions()->mutable_header();
  header->set_name("header_name7");
  header->set_contains_match("contains_match");
  // path exact match with ignore_case
  auto* string_matcher =
      policy.add_permissions()->mutable_url_path()->mutable_path();
  string_matcher->set_exact("exact_match");
  string_matcher->set_ignore_case(true);
  // path prefix match
  string_matcher = policy.add_permissions()->mutable_url_path()->mutable_path();
  string_matcher->set_prefix("prefix_match");
  // path suffix match
  string_matcher = policy.add_permissions()->mutable_url_path()->mutable_path();
  string_matcher->set_suffix("suffix_match");
  // path contains match
  string_matcher = policy.add_permissions()->mutable_url_path()->mutable_path();
  string_matcher->set_contains("contains_match");
  // path regex match
  string_matcher = policy.add_permissions()->mutable_url_path()->mutable_path();
  string_matcher->mutable_safe_regex()->set_regex("regex_match");
  // destination IP match with prefix len
  auto* cidr_range = policy.add_permissions()->mutable_destination_ip();
  cidr_range->set_address_prefix("127.0.0");
  cidr_range->mutable_prefix_len()->set_value(24);
  // destination IP match
  cidr_range = policy.add_permissions()->mutable_destination_ip();
  cidr_range->set_address_prefix("10.0.0");
  // destination port match
  policy.add_permissions()->set_destination_port(1234);
  // metadata match
  policy.add_permissions()->mutable_metadata();
  // metadata match with invert
  policy.add_permissions()->mutable_metadata()->set_invert(true);
  // requested server name
  string_matcher = policy.add_permissions()->mutable_requested_server_name();
  string_matcher->set_exact("exact_match");
  // not
  policy.add_permissions()->mutable_not_rule()->set_any(true);
  // and
  policy.add_permissions()->mutable_and_rules()->add_rules()->set_any(true);
  // or
  policy.add_permissions()->mutable_or_rules()->add_rules()->set_any(true);
  auto config = GenerateConfig(rbac);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_TRUE(config.has_value());
  EXPECT_EQ(JsonDump(*config),
            "{\"rules\":{"
            "\"action\":0,"
            "\"policies\":{"
            "\"policy_name\":{"
            "\"permissions\":["
            // any
            "{\"any\":true},"
            // header exact match with invert
            "{\"header\":"
            "{\"exactMatch\":\"exact_match\",\"invertMatch\":true,"
            "\"name\":\"header_name1\"}},"
            // header regex match
            "{\"header\":"
            "{\"invertMatch\":false,\"name\":\"header_name2\","
            "\"safeRegexMatch\":{\"regex\":\"regex_match\"}}},"
            // header range match
            "{\"header\":"
            "{\"invertMatch\":false,\"name\":\"header_name3\","
            "\"rangeMatch\":{\"end\":3,\"start\":1}}},"
            // header present match
            "{\"header\":"
            "{\"invertMatch\":false,\"name\":\"header_name4\","
            "\"presentMatch\":true}},"
            // header prefix match
            "{\"header\":"
            "{\"invertMatch\":false,\"name\":\"header_name5\","
            "\"prefixMatch\":\"prefix_match\"}},"
            // header suffix match
            "{\"header\":"
            "{\"invertMatch\":false,\"name\":\"header_name6\","
            "\"suffixMatch\":\"suffix_match\"}},"
            // header contains match
            "{\"header\":"
            "{\"containsMatch\":\"contains_match\",\"invertMatch\":false,"
            "\"name\":\"header_name7\"}},"
            // path exact match with ignore_case
            "{\"urlPath\":{\"path\":{"
            "\"exact\":\"exact_match\",\"ignoreCase\":true}}},"
            // path prefix match
            "{\"urlPath\":{\"path\":{"
            "\"ignoreCase\":false,\"prefix\":\"prefix_match\"}}},"
            // path suffix match
            "{\"urlPath\":{\"path\":{"
            "\"ignoreCase\":false,\"suffix\":\"suffix_match\"}}},"
            // path contains match
            "{\"urlPath\":{\"path\":{"
            "\"contains\":\"contains_match\",\"ignoreCase\":false}}},"
            // path regex match
            "{\"urlPath\":{\"path\":{"
            "\"ignoreCase\":false,\"safeRegex\":{\"regex\":\"regex_match\"}}}},"
            // destination IP match with prefix len
            "{\"destinationIp\":{"
            "\"addressPrefix\":\"127.0.0\",\"prefixLen\":24}},"
            // destination IP match
            "{\"destinationIp\":{\"addressPrefix\":\"10.0.0\"}},"
            // destination port match
            "{\"destinationPort\":1234},"
            // metadata match
            "{\"metadata\":{\"invert\":false}},"
            // metadata match with invert
            "{\"metadata\":{\"invert\":true}},"
            // requested server name
            "{\"requestedServerName\":{"
            "\"exact\":\"exact_match\",\"ignoreCase\":false}},"
            // not
            "{\"notRule\":{\"any\":true}},"
            // and
            "{\"andRules\":{\"rules\":[{\"any\":true}]}},"
            // or
            "{\"orRules\":{\"rules\":[{\"any\":true}]}}"
            "],"
            "\"principals\":[]"
            "}}}}");
}

TEST_P(XdsRbacFilterConfigTest, AtMaxPermissionDepth) {
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto& policy = (*rules->mutable_policies())["policy_name"];
  // We want to make sure that all of "and", "or", and "not" track
  // depth, so we're including them all here.  We will start out with a
  // single "and" and a single "or" and then use "not" for the rest.
  auto* permission = policy.add_permissions();
  permission = permission->mutable_and_rules()->add_rules();
  permission = permission->mutable_or_rules()->add_rules();
  for (size_t i = 0; i < 14; ++i) {
    permission = permission->mutable_not_rule();
  }
  permission->set_any(true);
  auto config = GenerateConfig(rbac);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_TRUE(config.has_value());
  EXPECT_EQ(JsonDump(*config),
            "{\"rules\":{"
            "\"action\":0,"
            "\"policies\":{"
            "\"policy_name\":{"
            "\"permissions\":["
            "{\"andRules\":{\"rules\":["
            "{\"orRules\":{\"rules\":["
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"notRule\":"
            "{\"any\":true}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "]}}"
            "]}}"
            "],"
            "\"principals\":[]"
            "}}}}");
}

TEST_P(XdsRbacFilterConfigTest, ExceedsMaxPermissionDepth) {
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto& policy = (*rules->mutable_policies())["policy_name"];
  // We want to make sure that all of "and", "or", and "not" track
  // depth, so we're including them all here.  We will start out with a
  // single "and" and a single "or" and then use "not" for the rest.
  auto* permission = policy.add_permissions();
  permission = permission->mutable_and_rules()->add_rules();
  permission = permission->mutable_or_rules()->add_rules();
  for (size_t i = 0; i < 15; ++i) {
    permission = permission->mutable_not_rule();
  }
  permission->set_any(true);
  auto config = GenerateConfig(rbac);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      absl::StrCat("errors validating filter config: [field:", FieldPrefix(),
                   ".rules.policies[policy_name]"
                   ".permissions[0].and_permission.rules[0]"
                   ".or_permission.rules[0]"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule"
                   ".not_rule "
                   "error:exceeded max recursion depth]"))
      << status;
}

TEST_P(XdsRbacFilterConfigTest, AllPrincipalTypes) {
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto& policy = (*rules->mutable_policies())["policy_name"];
  // any
  policy.add_principals()->set_any(true);
  // authenticated principal name
  // (not testing all possible string matchers here, since they're
  // covered in the AllPermissionTypes test above)
  auto* string_matcher = policy.add_principals()
                             ->mutable_authenticated()
                             ->mutable_principal_name();
  string_matcher->set_exact("exact_match");
  // source IP
  auto* cidr_range = policy.add_principals()->mutable_source_ip();
  cidr_range->set_address_prefix("127.0.0");
  // direct remote IP
  cidr_range = policy.add_principals()->mutable_direct_remote_ip();
  cidr_range->set_address_prefix("127.0.1");
  // remote IP
  cidr_range = policy.add_principals()->mutable_remote_ip();
  cidr_range->set_address_prefix("127.0.2");
  // header match
  // (not testing all possible header matchers here, since they're
  // covered in the AllPermissionTypes test above)
  auto* header = policy.add_principals()->mutable_header();
  header->set_name("header_name1");
  header->set_exact_match("exact_match");
  // path match
  // (not testing all possible string matchers here, since they're
  // covered in the AllPermissionTypes test above)
  string_matcher = policy.add_principals()->mutable_url_path()->mutable_path();
  string_matcher->set_exact("exact_match");
  // metadata match
  // (not testing invert here, since it's covered in the AllPermissionTypes
  // test above)
  policy.add_principals()->mutable_metadata();
  // not
  policy.add_principals()->mutable_not_id()->set_any(true);
  // and
  policy.add_principals()->mutable_and_ids()->add_ids()->set_any(true);
  // or
  policy.add_principals()->mutable_or_ids()->add_ids()->set_any(true);
  auto config = GenerateConfig(rbac);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_TRUE(config.has_value());
  EXPECT_EQ(JsonDump(*config),
            "{\"rules\":{"
            "\"action\":0,"
            "\"policies\":{"
            "\"policy_name\":{"
            "\"permissions\":[],"
            "\"principals\":["
            // any
            "{\"any\":true},"
            // authenticated principal name
            "{\"authenticated\":{\"principalName\":{"
            "\"exact\":\"exact_match\",\"ignoreCase\":false}}},"
            // source IP
            "{\"sourceIp\":{\"addressPrefix\":\"127.0.0\"}},"
            // direct remote IP
            "{\"directRemoteIp\":{\"addressPrefix\":\"127.0.1\"}},"
            // remote IP
            "{\"remoteIp\":{\"addressPrefix\":\"127.0.2\"}},"
            // header exact match with invert
            "{\"header\":"
            "{\"exactMatch\":\"exact_match\",\"invertMatch\":false,"
            "\"name\":\"header_name1\"}},"
            // path exact match
            "{\"urlPath\":{\"path\":{"
            "\"exact\":\"exact_match\",\"ignoreCase\":false}}},"
            // metadata match
            "{\"metadata\":{\"invert\":false}},"
            // not
            "{\"notId\":{\"any\":true}},"
            // and
            "{\"andIds\":{\"ids\":[{\"any\":true}]}},"
            // or
            "{\"orIds\":{\"ids\":[{\"any\":true}]}}"
            "]"
            "}}}}");
}

TEST_P(XdsRbacFilterConfigTest, AtMaxPrincipalDepth) {
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto& policy = (*rules->mutable_policies())["policy_name"];
  // We want to make sure that all of "and", "or", and "not" track
  // depth, so we're including them all here.  We will start out with a
  // single "and" and a single "or" and then use "not" for the rest.
  auto* principal = policy.add_principals();
  principal = principal->mutable_and_ids()->add_ids();
  principal = principal->mutable_or_ids()->add_ids();
  for (size_t i = 0; i < 14; ++i) {
    principal = principal->mutable_not_id();
  }
  principal->set_any(true);
  auto config = GenerateConfig(rbac);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_TRUE(config.has_value());
  EXPECT_EQ(JsonDump(*config),
            "{\"rules\":{"
            "\"action\":0,"
            "\"policies\":{"
            "\"policy_name\":{"
            "\"permissions\":[],"
            "\"principals\":["
            "{\"andIds\":{\"ids\":["
            "{\"orIds\":{\"ids\":["
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"notId\":"
            "{\"any\":true}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "}"
            "]}}"
            "]}}"
            "]"
            "}}}}");
}

TEST_P(XdsRbacFilterConfigTest, ExceedsMaxPrincipalDepth) {
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto& policy = (*rules->mutable_policies())["policy_name"];
  // We want to make sure that all of "and", "or", and "not" track
  // depth, so we're including them all here.  We will start out with a
  // single "and" and a single "or" and then use "not" for the rest.
  auto* principal = policy.add_principals();
  principal = principal->mutable_and_ids()->add_ids();
  principal = principal->mutable_or_ids()->add_ids();
  for (size_t i = 0; i < 15; ++i) {
    principal = principal->mutable_not_id();
  }
  principal->set_any(true);
  auto config = GenerateConfig(rbac);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      absl::StrCat("errors validating filter config: [field:", FieldPrefix(),
                   ".rules.policies[policy_name]"
                   ".principals[0].and_ids.ids[0]"
                   ".or_ids.ids[0]"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id"
                   ".not_id "
                   "error:exceeded max recursion depth]"))
      << status;
}

TEST_P(XdsRbacFilterConfigTest, AuditLoggingOptionsIgnoredWithFeatureDisabled) {
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto* logging_options = rules->mutable_audit_logging_options();
  logging_options->set_audit_condition(
      envoy::config::rbac::v3::RBAC_AuditLoggingOptions_AuditCondition_ON_DENY);
  envoy::config::rbac::v3::RBAC_AuditLoggingOptions::AuditLoggerConfig
      logger_config;
  auto* audit_logger = logger_config.mutable_audit_logger();
  audit_logger->mutable_typed_config()->set_type_url(
      "/envoy.extensions.rbac.audit_loggers.stream.v3.StdoutAuditLog");
  *logging_options->add_logger_configs() = logger_config;
  auto config = GenerateConfig(rbac);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_TRUE(config.has_value());
  EXPECT_EQ(JsonDump(*config), "{\"rules\":{\"action\":0}}");
}

TEST_P(XdsRbacFilterConfigTest, AuditLoggingOptions) {
  ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_RBAC_AUDIT_LOGGING");
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto* logging_options = rules->mutable_audit_logging_options();
  logging_options->set_audit_condition(
      envoy::config::rbac::v3::RBAC_AuditLoggingOptions_AuditCondition_ON_DENY);
  envoy::config::rbac::v3::RBAC_AuditLoggingOptions::AuditLoggerConfig
      logger_config;
  auto* audit_logger = logger_config.mutable_audit_logger();
  audit_logger->mutable_typed_config()->set_type_url(
      "/envoy.extensions.rbac.audit_loggers.stream.v3.StdoutAuditLog");
  *logging_options->add_logger_configs() = logger_config;
  auto config = GenerateConfig(rbac);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_TRUE(config.has_value());
  EXPECT_EQ(JsonDump(*config),
            "{\"rules\":{\"action\":0,"
            "\"audit_condition\":1,"
            "\"audit_loggers\":[{\"stdout_logger\":{}}]"
            "}}");
}

TEST_P(XdsRbacFilterConfigTest, InvalidAuditCondition) {
  ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_RBAC_AUDIT_LOGGING");
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto* logging_options = rules->mutable_audit_logging_options();
  logging_options->set_audit_condition(
      static_cast<
          envoy::config::rbac::v3::RBAC_AuditLoggingOptions_AuditCondition>(
          100));
  auto config = GenerateConfig(rbac);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            absl::StrCat("errors validating filter config: ["
                         "field:",
                         FieldPrefix(),
                         ".rules.audit_logging_options.audit_condition "
                         "error:invalid audit condition]"))
      << status;
}

TEST_P(XdsRbacFilterConfigTest, InvalidAuditLoggerConfig) {
  ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_RBAC_AUDIT_LOGGING");
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto* logging_options = rules->mutable_audit_logging_options();
  envoy::config::rbac::v3::RBAC_AuditLoggingOptions::AuditLoggerConfig
      logger_config;
  auto* audit_logger = logger_config.mutable_audit_logger();
  audit_logger->mutable_typed_config()->set_type_url("/foo_logger");
  *logging_options->add_logger_configs() = logger_config;
  auto config = GenerateConfig(rbac);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            absl::StrCat("errors validating filter config: ["
                         "field:",
                         FieldPrefix(),
                         ".rules.audit_logging_options.logger_configs[0].audit_"
                         "logger.typed_config.value[foo_logger] "
                         "error:unsupported audit logger type]"))
      << status;
}

TEST_P(XdsRbacFilterConfigTest, InvalidFieldsInPolicy) {
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto& policy = (*rules->mutable_policies())["policy_name"];
  policy.mutable_condition();
  policy.mutable_checked_condition();
  auto config = GenerateConfig(rbac);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            absl::StrCat("errors validating filter config: ["
                         "field:",
                         FieldPrefix(),
                         ".rules.policies[policy_name].checked_condition "
                         "error:checked condition not supported; "
                         "field:",
                         FieldPrefix(),
                         ".rules.policies[policy_name].condition "
                         "error:condition not supported]"))
      << status;
}

TEST_P(XdsRbacFilterConfigTest, InvalidHeaderMatchers) {
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto& policy = (*rules->mutable_policies())["policy_name"];
  auto* header = policy.add_permissions()->mutable_header();
  header->set_name(":scheme");
  header->set_exact_match("exact_match");
  header = policy.add_principals()->mutable_header();
  header->set_name("grpc-foo");
  header->set_exact_match("exact_match");
  header = policy.add_principals()->mutable_header();
  header->set_name("header_name");
  auto config = GenerateConfig(rbac);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      absl::StrCat("errors validating filter config: ["
                   "field:",
                   FieldPrefix(),
                   ".rules.policies[policy_name].permissions[0].header.name "
                   "error:':scheme' not allowed in header; "
                   "field:",
                   FieldPrefix(),
                   ".rules.policies[policy_name].principals[0].header.name "
                   "error:'grpc-' prefixes not allowed in header; "
                   "field:",
                   FieldPrefix(),
                   ".rules.policies[policy_name].principals[1].header "
                   "error:invalid route header matcher specified]"))
      << status;
}

TEST_P(XdsRbacFilterConfigTest, InvalidStringMatchers) {
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto& policy = (*rules->mutable_policies())["policy_name"];
  policy.add_permissions()->mutable_url_path()->mutable_path();
  policy.add_principals()->mutable_url_path();
  auto config = GenerateConfig(rbac);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      absl::StrCat("errors validating filter config: ["
                   "field:",
                   FieldPrefix(),
                   ".rules.policies[policy_name].permissions[0].url_path.path "
                   "error:invalid match pattern; "
                   "field:",
                   FieldPrefix(),
                   ".rules.policies[policy_name].principals[0].url_path.path "
                   "error:field not present]"))
      << status;
}

TEST_P(XdsRbacFilterConfigTest, InvalidPermissionAndPrincipal) {
  RBAC rbac;
  auto* rules = rbac.mutable_rules();
  rules->set_action(rules->ALLOW);
  auto& policy = (*rules->mutable_policies())["policy_name"];
  policy.add_permissions();
  policy.add_principals();
  auto config = GenerateConfig(rbac);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            absl::StrCat("errors validating filter config: ["
                         "field:",
                         FieldPrefix(),
                         ".rules.policies[policy_name].permissions[0] "
                         "error:invalid rule; "
                         "field:",
                         FieldPrefix(),
                         ".rules.policies[policy_name].principals[0] "
                         "error:invalid rule]"))
      << status;
}

//
// StatefulSession filter tests
//

class XdsStatefulSessionFilterTest : public XdsHttpFilterTest {
 protected:
  void SetUp() override {
    XdsExtension extension = MakeXdsExtension(StatefulSession());
    filter_ = GetFilter(extension.type);
    GRPC_CHECK_NE(filter_, nullptr);
  }

  const XdsHttpFilterImpl* filter_;
};

TEST_F(XdsStatefulSessionFilterTest, Accessors) {
  EXPECT_EQ(
      filter_->ConfigProtoName(),
      "envoy.extensions.filters.http.stateful_session.v3.StatefulSession");
  EXPECT_EQ(filter_->OverrideConfigProtoName(),
            "envoy.extensions.filters.http.stateful_session.v3"
            ".StatefulSessionPerRoute");
  EXPECT_EQ(filter_->channel_filter(), &StatefulSessionFilter::kFilterVtable);
  EXPECT_TRUE(filter_->IsSupportedOnClients());
  EXPECT_FALSE(filter_->IsSupportedOnServers());
  EXPECT_FALSE(filter_->IsTerminalFilter());
}

TEST_F(XdsStatefulSessionFilterTest, ParseOverrideConfigDisabled) {
  StatefulSessionPerRoute stateful_session_per_route;
  stateful_session_per_route.set_disabled(true);
  XdsExtension extension = MakeXdsExtension(stateful_session_per_route);
  auto config =
      filter_->ParseOverrideConfig("", decode_context_, extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), StatefulSessionFilter::Config::Type());
  EXPECT_EQ(config->ToString(), "{}");
}

TEST_F(XdsStatefulSessionFilterTest, ParseTopLevelConfigTypedStruct) {
  XdsExtension extension = MakeXdsExtension(StatefulSession());
  extension.value = Json();
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      "errors validating filter config: ["
      "field:http_filter.value["
      "envoy.extensions.filters.http.stateful_session.v3.StatefulSession] "
      "error:could not parse stateful session filter config]")
      << status;
}

TEST_F(XdsStatefulSessionFilterTest, ParseTopLevelConfigUnparsable) {
  XdsExtension extension = MakeXdsExtension(StatefulSession());
  std::string serialized_resource("\0", 1);
  extension.value = absl::string_view(serialized_resource);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      "errors validating filter config: ["
      "field:http_filter.value["
      "envoy.extensions.filters.http.stateful_session.v3.StatefulSession] "
      "error:could not parse stateful session filter config]")
      << status;
}

TEST_F(XdsStatefulSessionFilterTest, ParseOverrideConfigTypedStruct) {
  XdsExtension extension = MakeXdsExtension(StatefulSessionPerRoute());
  extension.value = Json();
  auto config =
      filter_->ParseOverrideConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.filters.http.stateful_session.v3"
            ".StatefulSessionPerRoute] "
            "error:could not parse stateful session filter override config]")
      << status;
}

TEST_F(XdsStatefulSessionFilterTest, ParseOverrideConfigUnparsable) {
  XdsExtension extension = MakeXdsExtension(StatefulSessionPerRoute());
  std::string serialized_resource("\0", 1);
  extension.value = absl::string_view(serialized_resource);
  auto config =
      filter_->ParseOverrideConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.filters.http.stateful_session.v3"
            ".StatefulSessionPerRoute] "
            "error:could not parse stateful session filter override config]")
      << status;
}

// For the stateful session filter, the override config is a superset of the
// top-level config, so we test all of the common fields as input for both.
class XdsStatefulSessionFilterConfigTest
    : public XdsStatefulSessionFilterTest,
      public ::testing::WithParamInterface<bool> {
 protected:
  RefCountedPtr<const FilterConfig> ParseConfig(
      StatefulSession stateful_session) {
    if (GetParam()) {
      StatefulSessionPerRoute stateful_session_per_route;
      *stateful_session_per_route.mutable_stateful_session() = stateful_session;
      XdsExtension extension = MakeXdsExtension(stateful_session_per_route);
      return filter_->ParseOverrideConfig("", decode_context_, extension,
                                          &errors_);
    }
    XdsExtension extension = MakeXdsExtension(stateful_session);
    return filter_->ParseTopLevelConfig("", decode_context_, extension,
                                        &errors_);
  }

  std::string FieldPrefix() {
    return absl::StrCat("http_filter.value[",
                        (GetParam() ? filter_->OverrideConfigProtoName()
                                    : filter_->ConfigProtoName()),
                        "]", (GetParam() ? ".stateful_session" : ""));
  }
};

INSTANTIATE_TEST_SUITE_P(XdsStatefulSessionFilter,
                         XdsStatefulSessionFilterConfigTest, ::testing::Bool());

TEST_P(XdsStatefulSessionFilterConfigTest, ParseMinimalConfig) {
  CookieBasedSessionState cookie_state;
  cookie_state.mutable_cookie()->set_name("foo");
  StatefulSession stateful_session;
  stateful_session.mutable_session_state()->mutable_typed_config()->PackFrom(
      cookie_state);
  auto config = ParseConfig(stateful_session);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), StatefulSessionFilter::Config::Type());
  EXPECT_EQ(config->ToString(), "{cookie_name=\"foo\"}");
}

TEST_P(XdsStatefulSessionFilterConfigTest, ParsePathAndTtl) {
  CookieBasedSessionState cookie_state;
  auto* cookie = cookie_state.mutable_cookie();
  cookie->set_name("foo");
  cookie->set_path("/service/method");
  cookie->mutable_ttl()->set_seconds(3);
  StatefulSession stateful_session;
  stateful_session.mutable_session_state()->mutable_typed_config()->PackFrom(
      cookie_state);
  auto config = ParseConfig(stateful_session);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), StatefulSessionFilter::Config::Type());
  EXPECT_EQ(config->ToString(),
            "{cookie_name=\"foo\", "
            "path=\"/service/method\", "
            "ttl=3000ms}");
}

TEST_P(XdsStatefulSessionFilterConfigTest, ParseSessionStateUnset) {
  auto config = ParseConfig(StatefulSession());
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), StatefulSessionFilter::Config::Type());
  EXPECT_EQ(config->ToString(), "{}");
}

TEST_P(XdsStatefulSessionFilterConfigTest, ParseCookieNotPresent) {
  StatefulSession stateful_session;
  stateful_session.mutable_session_state()->mutable_typed_config()->PackFrom(
      CookieBasedSessionState());
  auto config = ParseConfig(stateful_session);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  ASSERT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      absl::StrCat("errors validating filter config: [field:", FieldPrefix(),
                   ".session_state.typed_config.value["
                   "envoy.extensions.http.stateful_session.cookie.v3"
                   ".CookieBasedSessionState].cookie "
                   "error:field not present]"))
      << status;
}

TEST_P(XdsStatefulSessionFilterConfigTest, ParseCookieNameNotPresent) {
  CookieBasedSessionState cookie_state;
  cookie_state.mutable_cookie();
  StatefulSession stateful_session;
  stateful_session.mutable_session_state()->mutable_typed_config()->PackFrom(
      cookie_state);
  auto config = ParseConfig(stateful_session);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  ASSERT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      absl::StrCat("errors validating filter config: [field:", FieldPrefix(),
                   ".session_state.typed_config.value["
                   "envoy.extensions.http.stateful_session.cookie.v3"
                   ".CookieBasedSessionState].cookie.name "
                   "error:field not present]"))
      << status;
}

TEST_P(XdsStatefulSessionFilterConfigTest, ParseInvalidTtl) {
  CookieBasedSessionState cookie_state;
  auto* cookie = cookie_state.mutable_cookie();
  cookie->set_name("foo");
  cookie->mutable_ttl()->set_seconds(-1);
  StatefulSession stateful_session;
  stateful_session.mutable_session_state()->mutable_typed_config()->PackFrom(
      cookie_state);
  auto config = ParseConfig(stateful_session);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  ASSERT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      absl::StrCat("errors validating filter config: [field:", FieldPrefix(),
                   ".session_state.typed_config.value["
                   "envoy.extensions.http.stateful_session.cookie.v3"
                   ".CookieBasedSessionState].cookie.ttl.seconds "
                   "error:value must be in the range [0, 315576000000]]"))
      << status;
}

TEST_P(XdsStatefulSessionFilterConfigTest, ParseUnknownSessionStateType) {
  StatefulSession stateful_session;
  stateful_session.mutable_session_state()->mutable_typed_config()->PackFrom(
      Router());
  auto config = ParseConfig(stateful_session);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  ASSERT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      absl::StrCat("errors validating filter config: [field:", FieldPrefix(),
                   ".session_state.typed_config.value["
                   "envoy.extensions.filters.http.router.v3.Router] "
                   "error:unsupported session state type]"))
      << status;
}

TEST_P(XdsStatefulSessionFilterConfigTest, ParseTypedStructSessionState) {
  StatefulSession stateful_session;
  auto* typed_config =
      stateful_session.mutable_session_state()->mutable_typed_config();
  typed_config->PackFrom(CookieBasedSessionState());
  ::xds::type::v3::TypedStruct typed_struct;
  typed_struct.set_type_url(typed_config->type_url());
  typed_config->PackFrom(typed_struct);
  auto config = ParseConfig(stateful_session);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  ASSERT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      absl::StrCat("errors validating filter config: [field:", FieldPrefix(),
                   ".session_state.typed_config.value["
                   "xds.type.v3.TypedStruct].value["
                   "envoy.extensions.http.stateful_session.cookie.v3"
                   ".CookieBasedSessionState] "
                   "error:could not parse session state config]"))
      << status;
}

TEST_P(XdsStatefulSessionFilterConfigTest, ParseUnparsableSessionState) {
  StatefulSession stateful_session;
  stateful_session.mutable_session_state()->mutable_typed_config()->PackFrom(
      CookieBasedSessionState());
  stateful_session.mutable_session_state()->mutable_typed_config()->set_value(
      std::string("\0", 1));
  auto config = ParseConfig(stateful_session);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  ASSERT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(
      status.message(),
      absl::StrCat("errors validating filter config: [field:", FieldPrefix(),
                   ".session_state.typed_config.value["
                   "envoy.extensions.http.stateful_session.cookie.v3"
                   ".CookieBasedSessionState] "
                   "error:could not parse session state config]"))
      << status;
}

//
// GCP auth filter tests
//

class XdsGcpAuthnFilterTest : public XdsHttpFilterTest {
 protected:
  XdsGcpAuthnFilterTest() {
    XdsExtension extension = MakeXdsExtension(GcpAuthnFilterConfig());
    filter_ = GetFilter(extension.type);
    GRPC_CHECK_NE(filter_, nullptr) << extension.type;
  }

  const XdsHttpFilterImpl* filter_;
};

TEST_F(XdsGcpAuthnFilterTest, Accessors) {
  EXPECT_EQ(filter_->ConfigProtoName(),
            "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig");
  EXPECT_EQ(filter_->OverrideConfigProtoName(), "");
  EXPECT_EQ(filter_->channel_filter(), &GcpAuthenticationFilter::kFilterVtable);
  EXPECT_TRUE(filter_->IsSupportedOnClients());
  EXPECT_FALSE(filter_->IsSupportedOnServers());
  EXPECT_FALSE(filter_->IsTerminalFilter());
}

TEST_F(XdsGcpAuthnFilterTest, ParseTopLevelConfigEmpty) {
  XdsExtension extension = MakeXdsExtension(GcpAuthnFilterConfig());
  auto config = filter_->ParseTopLevelConfig("enterprise", decode_context_,
                                             extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), GcpAuthenticationFilter::Config::Type());
  EXPECT_EQ(config->ToString(),
            "{instance_name=\"enterprise\", cache_size=10, cache=(nil)}");
}

TEST_F(XdsGcpAuthnFilterTest, ParseTopLevelConfigCacheSizeDefault) {
  GcpAuthnFilterConfig proto;
  proto.mutable_cache_config();
  XdsExtension extension = MakeXdsExtension(proto);
  auto config = filter_->ParseTopLevelConfig("yorktown", decode_context_,
                                             extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), GcpAuthenticationFilter::Config::Type());
  EXPECT_EQ(config->ToString(),
            "{instance_name=\"yorktown\", cache_size=10, cache=(nil)}");
}

TEST_F(XdsGcpAuthnFilterTest, ParseTopLevelConfigCacheSize) {
  GcpAuthnFilterConfig proto;
  proto.mutable_cache_config()->mutable_cache_size()->set_value(6);
  XdsExtension extension = MakeXdsExtension(proto);
  auto config = filter_->ParseTopLevelConfig("hornet", decode_context_,
                                             extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), GcpAuthenticationFilter::Config::Type());
  EXPECT_EQ(config->ToString(),
            "{instance_name=\"hornet\", cache_size=6, cache=(nil)}");
}

TEST_F(XdsGcpAuthnFilterTest, ParseTopLevelConfigCacheSizeZero) {
  GcpAuthnFilterConfig proto;
  proto.mutable_cache_config()->mutable_cache_size()->set_value(0);
  XdsExtension extension = MakeXdsExtension(proto);
  auto config = filter_->ParseTopLevelConfig("ranger", decode_context_,
                                             extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig]"
            ".cache_config.cache_size "
            "error:must be greater than 0]")
      << status;
}

TEST_F(XdsGcpAuthnFilterTest, ParseTopLevelConfigTypedStruct) {
  XdsExtension extension = MakeXdsExtension(GcpAuthnFilterConfig());
  extension.value = Json();
  auto config = filter_->ParseTopLevelConfig("lexington", decode_context_,
                                             extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig] "
            "error:could not parse GCP auth filter config]")
      << status;
}

TEST_F(XdsGcpAuthnFilterTest, ParseTopLevelConfigUnparseable) {
  XdsExtension extension = MakeXdsExtension(GcpAuthnFilterConfig());
  std::string serialized_resource("\0", 1);
  extension.value = absl::string_view(serialized_resource);
  auto config = filter_->ParseTopLevelConfig("saratoga", decode_context_,
                                             extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig] "
            "error:could not parse GCP auth filter config]")
      << status;
}

TEST_F(XdsGcpAuthnFilterTest, ParseOverrideConfig) {
  XdsExtension extension = MakeXdsExtension(GcpAuthnFilterConfig());
  auto config = filter_->ParseOverrideConfig("wasp", decode_context_, extension,
                                             &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig] "
            "error:GCP auth filter does not support config override]")
      << status;
}

TEST_F(XdsGcpAuthnFilterTest, MergeConfigsGetsCacheFromBlackboard) {
  auto config = MakeRefCounted<GcpAuthenticationFilter::Config>();
  config->instance_name = "langley";
  config->cache_size = 1;
  auto blackboard = MakeRefCounted<Blackboard>();
  auto merged_config = filter_->MergeConfigs(
      config, /*virtual_host_override_config=*/nullptr,
      /*route_override_config=*/nullptr,
      /*cluster_weight_override_config=*/nullptr, *blackboard);
  ASSERT_NE(merged_config, nullptr);
  ASSERT_EQ(merged_config->type(), GcpAuthenticationFilter::Config::Type());
  EXPECT_THAT(merged_config->ToString(),
              ::testing::MatchesRegex(
                  "\\{instance_name=\"langley\", cache_size=1, cache=0x.+\\}"));
  auto blackboard_entry =
      blackboard->Get<GcpAuthenticationFilter::CallCredentialsCache>("langley");
  ASSERT_NE(blackboard_entry, nullptr);
  EXPECT_EQ(
      DownCast<const GcpAuthenticationFilter::Config&>(*merged_config).cache,
      blackboard_entry);
  EXPECT_EQ(blackboard_entry->max_size(), 1);
}

//
// composite filter tests
//

class XdsCompositeFilterTest : public XdsHttpFilterTest {
 protected:
  XdsCompositeFilterTest() : env_("GRPC_EXPERIMENTAL_XDS_COMPOSITE_FILTER") {}

  void SetUp() override {
    // Recreate registry now that env var is set.
    registry_ = XdsHttpFilterRegistry();
    XdsExtension extension = MakeXdsExtension(ExtensionWithMatcher());
    filter_ = GetFilter(extension.type);
    GRPC_CHECK_NE(filter_, nullptr) << extension.type;
  }

  const XdsHttpFilterImpl* filter_;
  ScopedExperimentalEnvVar env_;
};

TEST_F(XdsCompositeFilterTest, Accessors) {
  EXPECT_EQ(filter_->ConfigProtoName(),
            "envoy.extensions.common.matching.v3.ExtensionWithMatcher");
  EXPECT_EQ(filter_->OverrideConfigProtoName(),
            "envoy.extensions.common.matching.v3.ExtensionWithMatcherPerRoute");
  EXPECT_EQ(filter_->channel_filter(), &CompositeFilter::kFilterVtable);
  EXPECT_TRUE(filter_->IsSupportedOnClients());
  EXPECT_FALSE(filter_->IsSupportedOnServers());
  EXPECT_FALSE(filter_->IsTerminalFilter());
}

TEST_F(XdsCompositeFilterTest, ParseTopLevelConfigSkipFilter) {
  ExtensionWithMatcher extension_with_matcher;
  extension_with_matcher.mutable_extension_config()
      ->mutable_typed_config()
      ->PackFrom(Composite());
  auto* matcher_tree =
      extension_with_matcher.mutable_xds_matcher()->mutable_matcher_tree();
  HttpRequestHeaderMatchInput input;
  input.set_header_name("header_name");
  matcher_tree->mutable_input()->mutable_typed_config()->PackFrom(input);
  auto* map = matcher_tree->mutable_exact_match_map()->mutable_map();
  (*map)["header_value"].mutable_action()->mutable_typed_config()->PackFrom(
      SkipFilter());
  XdsExtension extension = MakeXdsExtension(extension_with_matcher);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), CompositeFilter::Config::Type());
  EXPECT_EQ(config->ToString(),
            "XdsMatcherExactMap{input=MetadataInput(key=header_name), map={"
            "{\"header_value\": {action=SkipFilter, keep_matching=false}}}}");
}

TEST_F(XdsCompositeFilterTest, ParseTopLevelConfigSingleFilter) {
  ExtensionWithMatcher extension_with_matcher;
  extension_with_matcher.mutable_extension_config()
      ->mutable_typed_config()
      ->PackFrom(Composite());
  auto* matcher_tree =
      extension_with_matcher.mutable_xds_matcher()->mutable_matcher_tree();
  HttpRequestHeaderMatchInput input;
  input.set_header_name("header_name");
  matcher_tree->mutable_input()->mutable_typed_config()->PackFrom(input);
  ExecuteFilterAction execute_filter_action;
  execute_filter_action.mutable_typed_config()
      ->mutable_typed_config()
      ->PackFrom(HTTPFault());
  auto* map = matcher_tree->mutable_exact_match_map()->mutable_map();
  (*map)["header_value"].mutable_action()->mutable_typed_config()->PackFrom(
      execute_filter_action);
  XdsExtension extension = MakeXdsExtension(extension_with_matcher);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.message("unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), CompositeFilter::Config::Type());
  EXPECT_EQ(config->ToString(),
            "XdsMatcherExactMap{input=MetadataInput(key=header_name), map={"
            "{\"header_value\": {action=ExecuteFilterAction{filter_chain=["
            "fault_injection_filter_config={max_faults=4294967295}]}, "
            "keep_matching=false}}}}");
}

TEST_F(XdsCompositeFilterTest, ParseTopLevelConfigFilterChain) {
  ExtensionWithMatcher extension_with_matcher;
  extension_with_matcher.mutable_extension_config()
      ->mutable_typed_config()
      ->PackFrom(Composite());
  auto* matcher_tree =
      extension_with_matcher.mutable_xds_matcher()->mutable_matcher_tree();
  HttpRequestHeaderMatchInput input;
  input.set_header_name("header_name");
  matcher_tree->mutable_input()->mutable_typed_config()->PackFrom(input);
  ExecuteFilterAction execute_filter_action;
  execute_filter_action.mutable_filter_chain()
      ->add_typed_config()
      ->mutable_typed_config()
      ->PackFrom(HTTPFault());
  auto* map = matcher_tree->mutable_exact_match_map()->mutable_map();
  (*map)["header_value"].mutable_action()->mutable_typed_config()->PackFrom(
      execute_filter_action);
  XdsExtension extension = MakeXdsExtension(extension_with_matcher);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.message("unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), CompositeFilter::Config::Type());
  EXPECT_EQ(config->ToString(),
            "XdsMatcherExactMap{input=MetadataInput(key=header_name), map={"
            "{\"header_value\": {action=ExecuteFilterAction{filter_chain=["
            "fault_injection_filter_config={max_faults=4294967295}]}, "
            "keep_matching=false}}}}");
}

TEST_F(XdsCompositeFilterTest, ParseTopLevelConfigSamplePercentage) {
  ExtensionWithMatcher extension_with_matcher;
  extension_with_matcher.mutable_extension_config()
      ->mutable_typed_config()
      ->PackFrom(Composite());
  auto* matcher_tree =
      extension_with_matcher.mutable_xds_matcher()->mutable_matcher_tree();
  HttpRequestHeaderMatchInput input;
  input.set_header_name("header_name");
  matcher_tree->mutable_input()->mutable_typed_config()->PackFrom(input);
  ExecuteFilterAction execute_filter_action;
  execute_filter_action.mutable_typed_config()
      ->mutable_typed_config()
      ->PackFrom(HTTPFault());
  execute_filter_action.mutable_sample_percent()
      ->mutable_default_value()
      ->set_numerator(25);
  auto* map = matcher_tree->mutable_exact_match_map()->mutable_map();
  (*map)["header_value"].mutable_action()->mutable_typed_config()->PackFrom(
      execute_filter_action);
  XdsExtension extension = MakeXdsExtension(extension_with_matcher);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.message("unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), CompositeFilter::Config::Type());
  EXPECT_EQ(config->ToString(),
            "XdsMatcherExactMap{input=MetadataInput(key=header_name), map={"
            "{\"header_value\": {action=ExecuteFilterAction{filter_chain=["
            "fault_injection_filter_config={max_faults=4294967295}], "
            "sample_per_million=250000}, keep_matching=false}}}}");
}

TEST_F(XdsCompositeFilterTest, ParseTopLevelConfigNoMatcher) {
  ExtensionWithMatcher extension_with_matcher;
  extension_with_matcher.mutable_extension_config()
      ->mutable_typed_config()
      ->PackFrom(Composite());
  XdsExtension extension = MakeXdsExtension(extension_with_matcher);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), CompositeFilter::Config::Type());
  EXPECT_EQ(config->ToString(), "{}");
}

TEST_F(XdsCompositeFilterTest, ParseTopLevelConfigInvalidNestedFilterConfig) {
  ExtensionWithMatcher extension_with_matcher;
  extension_with_matcher.mutable_extension_config()
      ->mutable_typed_config()
      ->PackFrom(Composite());
  auto* matcher_tree =
      extension_with_matcher.mutable_xds_matcher()->mutable_matcher_tree();
  HttpRequestHeaderMatchInput input;
  input.set_header_name("header_name");
  matcher_tree->mutable_input()->mutable_typed_config()->PackFrom(input);
  ExecuteFilterAction execute_filter_action;
  HTTPFault fault;
  fault.mutable_abort()->set_grpc_status(17);
  execute_filter_action.mutable_typed_config()
      ->mutable_typed_config()
      ->PackFrom(fault);
  auto* map = matcher_tree->mutable_exact_match_map()->mutable_map();
  (*map)["header_value"].mutable_action()->mutable_typed_config()->PackFrom(
      execute_filter_action);
  XdsExtension extension = MakeXdsExtension(extension_with_matcher);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  EXPECT_EQ(errors_.message("errors validating filter config"),
            "errors validating filter config: "
            "[field:http_filter.value["
            "envoy.extensions.common.matching.v3.ExtensionWithMatcher]"
            ".xds_matcher.matcher_tree.exact_match_map.on_match.action.value["
            "envoy.extensions.filters.http.composite.v3.ExecuteFilterAction]"
            ".typed_config.typed_config.value["
            "envoy.extensions.filters.http.fault.v3.HTTPFault]"
            ".abort.grpc_status "
            "error:invalid gRPC status code: 17]");
}

TEST_F(XdsCompositeFilterTest, ParseTopLevelConfigTerminalFilter) {
  ExtensionWithMatcher extension_with_matcher;
  extension_with_matcher.mutable_extension_config()
      ->mutable_typed_config()
      ->PackFrom(Composite());
  auto* matcher_tree =
      extension_with_matcher.mutable_xds_matcher()->mutable_matcher_tree();
  HttpRequestHeaderMatchInput input;
  input.set_header_name("header_name");
  matcher_tree->mutable_input()->mutable_typed_config()->PackFrom(input);
  ExecuteFilterAction execute_filter_action;
  execute_filter_action.mutable_typed_config()
      ->mutable_typed_config()
      ->PackFrom(Router());
  auto* map = matcher_tree->mutable_exact_match_map()->mutable_map();
  (*map)["header_value"].mutable_action()->mutable_typed_config()->PackFrom(
      execute_filter_action);
  XdsExtension extension = MakeXdsExtension(extension_with_matcher);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  EXPECT_EQ(errors_.message("errors validating filter config"),
            "errors validating filter config: "
            "[field:http_filter.value["
            "envoy.extensions.common.matching.v3.ExtensionWithMatcher]"
            ".xds_matcher.matcher_tree.exact_match_map.on_match.action.value["
            "envoy.extensions.filters.http.composite.v3.ExecuteFilterAction]"
            ".typed_config.typed_config.value["
            "envoy.extensions.filters.http.router.v3.Router] "
            "error:terminal filters may not be used under composite filter]");
}

TEST_F(XdsCompositeFilterTest, ParseTopLevelConfigTypedStruct) {
  XdsExtension extension = MakeXdsExtension(ExtensionWithMatcher());
  extension.value = Json();
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.common.matching.v3.ExtensionWithMatcher] "
            "error:could not parse composite filter config]")
      << status;
}

TEST_F(XdsCompositeFilterTest, ParseTopLevelConfigUnparseable) {
  XdsExtension extension = MakeXdsExtension(ExtensionWithMatcher());
  std::string serialized_resource("\0", 1);
  extension.value = absl::string_view(serialized_resource);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.common.matching.v3.ExtensionWithMatcher] "
            "error:could not parse composite filter config]")
      << status;
}

// Note: This shows that we include validation errors from the matcher
// itself.  We don't need to test every possible matcher validation
// failure case here, because those are covered in xds_matcher_parse_test.cc.
TEST_F(XdsCompositeFilterTest, ParseTopLevelConfigRequiredFieldsMissing) {
  XdsExtension extension = MakeXdsExtension(ExtensionWithMatcher());
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.common.matching.v3.ExtensionWithMatcher]"
            ".extension_config error:field not set]")
      << status;
}

TEST_F(XdsCompositeFilterTest, ParseTopLevelConfigWrongExtensionConfigType) {
  ExtensionWithMatcher extension_with_matcher;
  extension_with_matcher.mutable_extension_config()
      ->mutable_typed_config()
      ->PackFrom(Router());
  auto* matcher_tree =
      extension_with_matcher.mutable_xds_matcher()->mutable_matcher_tree();
  HttpRequestHeaderMatchInput input;
  input.set_header_name("header_name");
  matcher_tree->mutable_input()->mutable_typed_config()->PackFrom(input);
  auto* map = matcher_tree->mutable_exact_match_map()->mutable_map();
  (*map)["header_value"].mutable_action()->mutable_typed_config()->PackFrom(
      SkipFilter());
  XdsExtension extension = MakeXdsExtension(extension_with_matcher);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.common.matching.v3.ExtensionWithMatcher]"
            ".extension_config.typed_config.value["
            "envoy.extensions.filters.http.router.v3.Router] "
            "error:unsupported extension config type]")
      << status;
}

TEST_F(XdsCompositeFilterTest, ParseOverrideConfigBasic) {
  ExtensionWithMatcherPerRoute extension_with_matcher;
  auto* matcher_tree =
      extension_with_matcher.mutable_xds_matcher()->mutable_matcher_tree();
  HttpRequestHeaderMatchInput input;
  input.set_header_name("header_name");
  matcher_tree->mutable_input()->mutable_typed_config()->PackFrom(input);
  auto* map = matcher_tree->mutable_exact_match_map()->mutable_map();
  (*map)["header_value"].mutable_action()->mutable_typed_config()->PackFrom(
      SkipFilter());
  XdsExtension extension = MakeXdsExtension(extension_with_matcher);
  auto config =
      filter_->ParseOverrideConfig("", decode_context_, extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.status(
      absl::StatusCode::kInvalidArgument, "unexpected errors");
  ASSERT_NE(config, nullptr);
  ASSERT_EQ(config->type(), CompositeFilter::Config::Type());
  EXPECT_EQ(config->ToString(),
            "XdsMatcherExactMap{input=MetadataInput(key=header_name), map={"
            "{\"header_value\": {action=SkipFilter, keep_matching=false}}}}");
}

TEST_F(XdsCompositeFilterTest, ParseOverrideConfigTypedStruct) {
  XdsExtension extension = MakeXdsExtension(ExtensionWithMatcherPerRoute());
  extension.value = Json();
  auto config =
      filter_->ParseOverrideConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.common.matching.v3.ExtensionWithMatcherPerRoute] "
            "error:could not parse composite filter override config]")
      << status;
}

TEST_F(XdsCompositeFilterTest, ParseOverrideConfigUnparseable) {
  XdsExtension extension = MakeXdsExtension(ExtensionWithMatcherPerRoute());
  std::string serialized_resource("\0", 1);
  extension.value = absl::string_view(serialized_resource);
  auto config =
      filter_->ParseOverrideConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.common.matching.v3.ExtensionWithMatcherPerRoute] "
            "error:could not parse composite filter override config]")
      << status;
}

// Note: This shows that we include validation errors from the matcher
// itself.  We don't need to test every possible matcher validation
// failure case here, because those are covered in xds_matcher_parse_test.cc.
TEST_F(XdsCompositeFilterTest, ParseOverrideConfigRequiredFieldMissing) {
  XdsExtension extension = MakeXdsExtension(ExtensionWithMatcherPerRoute());
  auto config =
      filter_->ParseOverrideConfig("", decode_context_, extension, &errors_);
  absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument,
                                       "errors validating filter config");
  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
  EXPECT_EQ(status.message(),
            "errors validating filter config: ["
            "field:http_filter.value["
            "envoy.extensions.common.matching.v3.ExtensionWithMatcherPerRoute]"
            ".xds_matcher error:field not set]")
      << status;
}

TEST_F(XdsCompositeFilterTest, MergeConfigsHandlesBlackboardForNestedFilters) {
  // First, construct the composite filter config.  We use the GCP authn
  // filter as the child, since it uses the blackboard.
  ExtensionWithMatcher extension_with_matcher;
  extension_with_matcher.mutable_extension_config()
      ->mutable_typed_config()
      ->PackFrom(Composite());
  auto* matcher_tree =
      extension_with_matcher.mutable_xds_matcher()->mutable_matcher_tree();
  HttpRequestHeaderMatchInput input;
  input.set_header_name("header_name");
  matcher_tree->mutable_input()->mutable_typed_config()->PackFrom(input);
  GcpAuthnFilterConfig gcp_authn_filter_config;
  gcp_authn_filter_config.mutable_cache_config()
      ->mutable_cache_size()
      ->set_value(6);
  ExecuteFilterAction execute_filter_action;
  auto* typed_extension_config = execute_filter_action.mutable_typed_config();
  typed_extension_config->set_name("gcp_authn_instance");
  typed_extension_config->mutable_typed_config()->PackFrom(
      gcp_authn_filter_config);
  auto* map = matcher_tree->mutable_exact_match_map()->mutable_map();
  (*map)["header_value"].mutable_action()->mutable_typed_config()->PackFrom(
      execute_filter_action);
  XdsExtension extension = MakeXdsExtension(extension_with_matcher);
  auto config =
      filter_->ParseTopLevelConfig("", decode_context_, extension, &errors_);
  ASSERT_TRUE(errors_.ok()) << errors_.message("unexpected errors");
  ASSERT_NE(config, nullptr);
  // Now call MergeConfigs() and make sure it delegates to the child
  // filters to handle the blackboard.
  auto blackboard = MakeRefCounted<Blackboard>();
  auto merged_config = filter_->MergeConfigs(
      config, /*virtual_host_override_config=*/nullptr,
      /*route_override_config=*/nullptr,
      /*cluster_weight_override_config=*/nullptr, *blackboard);
  ASSERT_NE(merged_config, nullptr);
  ASSERT_EQ(merged_config->type(), CompositeFilter::Config::Type());
  EXPECT_EQ(merged_config->ToString(),
            "XdsMatcherExactMap{input=MetadataInput(key=header_name), "
            "map={{\"header_value\": {action=ExecuteFilterAction{"
            "filter_chain=[gcp_authentication_filter_config="
            "{instance_name=\"gcp_authn_instance\", cache_size=6, "
            "cache=(nil)}]}, keep_matching=false}}}}");
  auto blackboard_entry =
      blackboard->Get<GcpAuthenticationFilter::CallCredentialsCache>(
          "gcp_authn_instance");
  ASSERT_NE(blackboard_entry, nullptr);
  EXPECT_EQ(blackboard_entry->max_size(), 6);
  auto& merged_composite_config =
      DownCast<const CompositeFilter::Config&>(*merged_config);
  ASSERT_EQ(merged_composite_config.merged_config_map.size(), 1);
  auto it = merged_composite_config.merged_config_map.begin();
  ASSERT_EQ(it->second.size(), 1);
  ASSERT_EQ(it->second[0]->type(), GcpAuthenticationFilter::Config::Type());
  auto& gcp_auth_merged_config =
      DownCast<const GcpAuthenticationFilter::Config&>(*it->second[0]);
  EXPECT_EQ(gcp_auth_merged_config.cache, blackboard_entry);
}

}  // namespace
}  // namespace testing
}  // namespace grpc_core

int main(int argc, char** argv) {
  ::testing::InitGoogleTest(&argc, argv);
  grpc::testing::TestEnvironment env(&argc, argv);
  grpc_init();
  auto result = RUN_ALL_TESTS();
  grpc_shutdown();
  return result;
}
