#include "searchproxy_tvm_ut.h"

#include "helpers.h"
#include "staticserver.h"

#include <saas/library/searchmap/parsers/json/json.h>
#include <saas/library/searchmap/searchmap.h>

#include <library/cpp/testing/unittest/registar.h>
#include <library/cpp/neh/neh.h>
#include <google/protobuf/text_format.h>

namespace {

    class TFalseTvmClient: public NSearchProxy::ITvmClient {
    public:
        TMaybe<ui32> CheckServiceTicket(const TString&) const override {
            return Nothing();
        }

        TMaybe<ui64> CheckUserTicket(const TString&) const override {
            Y_FAIL();
            return Nothing();
        }
    };

    class TTestMainTvmClient: public NSearchProxy::ITvmClient {
    public:
        TMaybe<ui32> CheckServiceTicket(const TString& ticket) const override {
            return CheckTicket<ui32>(ticket);
        }

        TMaybe<ui64> CheckUserTicket(const TString& ticket) const override {
            return CheckTicket<ui64>(ticket);
        }

        template <typename T>
        TMaybe<T> CheckTicket(const TString& ticketStr) const {
            if (ticketStr == "throw") {
                ythrow yexception() << "TTestMainTvmClient failed";
            }

            T srcId{};
            return TryFromString<T>(ticketStr, srcId)
                ? MakeMaybe<T>(srcId)
                : Nothing();
        }

        static const constexpr ui32 DisallowedSrcId = 2;
        static const constexpr ui32 AllowedSrcId = 7;
        static const constexpr ui32 TvmProxyTvmId = 500;

        static const constexpr ui64 DisallowedUid = 20;
        static const constexpr ui64 AllowedUid = 70;
    };

    class TTestFlowMirrorTvmClient: public NSearchProxy::ITvmClient {
    public:
        TMaybe<ui32> CheckServiceTicket(const TString& ticketStr) const override {
            if (ticketStr == "throw") {
                ythrow yexception() << "TTestFlowMirrorTvmClient failed";
            }

            ui32 srcId{};
            return TryFromString<ui32>(ticketStr, srcId)
                ? MakeMaybe<ui32>(srcId)
                : Nothing();
        }

        TMaybe<ui64> CheckUserTicket(const TString&) const override {
            ythrow yexception() << "TTestFlowMirrorTvmClient CheckUserTicket must not be used";
            return Nothing();
        }

        static const constexpr ui32 AllowedSrcId = 707;
    };

    class TTestAbcResolver: public NSearchProxy::IAbcResolver {
    public:
        TVector<TUid> Resolve(const TVector<TAbcId>& groups) const override {
            static const THashMap<TAbcId, TVector<TUid>> Mapping{
                {GlobalGroupId, {77, TTestMainTvmClient::AllowedUid, 777}},
                {ServiceGroupId, {5, 9}},
            };
            TVector<TUid> ret;
            for (const auto& group : groups) {
                const auto& uids = Mapping.at(group);
                ret.insert(ret.end(), uids.begin(), uids.end());
            }
            return ret;
        }

        static const constexpr TAbcId GlobalGroupId = 664;
        static const constexpr TAbcId ServiceGroupId = 310;
    };
}


class TSearchProxyTvmTest: public TTestBase {
public:
    UNIT_TEST_SUITE(TSearchProxyTvmTest)
        UNIT_TEST(TestAll)
        UNIT_TEST(TestForcedAuth)
        UNIT_TEST(TestTvmProxyUserTickets)
        UNIT_TEST(TestAllowedServiceButBadUserTicket)
        UNIT_TEST(TestNoFlowMirror)
        UNIT_TEST(TestFlowMirrorAllowed)
    UNIT_TEST_SUITE_END();

    using EServerAuthMode = NSearchProxyTvmTest::EServerAuthMode;
    using EServiceAuthMode = NSearchProxyTvmTest::EServiceAuthMode;
    using EMetaAuth = NSearchProxyTvmTest::EMetaAuth;
    using EProto = NSearchProxyTvmTest::EProto;
    using ETicketContainer = NSearchProxyTvmTest::ETicketContainer;
    using EService = NSearchProxyTvmTest::EService;
    using EMeta = NSearchProxyTvmTest::EMeta;
    using EServiceTicket = NSearchProxyTvmTest::EServiceTicket;
    using EResult = NSearchProxyTvmTest::EResult;
    using EErrorSource = NSearchProxyTvmTest::EErrorSource;
    using EForceAuth = NSearchProxyTvmTest::EForceAuth;
    using EUserTicket = NSearchProxyTvmTest::EUserTicket;
    using EFlowMirror = NSearchProxyTvmTest::EFlowMirror;

    class TServer {
    public:
        struct TServerParams {
            EServerAuthMode ServerAuthMode;
            EServiceAuthMode ServiceAuthMode;
            EMetaAuth MetaAuth;
            EService ServiceType;
            EMeta MetaType;
            EFlowMirror FlowMirror = EFlowMirror::Enabled;
        };

        struct TRequestParams {
            EProto Proto;
            ETicketContainer TicketContainer;
            EServiceTicket ServiceTicketMode;
            EResult ExpectedResult;
            EForceAuth ForceAuth;
            EUserTicket UserTicketMode = EUserTicket::NoTicket;
        };

        struct TOneTestParams {
            TServerParams ServerParams;
            TRequestParams RequestParams;

            void Out(IOutputStream& out) const;
        };

        bool IsKv() const {
            return Params.ServiceType == EService::KeyValue;
        }

        bool IsMeta() const {
            return Params.MetaType == EMeta::Meta;
        }

        struct TServiceConfigSectionParams {
            TString ServiceName;
            EServiceAuthMode AuthMode;
            bool IsKv = false;
        };

        TString MakeKvRequest(const TString& srvType, const TString& srvName) const {
            return TStringBuilder()
                << "/?&kps=" << Kps
                << "&" << srvType << "=" << srvName
                << "&text=" + RequestId;
        }

        TString MakeFullTextRequest(const TString& srv) {
            return OneStepRequest(srv, Kps, RequestId);
        }

        TServer(const TServerParams& params)
            : Params(params)
            , Backend(new TStaticServer())
            , ProxyPort(TSilentServer().GetPort())

        {
            Backend->SetKV(IsKv());
            const TSearchResults responseData(200,
                TSearchResultsGroup(100500, TSearchResultsGroup::TUrls(1, DocUrl)));

            {
                const auto url = IsKv()
                    ? IsMeta()
                        ? MakeKvRequest("metaservice", MetaServiceName)
                        : MakeKvRequest("service", BackendServiceName)
                    : MakeFullTextRequest(BackendServiceName)
                        + (IsMeta() ? "&metaservice=" + MetaServiceName : TString());

                UNIT_ASSERT(Backend->AddDoc(url.data(), GenerateReport(responseData).SerializeAsString()));
            }

            TString conf;
            {
                {
                    TServiceConfigSectionParams params;
                    params.ServiceName = "test";
                    params.AuthMode = Params.ServiceAuthMode;
                    params.IsKv = IsKv();
                    conf += ProduceServiceSection(params);
                }
                {
                    TServiceConfigSectionParams params;
                    params.ServiceName = "test_meta";

                    EServiceAuthMode auth{};
                    switch (Params.MetaAuth) {
                    case EMetaAuth::Disabled:
                        auth = EServiceAuthMode::Disabled;
                        break;
                    case EMetaAuth::AsOnBackend:
                        auth = Params.ServiceAuthMode;
                        break;
                    }
                    params.AuthMode = auth;

                    params.IsKv = IsKv();
                    conf += ProduceServiceSection(params);
                }
                conf += ProduceServerTvmSection();
            }

            TConfigPatcher cp;
            ProxyConfig.Reset(new TSearchProxyConfig(
                GetPatientSearchProxyConfig(ProxyPort, "", nullptr, conf.data()).data(),
                TDaemonConfig(GetDaemonConfig(), true),
                ProduceMetaServiceSearchMap(Backend->GetPort()),
                cp
            ));

            {
                //TVector<NSearchProxy::ITvmClientPtr> clients;
                //clients.emplace_back(new TFalseTvmClient());
                //clients.emplace_back(new TTestMainTvmClient());
                //clients.emplace_back(new TFalseTvmClient());

                TSearchProxyServer::TTestTvmSuite suite;
                suite.TvmClients.Main = new TTestMainTvmClient();
                if (params.FlowMirror == EFlowMirror::Enabled) {
                    suite.TvmClients.FlowMirror = new TTestFlowMirrorTvmClient();
                }
                suite.AbcResolver = new TTestAbcResolver();

                Proxy.Reset(new TAutoSearchProxyServer(*ProxyConfig, &suite));
            }
        }

        NSearchMapParser::TSearchMap ProduceMetaServiceSearchMap(ui16 searchPort) {
            static const TString json = R"SEARCHMAP(
{
"properties" : {
    "version" : 2
},
"meta" : {
    "test_meta" : {
        "components" : [
            {
                "service" : "test",
                "priority" : 0
            }
        ],
        "shard_by" : "url_hash"
    }
},
"services" : {
    "test" : {
        "replicas" : {
        "default" : [
            {
                "host" : "localhost",
                "search_port" : 80,
                "shard_max" : 65533
            }
        ]
        },
        "shard_by" : "url_hash"
    }
}
}
)SEARCHMAP";

            NJson::TJsonValue map;
            Y_ENSURE(NJson::ReadJsonTree(json, &map, true));

            map["services"]["test"]["replicas"]["default"][0]["search_port"] = searchPort;

            NSearchMapParser::TJsonSearchMapParser parser(map);
            return parser.GetSearchMap();
        }

        static TString ProduceServiceSection(const TServiceConfigSectionParams& params) {
            TStringStream srvConf;

            srvConf << R"CONFIG(
                <Service>
                    Name:)CONFIG";
            srvConf << params.ServiceName;

            srvConf << R"CONFIG(
                    TwoStepQuery: false
                    GroupingByDC: false
            )CONFIG";

            if (params.IsKv) {
                srvConf << R"CONFIG(
                        <ProxyMeta>
                            ParallelRequestCount: 2
                            MaxAttempts: 2
                        </ProxyMeta>
                        <CgiParams>
                            meta_search: first_found
                            normal_kv_report: da
                        </CgiParams>
                )CONFIG";
            }

            srvConf << "\n"
                    << "<Tvm>\n"
                    << "    AllowedSourceServiceIds: "
                        << TTestMainTvmClient::AllowedSrcId << ", " << TTestFlowMirrorTvmClient::AllowedSrcId << "\n"
                    << "    Mode: ";

            switch (params.AuthMode) {
            case EServiceAuthMode::Disabled:
                srvConf << "Disabled";
                break;
            case EServiceAuthMode::DryRun:
                srvConf << "DryRun";
                break;
            case EServiceAuthMode::Enabled:
                srvConf << "Enabled";
                break;
            }
            srvConf << "\n"
                    << "    UserAbcGroups: " << TTestAbcResolver::ServiceGroupId << "\n"
                    << "</Tvm>\n</Service>\n";

            return srvConf.Str();
        }

        TString ProduceServerTvmSection() {
            TStringStream srvConf;
            srvConf << "\n<Tvm>\nEnabled: ";
            switch (Params.ServerAuthMode) {
            case EServerAuthMode::Disabled:
                srvConf << "false";
                break;
            case EServerAuthMode::Enabled:
                srvConf << "true";
                break;
            }

            srvConf << "\n    UserAbcGroups: " << TTestAbcResolver::GlobalGroupId << "\n"
                    << "    TvmProxyTvmId: " << TTestMainTvmClient::TvmProxyTvmId << "\n";
            srvConf << R"CONFIG(
                    TvmId: 123
                    FlowMirrorTvmId: 100
                    EnableUserTickets: true
                </Tvm>
            )CONFIG";
            return srvConf.Str();
        }

        void TestRequest(const TRequestParams& reqParams) {
            TStringStream requestUrl;
            {
                const auto& srv = IsMeta() ? MetaServiceName : BackendServiceName;
                requestUrl
                    << (IsKv() ? MakeKvRequest("service", srv) : MakeFullTextRequest(srv))
                    << "&ms=proto"
                    << "&hr=da"
                    ;

                if (reqParams.ForceAuth == EForceAuth::Force) {
                    requestUrl << "&force_tvm_auth=1";
                }
            }

            if (IsKv()) {
                requestUrl << "&sp_meta_search=proxy";
            }

            THashMap<TString, TString> extraHeaders;

            TMaybe<TString> serviceTicket;
            switch (reqParams.ServiceTicketMode) {
            case EServiceTicket::NoTicket:
                break;
            case EServiceTicket::BadTicket:
                serviceTicket = "bad";
                break;
            case EServiceTicket::Allowed:
                serviceTicket = ToString(TTestMainTvmClient::AllowedSrcId);
                break;
            case EServiceTicket::Disallowed:
                serviceTicket = ToString(TTestMainTvmClient::DisallowedSrcId);
                break;
            case EServiceTicket::Throw:
                serviceTicket = "throw";
                break;
            case EServiceTicket::TvmProxy:
                serviceTicket = ToString(TTestMainTvmClient::TvmProxyTvmId);
                break;
            case EServiceTicket::FlowMirrorAllowed:
                serviceTicket = ToString(TTestFlowMirrorTvmClient::AllowedSrcId);
                break;
            };

            if (serviceTicket.Defined()) {
                switch (reqParams.TicketContainer) {
                case ETicketContainer::Header:
                    Y_ENSURE(reqParams.Proto == EProto::Http);
                    extraHeaders["X-Ya-Service-Ticket"] = *serviceTicket;
                    break;
                case ETicketContainer::Cgi:
                    requestUrl << "&ya_service_ticket=" << *serviceTicket;
                    break;
                };
            }

            TMaybe<TString> userTicket;
            switch (reqParams.UserTicketMode) {
            case EUserTicket::NoTicket:
                break;
            case EUserTicket::BadTicket:
                userTicket = "bad";
                break;
            case EUserTicket::Allowed:
                userTicket = ToString(TTestMainTvmClient::AllowedUid);
                break;
            case EUserTicket::Disallowed:
                userTicket = ToString(TTestMainTvmClient::DisallowedUid);
                break;
            case EUserTicket::Throw:
                userTicket = "throw";
                break;
            };

            if (userTicket.Defined()) {
                switch (reqParams.TicketContainer) {
                case ETicketContainer::Header:
                    Y_ENSURE(reqParams.Proto == EProto::Http);
                    extraHeaders["X-Ya-User-Ticket"] = *userTicket;
                    break;
                case ETicketContainer::Cgi:
                    requestUrl << "&ya_user_ticket=" << *userTicket;
                    break;
                };
            }

            EResult gotResult = EResult::Unknown;
            TString body;
            int httpCode = 0;

Cerr << "REQUEST: " << requestUrl.Data() << "\n";
for (const auto& [key, value] : extraHeaders) {
    Cerr << "   " << key << ": " << value << "\n";
}

            if (reqParams.Proto == EProto::Http) {
                const THttpResponse& response = GetHttpData(ProxyPort, requestUrl.Data(), extraHeaders);
                body = response.Body;
                httpCode = response.HttpCode;
            } else {
                auto handle = NNeh::Request({
                    TStringBuilder() << "inproc://localhost:" << (ProxyPort + 1),
                    requestUrl.Str()
                });

                auto resp = handle->Wait(TDuration::Seconds(1));
                Y_ENSURE(resp && !resp->IsError());

                body = resp->Data;
            }

            UNIT_ASSERT(body);
            NMetaProtocol::TReport report;
            UNIT_ASSERT(::google::protobuf::TextFormat::ParseFromString(body, &report));

            if (reqParams.Proto == EProto::Neh) {
                httpCode = report.GetErrorInfo().GetCode();
                if (!httpCode) {
                    httpCode = 200;
                }
            }

            auto checkHasDoc = [&]() {
                UNIT_ASSERT(report.GetGrouping(0).GetGroup(0).GetDocument(0).GetArchiveInfo().GetUrl() == DocUrl);
            };

            auto findError = [&](const TString& text) {
                if (text.find("No TVM ServiceTicket in user request") != TString::npos
                    || text.find("No TVM UserTicket in user request") != TString::npos) {
                    gotResult = EResult::Unauthorized;
                }
                else if (text.find("Got malformed TVM ServiceTicket from user") != TString::npos
                      || text.find("Got malformed TVM UserTicket from user") != TString::npos) {
                    gotResult = EResult::Unauthorized;
                }
                else if (text.find("TVM ServiceTicket source service id is not allowed") != TString::npos
                      || text.find("TVM UserTicket UID is not allowed") != TString::npos) {
                    gotResult = EResult::Forbidden;
                }
                else if (text.find("TTestMainTvmClient failed") != TString::npos) {
                    gotResult = EResult::InternalServerError;
                } else {
                    return false;
                }
                return true;
            };

            TMaybe<EErrorSource> errorSource;
            if (IsMeta()) {
                if (report.GroupingSize()) {
                    checkHasDoc();
                    gotResult = EResult::Authorized;
                } else {
                    if (findError(report.GetErrorInfo().GetText())) {
                        errorSource = EErrorSource::ErrorInfo;
                    } else {
                        for (const auto& prop : report.GetSearcherProp()) {
                            if (prop.GetKey() == "SearchErrors.debug") {
                                if (findError(prop.GetValue())) {
                                    errorSource = EErrorSource::SearcherProp;
                                    break;
                                }
                            }
                        }
                    }
                }
            } else {
                switch (httpCode) {
                case 200: {
                    checkHasDoc();
                    gotResult = EResult::Authorized;
                    break;
                }
                case 401:
                    gotResult = EResult::Unauthorized;
                    break;
                case 403:
                    gotResult = EResult::Forbidden;
                    break;
                case 500:
                    gotResult = EResult::InternalServerError;
                    break;
                }
            }

            TMaybe<bool> incompleteAnswer;
            if (report.GetDebugInfo().HasAnswerIsComplete()) {
                incompleteAnswer = report.GetDebugInfo().GetAnswerIsComplete();
            }

            if (gotResult != reqParams.ExpectedResult || httpCode == 404) {
                Cerr << "HTTP status code: " << httpCode << '\n';

                Cerr << "Request params: ";
                TOneTestParams{Params, reqParams}.Out(Cerr);
                Cerr << '\n';

                Cerr << "Response: " << body << '\n';
            }
            UNIT_ASSERT(httpCode != 404);
            UNIT_ASSERT(gotResult == reqParams.ExpectedResult);
        }

    private:
        const TServerParams Params;

        const TString BackendServiceName{"test"};
        const TString MetaServiceName{"test_meta"};

        const TString RequestId = "some";
        const TString Kps = "1%2C65533";
        const TString DocUrl{"http://example.com/foo"};

        THolder<TStaticServer> Backend;
        const ui16 ProxyPort = 0;
        THolder<TSearchProxyConfig> ProxyConfig;
        THolder<TAutoSearchProxyServer> Proxy;
    };

    static void TestAll() {
        const std::vector<std::pair<EServiceTicket, EResult>> ticketToResult = {
            {EServiceTicket::NoTicket, EResult::Unauthorized},
            {EServiceTicket::BadTicket, EResult::Unauthorized},
            {EServiceTicket::Disallowed, EResult::Forbidden},
            {EServiceTicket::Allowed, EResult::Authorized},
            {EServiceTicket::Throw, EResult::InternalServerError}
        };

        for (EServerAuthMode serverMode : {EServerAuthMode::Disabled, EServerAuthMode::Enabled}) {
            for (EServiceAuthMode serviceMode : {EServiceAuthMode::Disabled, EServiceAuthMode::DryRun, EServiceAuthMode::Enabled}) {
                for (EService serviceType : {EService::KeyValue, EService::FullText}) {
                    for (EMetaAuth metaAuth : {EMetaAuth::AsOnBackend, EMetaAuth::Disabled}) {
                        for (auto metaType : {EMeta::NotMeta, EMeta::Meta}) {
                            const bool allowMetaAuthSwitch = serverMode == EServerAuthMode::Enabled
                                && serviceMode == EServiceAuthMode::Enabled
                                && metaType == EMeta::Meta;

                            if (!allowMetaAuthSwitch && metaAuth == EMetaAuth::Disabled) {
                                continue;
                            }

                            TServer server({
                                serverMode,
                                serviceMode,
                                metaAuth,
                                serviceType,
                                metaType
                            });

                            for (auto proto : {EProto::Http, EProto::Neh}) {
                                for (auto ticketContainer : {ETicketContainer::Cgi, ETicketContainer::Header}) {
                                    if (proto == EProto::Neh && ticketContainer == ETicketContainer::Header) {
                                        continue;
                                    }

                                    for (auto [ticket, result] : ticketToResult) {
                                        if (serverMode != EServerAuthMode::Enabled || serviceMode != EServiceAuthMode::Enabled) {
                                            result = EResult::Authorized;
                                        }

                                        server.TestRequest({proto, ticketContainer, ticket, result, EForceAuth::DoNotForce});
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    static void TestForcedAuth() {
        struct TParams {
            EServiceAuthMode ServiceAuthMode;
            EResult ExpectedResult;
        };

        TVector<TParams> setups{
            {EServiceAuthMode::Disabled, EResult::Authorized},
            {EServiceAuthMode::DryRun, EResult::Unauthorized},
            {EServiceAuthMode::Enabled, EResult::Unauthorized}
        };

        for (EService serviceType : {EService::KeyValue, EService::FullText}) {
            for (const auto& params : setups) {
                TServer server({
                    EServerAuthMode::Enabled,
                    params.ServiceAuthMode,
                    EMetaAuth::Disabled,
                    serviceType,
                    EMeta::Meta
                });

                server.TestRequest({
                    EProto::Http,
                    ETicketContainer::Header,
                    EServiceTicket::NoTicket,
                    params.ExpectedResult,
                    EForceAuth::Force
                });
            }
        }
    }

    static void TestTvmProxyUserTickets() {
        struct TParams {
            EUserTicket UserTicket;
            EResult ExpectedResult;
        };

        TVector<TParams> setups{
            {EUserTicket::NoTicket, EResult::Unauthorized},
            {EUserTicket::BadTicket, EResult::Unauthorized},
            {EUserTicket::Allowed, EResult::Authorized},
            {EUserTicket::Disallowed, EResult::Forbidden},
            {EUserTicket::Throw, EResult::InternalServerError},
        };

        for (EService serviceType : {EService::KeyValue, EService::FullText}) {
            for (EMeta metaType : {EMeta::NotMeta, EMeta::Meta}) {
                for (const auto& params : setups) {
                    TServer server({
                        EServerAuthMode::Enabled,
                        EServiceAuthMode::Enabled,
                        EMetaAuth::AsOnBackend,
                        serviceType,
                        metaType,
                    });

                    server.TestRequest({
                        EProto::Http,
                        ETicketContainer::Header,
                        EServiceTicket::TvmProxy,
                        params.ExpectedResult,
                        EForceAuth::DoNotForce,
                        params.UserTicket,
                    });
                }
            }
        }
    }

    static void TestAllowedServiceButBadUserTicket() {
        TServer server({
            EServerAuthMode::Enabled,
            EServiceAuthMode::Enabled,
            EMetaAuth::AsOnBackend,
            EService::KeyValue,
            EMeta::NotMeta,
        });

        server.TestRequest({
            EProto::Http,
            ETicketContainer::Header,
            EServiceTicket::Allowed,
            EResult::Authorized,
            EForceAuth::DoNotForce,
            EUserTicket::BadTicket
        });
    }

    static void TestNoFlowMirror() {
        struct TParams {
            EServiceAuthMode ServiceAuthMode;
            EServiceTicket ServiceTicket;
        };

        TVector<TParams> setups{
            {EServiceAuthMode::Disabled, EServiceTicket::NoTicket},
            {EServiceAuthMode::Enabled, EServiceTicket::Allowed},
        };

        for (const TParams& params : setups) {
            TServer server({
                EServerAuthMode::Enabled,
                params.ServiceAuthMode,
                EMetaAuth::AsOnBackend,
                EService::KeyValue,
                EMeta::NotMeta,
                EFlowMirror::Disabled
            });

            server.TestRequest({
                EProto::Http,
                ETicketContainer::Header,
                params.ServiceTicket,
                EResult::Authorized,
                EForceAuth::DoNotForce,
                EUserTicket::NoTicket
            });
        }
    }

    static void TestFlowMirrorAllowed() {
        TServer server({
            EServerAuthMode::Enabled,
            EServiceAuthMode::Enabled,
            EMetaAuth::AsOnBackend,
            EService::KeyValue,
            EMeta::NotMeta,
        });

        server.TestRequest({
            EProto::Http,
            ETicketContainer::Header,
            EServiceTicket::FlowMirrorAllowed,
            EResult::Authorized,
            EForceAuth::DoNotForce,
            EUserTicket::BadTicket
        });
    }

};

void TSearchProxyTvmTest::TServer::TOneTestParams::Out(IOutputStream& out) const {
    out
        << "{\n"
        << "    {\n"
        << "        EServerAuthMode::" << ServerParams.ServerAuthMode << ",\n"
        << "        EServiceAuthMode::" << ServerParams.ServiceAuthMode << ",\n"
        << "        EMetaAuth::" << ServerParams.MetaAuth << ",\n"
        << "        EService::" << ServerParams.ServiceType << ",\n"
        << "        EMeta::" << ServerParams.MetaType << "\n"
        << "    },\n"
        << "    {\n"
        << "        EProto::" << RequestParams.Proto << ",\n"
        << "        ETicketContainer::" << RequestParams.TicketContainer << ",\n"
        << "        EServiceTicket::" << RequestParams.ServiceTicketMode << ",\n"
        << "        EResult::" << RequestParams.ExpectedResult << ",\n"
        << "        EForceAuth::" << RequestParams.ForceAuth << ",\n"
        << "        EUserTicket::" << RequestParams.UserTicketMode << ",\n"
        << "    }\n"
        << "}\n";
}


UNIT_TEST_SUITE_REGISTRATION(TSearchProxyTvmTest)
