// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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 <stdio.h>
#include <gtest/gtest.h>
#include <vector>
#include "butil/string_printf.h"
#include "butil/strings/string_split.h"
#include "butil/files/temp_file.h"
#include "bthread/bthread.h"
#include "brpc/http_status_code.h"
#ifdef BAIDU_INTERNAL
#include "brpc/policy/baidu_naming_service.h"
#endif
#include "brpc/policy/consul_naming_service.h"
#include "brpc/policy/domain_naming_service.h"
#include "brpc/policy/file_naming_service.h"
#include "brpc/policy/list_naming_service.h"
#include "brpc/policy/remote_file_naming_service.h"
#include "brpc/policy/discovery_naming_service.h"
#include "brpc/policy/nacos_naming_service.h"
#include "echo.pb.h"
#include "brpc/server.h"


namespace brpc {
DECLARE_int32(health_check_interval);

namespace policy {

DECLARE_bool(consul_enable_degrade_to_file_naming_service);
DECLARE_string(consul_file_naming_service_dir);
DECLARE_string(consul_service_discovery_url);
DECLARE_string(discovery_api_addr);
DECLARE_string(discovery_env);
DECLARE_int32(discovery_renew_interval_s);
DECLARE_string(nacos_address);
DECLARE_string(nacos_username);
DECLARE_string(nacos_password);

} // policy
} // brpc

namespace {

bool IsIPListEqual(const std::set<butil::ip_t>& s1, const std::set<butil::ip_t>& s2) {
    if (s1.size() != s2.size()) {
        return false;
    }
    for (auto it1 = s1.begin(), it2 = s2.begin(); it1 != s1.end(); ++it1, ++it2) {
        if (*it1 != *it2) {
            return false;
        }
    }
    return true;
}

TEST(NamingServiceTest, sanity) {
    std::vector<brpc::ServerNode> servers;

#ifdef BAIDU_INTERNAL
    brpc::policy::BaiduNamingService bns;
    ASSERT_EQ(0, bns.GetServers("qa-pbrpc.SAT.tjyx", &servers));
#endif

    auto collect_ips = [&servers]() {
        std::set<butil::ip_t> ret;
        for (auto& server : servers) {
            ret.insert(server.addr.ip);
        }
        return ret;
    };
    brpc::policy::DomainNamingService dns;
    ASSERT_EQ(0, dns.GetServers("baidu.com:1234", &servers));
    size_t server_size = servers.size();
    ASSERT_GE(server_size, 1);
    for (size_t i = 0; i < servers.size(); ++i) {
        ASSERT_EQ(1234, servers[i].addr.port);
    }
    const auto expected_ips = collect_ips();

    ASSERT_EQ(0, dns.GetServers("baidu.com", &servers));
    ASSERT_EQ(server_size, servers.size());
    ASSERT_TRUE(IsIPListEqual(expected_ips, collect_ips()));
    for (size_t i = 0; i < servers.size(); ++i) {
        ASSERT_EQ(80, servers[i].addr.port);
    }

    ASSERT_EQ(0, dns.GetServers("baidu.com:1234/useless1/useless2", &servers));
    ASSERT_EQ(server_size, servers.size());
    ASSERT_TRUE(IsIPListEqual(expected_ips, collect_ips()));
    for (size_t i = 0; i < servers.size(); ++i) {
        ASSERT_EQ(1234, servers[i].addr.port);
    }

    ASSERT_EQ(0, dns.GetServers("baidu.com/useless1/useless2", &servers));
    ASSERT_EQ(server_size, servers.size());
    ASSERT_TRUE(IsIPListEqual(expected_ips, collect_ips()));
    for (size_t i = 0; i < servers.size(); ++i) {
        ASSERT_EQ(80, servers[i].addr.port);
    }

    const char *address_list[] =  {
        "10.127.0.1:1234",
        "10.128.0.1:1234",
        "10.129.0.1:1234",
        "localhost:1234",
        "baidu.com:1234"
    };
    butil::TempFile tmp_file;
    {
        FILE* fp = fopen(tmp_file.fname(), "w");
        for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) {
            ASSERT_TRUE(fprintf(fp, "%s\n", address_list[i]));
        }
        fclose(fp);
    }
    brpc::policy::FileNamingService fns;
    ASSERT_EQ(0, fns.GetServers(tmp_file.fname(), &servers));
    ASSERT_EQ(ARRAY_SIZE(address_list), servers.size());
    for (size_t i = 0; i < ARRAY_SIZE(address_list) - 2; ++i) {
        std::ostringstream oss;
        oss << servers[i];
        ASSERT_EQ(address_list[i], oss.str()) << "i=" << i;
    }

    std::string s;
    for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) {
        ASSERT_EQ(0, butil::string_appendf(&s, "%s,", address_list[i]));
    }
    brpc::policy::ListNamingService lns;
    ASSERT_EQ(0, lns.GetServers(s.c_str(), &servers));
    ASSERT_EQ(ARRAY_SIZE(address_list), servers.size());
    for (size_t i = 0; i < ARRAY_SIZE(address_list) - 2; ++i) {
        std::ostringstream oss;
        oss << servers[i];
        ASSERT_EQ(address_list[i], oss.str()) << "i=" << i;
    }
}

TEST(NamingServiceTest, invalid_port) {
    std::vector<brpc::ServerNode> servers;

#ifdef BAIDU_INTERNAL
    brpc::policy::BaiduNamingService bns;
    ASSERT_EQ(0, bns.GetServers("qa-pbrpc.SAT.tjyx:main", &servers));
#endif

    brpc::policy::DomainNamingService dns;
    ASSERT_EQ(-1, dns.GetServers("baidu.com:", &servers));
    ASSERT_EQ(-1, dns.GetServers("baidu.com:123a", &servers));
    ASSERT_EQ(-1, dns.GetServers("baidu.com:99999", &servers));
}

TEST(NamingServiceTest, wrong_name) {
    std::vector<brpc::ServerNode> servers;

#ifdef BAIDU_INTERNAL
    brpc::policy::BaiduNamingService bns;
    ASSERT_EQ(-1, bns.GetServers("Wrong", &servers));
#endif

    const char *address_list[] =  {
        "10.127.0.1:1234",
        "10.128.0.1:12302344",
        "10.129.0.1:1234",
        "10.128.0.1:",
        "10.128.0.1",
        "localhost:1234",
        "baidu.com:1234",
        "LOCAL:1234"
    };
    butil::TempFile tmp_file;
    {
        FILE *fp = fopen(tmp_file.fname(), "w");
        for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) {
            ASSERT_TRUE(fprintf(fp, "%s\n", address_list[i]));
        }
        fclose(fp);
    }
    brpc::policy::FileNamingService fns;
    ASSERT_EQ(0, fns.GetServers(tmp_file.fname(), &servers));
    ASSERT_EQ(ARRAY_SIZE(address_list) - 4, servers.size());

    std::string s;
    for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) {
        ASSERT_EQ(0, butil::string_appendf(&s, ", %s", address_list[i]));
    }
    brpc::policy::ListNamingService lns;
    ASSERT_EQ(0, lns.GetServers(s.c_str(), &servers));
    ASSERT_EQ(ARRAY_SIZE(address_list) - 4, servers.size());
}

class UserNamingServiceImpl : public test::UserNamingService {
public:
    UserNamingServiceImpl() : list_names_count(0), touch_count(0) {}
    ~UserNamingServiceImpl() { }
    void ListNames(google::protobuf::RpcController* cntl_base,
                   const test::HttpRequest*,
                   test::HttpResponse*,
                   google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = (brpc::Controller*)cntl_base;
        cntl->http_response().set_content_type("text/plain");
        cntl->response_attachment().append(
            "0.0.0.0:8635 tag1\r\n0.0.0.0:8636 tag2\n"
            "0.0.0.0:8635 tag3\r\n0.0.0.0:8636\r\n");
        list_names_count.fetch_add(1);
    }
    void Touch(google::protobuf::RpcController*,
               const test::HttpRequest*,
               test::HttpResponse*,
               google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        touch_count.fetch_add(1);
    }

    butil::atomic<int64_t> list_names_count;
    butil::atomic<int64_t> touch_count;
};

TEST(NamingServiceTest, remotefile) {
    brpc::Server server1;
    UserNamingServiceImpl svc1;
    ASSERT_EQ(0, server1.AddService(&svc1, brpc::SERVER_DOESNT_OWN_SERVICE));
    ASSERT_EQ(0, server1.Start("localhost:8635", NULL));
    brpc::Server server2;
    UserNamingServiceImpl svc2;
    ASSERT_EQ(0, server2.AddService(&svc2, brpc::SERVER_DOESNT_OWN_SERVICE));
    ASSERT_EQ(0, server2.Start("localhost:8636", NULL));

    butil::EndPoint n1;
    ASSERT_EQ(0, butil::str2endpoint("0.0.0.0:8635", &n1));
    butil::EndPoint n2;
    ASSERT_EQ(0, butil::str2endpoint("0.0.0.0:8636", &n2));
    std::vector<brpc::ServerNode> expected_servers;
    expected_servers.push_back(brpc::ServerNode(n1, "tag1"));
    expected_servers.push_back(brpc::ServerNode(n2, "tag2"));
    expected_servers.push_back(brpc::ServerNode(n1, "tag3"));
    expected_servers.push_back(brpc::ServerNode(n2));
    std::sort(expected_servers.begin(), expected_servers.end());

    std::vector<brpc::ServerNode> servers;
    brpc::policy::RemoteFileNamingService rfns;
    ASSERT_EQ(0, rfns.GetServers("0.0.0.0:8635/UserNamingService/ListNames", &servers));
    ASSERT_EQ(expected_servers.size(), servers.size());
    std::sort(servers.begin(), servers.end());
    for (size_t i = 0; i < expected_servers.size(); ++i) {
        ASSERT_EQ(expected_servers[i], servers[i]);
    }

    ASSERT_EQ(0, rfns.GetServers("http://0.0.0.0:8635/UserNamingService/ListNames", &servers));
    ASSERT_EQ(expected_servers.size(), servers.size());
    std::sort(servers.begin(), servers.end());
    for (size_t i = 0; i < expected_servers.size(); ++i) {
        ASSERT_EQ(expected_servers[i], servers[i]);
    }
}

class ConsulNamingServiceImpl : public test::UserNamingService {
public:
  ConsulNamingServiceImpl() : list_names_count(0), touch_count(0) {
  }
    ~ConsulNamingServiceImpl() { }
    void ListNames(google::protobuf::RpcController* cntl_base,
                   const test::HttpRequest*,
                   test::HttpResponse*,
                   google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = (brpc::Controller*)cntl_base;
        cntl->http_response().SetHeader("X-Consul-Index", "1");
        cntl->response_attachment().append(
            R"([
                {
                    "Node": {
                        "ID": "44454c4c-4e00-1050-8052-b7c04f4b5931",
                        "Node": "sh-qs-10.121.36.189",
                        "Address": "10.121.36.189",
                        "Datacenter": "shjj",
                        "TaggedAddresses": {
                            "lan": "10.121.36.189",
                            "wan": "10.121.36.189"
                        },
                        "Meta": {
                            "consul-network-segment": ""
                        },
                        "CreateIndex": 4820296,
                        "ModifyIndex": 4823818
                    },
                    "Service": {
                        "ID": "10.121.36.189_8003_qs_show_leaf",
                        "Service": "qs_show_leaf",
                        "Tags": ["1"],
                        "Address": "10.121.36.189",
                        "Port": 8003,
                        "EnableTagOverride": false,
                        "CreateIndex": 6515285,
                        "ModifyIndex": 6515285
                    },
                    "Checks": [
                        {
                            "Node": "sh-qs-10.121.36.189",
                            "CheckID": "serfHealth",
                            "Name": "Serf Health Status",
                            "Status": "passing",
                            "Notes": "",
                            "Output": "Agent alive and reachable",
                            "ServiceID": "",
                            "ServiceName": "",
                            "ServiceTags": [ ],
                            "CreateIndex": 4820296,
                            "ModifyIndex": 4820296
                        },
                        {
                            "Node": "sh-qs-10.121.36.189",
                            "CheckID": "service:10.121.36.189_8003_qs_show_leaf",
                            "Name": "Service 'qs_show_leaf' check",
                            "Status": "passing",
                            "Notes": "",
                            "Output": "TCP connect 10.121.36.189:8003: Success",
                            "ServiceID": "10.121.36.189_8003_qs_show_leaf",
                            "ServiceName": "qs_show_leaf",
                            "ServiceTags": [ ],
                            "CreateIndex": 6515285,
                            "ModifyIndex": 6702198
                        }
                    ]
                },
                {
                    "Node": {
                        "ID": "44454c4c-4b00-1050-8052-b6c04f4b5931",
                        "Node": "sh-qs-10.121.36.190",
                        "Address": "10.121.36.190",
                        "Datacenter": "shjj",
                        "TaggedAddresses": {
                            "lan": "10.121.36.190",
                            "wan": "10.121.36.190"
                        },
                        "Meta": {
                            "consul-network-segment": ""
                        },
                        "CreateIndex": 4820296,
                        "ModifyIndex": 4823751
                    },
                    "Service": {
                        "ID": "10.121.36.190_8003_qs_show_leaf",
                        "Service": "qs_show_leaf",
                        "Tags": ["2"],
                        "Address": "10.121.36.190",
                        "Port": 8003,
                        "EnableTagOverride": false,
                        "CreateIndex": 6515635,
                        "ModifyIndex": 6515635
                    },
                    "Checks": [
                        {
                            "Node": "sh-qs-10.121.36.190",
                            "CheckID": "serfHealth",
                            "Name": "Serf Health Status",
                            "Status": "passing",
                            "Notes": "",
                            "Output": "Agent alive and reachable",
                            "ServiceID": "",
                            "ServiceName": "",
                            "ServiceTags": [ ],
                            "CreateIndex": 4820296,
                            "ModifyIndex": 4820296
                        },
                        {
                            "Node": "sh-qs-10.121.36.190",
                            "CheckID": "service:10.121.36.190_8003_qs_show_leaf",
                            "Name": "Service 'qs_show_leaf' check",
                            "Status": "passing",
                            "Notes": "",
                            "Output": "TCP connect 10.121.36.190:8003: Success",
                            "ServiceID": "10.121.36.190_8003_qs_show_leaf",
                            "ServiceName": "qs_show_leaf",
                            "ServiceTags": [ ],
                            "CreateIndex": 6515635,
                            "ModifyIndex": 6705515
                        }
                    ]
                }
            ])");
        list_names_count.fetch_add(1);
    }
    void Touch(google::protobuf::RpcController*,
               const test::HttpRequest*,
               test::HttpResponse*,
               google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        touch_count.fetch_add(1);
    }

    butil::atomic<int64_t> list_names_count;
    butil::atomic<int64_t> touch_count;
};

TEST(NamingServiceTest, consul_with_backup_file) {
    brpc::policy::FLAGS_consul_enable_degrade_to_file_naming_service = true;
    const int saved_hc_interval = brpc::FLAGS_health_check_interval;
    brpc::FLAGS_health_check_interval = 1;
    const char *address_list[] =  {
        "10.127.0.1:1234",
        "10.128.0.1:1234",
        "10.129.0.1:1234",
    };
    butil::TempFile tmp_file;
    const char * service_name = tmp_file.fname();
    {
        FILE* fp = fopen(tmp_file.fname(), "w");
        for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) {
            ASSERT_TRUE(fprintf(fp, "%s\n", address_list[i]));
        }
        fclose(fp);
    }
    std::cout << tmp_file.fname() << std::endl;

    std::vector<brpc::ServerNode> servers;
    brpc::policy::ConsulNamingService cns;
    ASSERT_EQ(0, cns.GetServers(service_name, &servers));
    ASSERT_EQ(ARRAY_SIZE(address_list), servers.size());
    for (size_t i = 0; i < ARRAY_SIZE(address_list); ++i) {
        std::ostringstream oss;
        oss << servers[i];
        ASSERT_EQ(address_list[i], oss.str()) << "i=" << i;
    }

    brpc::Server server;
    ConsulNamingServiceImpl svc;
    std::string restful_map(brpc::policy::FLAGS_consul_service_discovery_url);
    restful_map.append("/");
    restful_map.append(service_name);
    restful_map.append("   => ListNames");
    ASSERT_EQ(0, server.AddService(&svc,
                                   brpc::SERVER_DOESNT_OWN_SERVICE,
                                   restful_map.c_str()));
    ASSERT_EQ(0, server.Start("localhost:8500", NULL));

    bthread_usleep(5000000);

    butil::EndPoint n1;
    ASSERT_EQ(0, butil::str2endpoint("10.121.36.189:8003", &n1));
    butil::EndPoint n2;
    ASSERT_EQ(0, butil::str2endpoint("10.121.36.190:8003", &n2));
    std::vector<brpc::ServerNode> expected_servers;
    expected_servers.push_back(brpc::ServerNode(n1, "1"));
    expected_servers.push_back(brpc::ServerNode(n2, "2"));
    std::sort(expected_servers.begin(), expected_servers.end());

    servers.clear();
    ASSERT_EQ(0, cns.GetServers(service_name, &servers));
    ASSERT_EQ(expected_servers.size(), servers.size());
    std::sort(servers.begin(), servers.end());
    for (size_t i = 0; i < expected_servers.size(); ++i) {
        ASSERT_EQ(expected_servers[i], servers[i]);
    }
    brpc::FLAGS_health_check_interval = saved_hc_interval;
}


static const std::string s_fetchs_result = R"({
    "code":0,
    "message":"0",
    "ttl":1,
    "data":{
        "admin.test":{
            "instances":[
                {
                    "region":"",
                    "zone":"sh001",
                    "env":"uat",
                    "appid":"admin.test",
                    "treeid":0,
                    "hostname":"host123",
                    "http":"",
                    "rpc":"",
                    "version":"123",
                    "metadata":{
                        "weight": "10",
                        "cluster": ""
                    },
                    "addrs":[
                        "http://127.0.0.1:8999",
                        "grpc://127.0.1.1:9000"
                    ],
                    "status":1,
                    "reg_timestamp":1539001034551496412,
                    "up_timestamp":1539001034551496412,
                    "renew_timestamp":1539001034551496412,
                    "dirty_timestamp":1539001034551496412,
                    "latest_timestamp":1539001034551496412
                }
            ],
            "zone_instances":{
                "sh001":[
                    {
                        "region":"",
                        "zone":"sh001",
                        "env":"uat",
                        "appid":"admin.test",
                        "treeid":0,
                        "hostname":"host123",
                        "http":"",
                        "rpc":"",
                        "version":"123",
                        "metadata":{
                            "weight": "10",
                            "cluster": ""
                        },
                        "addrs":[
                            "http://127.0.0.1:8999",
                            "grpc://127.0.1.1:9000"
                        ],
                        "status":1,
                        "reg_timestamp":1539001034551496412,
                        "up_timestamp":1539001034551496412,
                        "renew_timestamp":1539001034551496412,
                        "dirty_timestamp":1539001034551496412,
                        "latest_timestamp":1539001034551496412
                    }
                ]
            },
            "latest_timestamp":1539001034551496412,
            "latest_timestamp_str":"1539001034"
        }
    }
})";

static std::string s_nodes_result = R"({
    "code": 0,
    "message": "0",
    "ttl": 1,
    "data": [
        {
            "addr": "127.0.0.1:8635",
            "status": 0,
            "zone": ""
        }, {
            "addr": "172.18.33.51:7171",
            "status": 0,
            "zone": ""
        }, {
            "addr": "172.18.33.52:7171",
            "status": 0,
            "zone": ""
        }
    ]
})";


class DiscoveryNamingServiceImpl : public test::DiscoveryNamingService {
public:
    DiscoveryNamingServiceImpl()
        : _renew_count(0)
        , _cancel_count(0) {}
    virtual ~DiscoveryNamingServiceImpl() {}

    void Nodes(google::protobuf::RpcController* cntl_base,
               const test::HttpRequest*,
               test::HttpResponse*,
               google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
        cntl->response_attachment().append(s_nodes_result);
    }

    void Fetchs(google::protobuf::RpcController* cntl_base,
                const test::HttpRequest*,
                test::HttpResponse*,
                google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
        cntl->response_attachment().append(s_fetchs_result);
    }

    void Register(google::protobuf::RpcController* cntl_base,
                 const test::HttpRequest*,
                 test::HttpResponse*,
                 google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
        auto body = cntl->request_attachment().to_string();
        for (brpc::QuerySplitter sp(body); sp; ++sp) {
            if (sp.key() == "addrs") {
                _addrs.insert(sp.value().as_string());
            }
        }
        cntl->response_attachment().append(R"({
            "code": 0,
            "message": "0"
        })");
        return;
    }

    void Renew(google::protobuf::RpcController* cntl_base,
               const test::HttpRequest*,
               test::HttpResponse*,
               google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
        cntl->response_attachment().append(R"({
            "code": 0,
            "message": "0"
        })");
        _renew_count++;
        return;
    }

    void Cancel(google::protobuf::RpcController* cntl_base,
                const test::HttpRequest*,
                test::HttpResponse*,
                google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
        cntl->response_attachment().append(R"({
            "code": 0,
            "message": "0"
        })");
        _cancel_count++;
        _addrs.clear();
        return;
    }

    int RenewCount() const { return _renew_count; }
    int CancelCount() const { return _cancel_count; }

    bool HasAddr(const std::string& addr) const {
        return _addrs.find(addr) != _addrs.end();
    }
    int AddrCount() const { return _addrs.size(); }

private:
    int _renew_count;
    int _cancel_count;

    std::set<std::string> _addrs;
};

TEST(NamingServiceTest, discovery_sanity) {
    brpc::policy::FLAGS_discovery_api_addr = "http://127.0.0.1:8635/discovery/nodes";
    brpc::policy::FLAGS_discovery_renew_interval_s = 1;
    brpc::Server server;
    DiscoveryNamingServiceImpl svc;
    std::string rest_mapping =
        "/discovery/nodes => Nodes, "
        "/discovery/fetchs => Fetchs, "
        "/discovery/register => Register, "
        "/discovery/renew => Renew, "
        "/discovery/cancel => Cancel";
    ASSERT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE,
                rest_mapping.c_str()));
    ASSERT_EQ(0, server.Start("localhost:8635", NULL));

    brpc::policy::DiscoveryNamingService dcns;
    std::vector<brpc::ServerNode> servers;
    ASSERT_EQ(0, dcns.GetServers("admin.test", &servers));
    ASSERT_EQ((size_t)1, servers.size());

    brpc::policy::DiscoveryRegisterParam dparam;
    dparam.appid = "main.test";
    dparam.hostname = "hostname";
    dparam.addrs = "grpc://10.0.0.1:8000";
    dparam.env = "dev";
    dparam.zone = "sh001";
    dparam.status = 1;
    dparam.version = "v1";
    {
        brpc::policy::DiscoveryClient dc;
    }
    // Cancel is called iff Register is called
    ASSERT_EQ(svc.CancelCount(), 0);
    {
        brpc::policy::DiscoveryClient dc;
        // Two Register should start one Renew task , and make
        // svc.RenewCount() be one.
        ASSERT_EQ(0, dc.Register(dparam));
        ASSERT_EQ(0, dc.Register(dparam));
        bthread_usleep(100000);
        ASSERT_TRUE(svc.HasAddr("grpc://10.0.0.1:8000"));
        ASSERT_FALSE(svc.HasAddr("http://10.0.0.1:8000"));
    }
    ASSERT_EQ(svc.RenewCount(), 1);
    ASSERT_EQ(svc.CancelCount(), 1);

    ASSERT_FALSE(svc.HasAddr("grpc://10.0.0.1:8000"));
    ASSERT_FALSE(svc.HasAddr("http://10.0.0.1:8000"));

    // addrs splitted by `,'
    dparam.addrs = ",grpc://10.0.0.1:8000,,http://10.0.0.1:8000,";
    {
        brpc::policy::DiscoveryClient dc;
        ASSERT_EQ(0, dc.Register(dparam));
        ASSERT_TRUE(svc.HasAddr("grpc://10.0.0.1:8000"));
        ASSERT_TRUE(svc.HasAddr("http://10.0.0.1:8000"));
        ASSERT_FALSE(svc.HasAddr(std::string()));
        ASSERT_EQ(2, svc.AddrCount());
    }
}

class NacosNamingServiceImpl : public test::NacosNamingService {
public:
    void Login(google::protobuf::RpcController* cntl_base,
               const test::HttpRequest*, test::HttpResponse*,
               google::protobuf::Closure* done) override {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);

        butil::StringPairs user;
        butil::SplitStringIntoKeyValuePairs(
            cntl->request_attachment().to_string(), '=', '&', &user);

        const auto expected_user =
            butil::StringPairs{{"username", "nacos"}, {"password", "nacos"}};

        if (user == expected_user) {
            cntl->http_response().set_content_type("application/json");
            cntl->response_attachment().append(
R"({
     "accessToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTY2MzAwODMzNn0.YKJJwzHT4v9cpC7kVqWroeJK1WioOYe0JZy4KX8nExs",
     "tokenTtl": 18000,
     "globalAdmin": true,
     "username": "nacos"
   })");
        } else {
            cntl->http_response().set_status_code(brpc::HTTP_STATUS_FORBIDDEN);
            cntl->response_attachment().append("unknow user!");
        }
    }

    void List(google::protobuf::RpcController* cntl_base,
              const test::HttpRequest*, test::HttpResponse*,
              google::protobuf::Closure* done) override {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = (brpc::Controller*)cntl_base;

        auto token = cntl->http_request().uri().GetQuery("accessToken");
        if (token == nullptr ||
            *token !=
                "eyJhbGciOiJIUzI1NiJ9."
                "eyJzdWIiOiJuYWNvcyIsImV4cCI6MTY2MzAwODMzNn0."
                "YKJJwzHT4v9cpC7kVqWroeJK1WioOYe0JZy4KX8nExs") {
            cntl->http_response().set_status_code(brpc::HTTP_STATUS_FORBIDDEN);
            cntl->response_attachment().append(
R"({
     "timestamp": "2022-09-12T22:56:02.730+08:00",
     "status": 403,
     "error": "Forbidden",
     "path": "/nacos/v1/ns/instance/list"
   })");
            return;
        }

        auto service_name = cntl->http_request().uri().GetQuery("serviceName");
        auto group_name = cntl->http_request().uri().GetQuery("groupName");
        auto namespace_id = cntl->http_request().uri().GetQuery("namespaceId");
        auto clusters = cntl->http_request().uri().GetQuery("clusters");
        if (service_name == nullptr || *service_name != "test" ||
            group_name == nullptr || *group_name != "g1" ||
            namespace_id == nullptr || *namespace_id != "n1" ||
            clusters == nullptr || *clusters != "wx") {
            cntl->http_response().set_status_code(brpc::HTTP_STATUS_NOT_FOUND);
            return;
        }

        cntl->http_response().set_content_type("application/json");
        cntl->response_attachment().append(
R"({
     "name": "g1@@test",
     "groupName": "g1",
     "clusters": "wx",
     "cacheMillis": 10000,
     "hosts":
       [
         {
           "instanceId": "127.0.0.1#8888#wx#g1@@test",
           "ip": "127.0.0.1",
           "port": 8888,
           "weight": 10.0,
           "healthy": true,
           "enabled": true,
           "ephemeral": true,
           "clusterName": "wx",
           "serviceName": "g1@@test",
           "metadata": {},
           "instanceHeartBeatInterval": 5000,
           "instanceHeartBeatTimeOut": 15000,
           "ipDeleteTimeout": 30000,
           "instanceIdGenerator": "simple"
         }
       ],
     "lastRefTime": 1662990336712,
     "checksum": "",
     "allIPs": false,
     "reachProtectionThreshold": false,
     "valid": true
   })");
    }
};

TEST(NamingServiceTest, nacos) {
    brpc::Server server;
    NacosNamingServiceImpl svc;
    ASSERT_EQ(0, server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE,
                                   "/nacos/v1/auth/login => Login, "
                                   "/nacos/v1/ns/instance/list => List"));
    ASSERT_EQ(0, server.Start("localhost:8848", nullptr));

    bthread_usleep(5000000);

    butil::EndPoint ep;
    ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:8888", &ep));
    const auto expected_node = brpc::ServerNode(ep, "10");

    const char* service_name =
        "serviceName=test&groupName=g1&namespaceId=n1&clusters=wx";
    brpc::policy::FLAGS_nacos_address = "http://localhost:8848";
    brpc::policy::FLAGS_nacos_username = "nacos";
    brpc::policy::FLAGS_nacos_password = "nacos";

    {
        brpc::policy::NacosNamingService nns;
        std::vector<brpc::ServerNode> nodes;
        ASSERT_EQ(0, nns.GetServers(service_name, &nodes));
        ASSERT_EQ(nodes.size(), 1);
        ASSERT_EQ(expected_node, nodes[0]);
    }
    {
        brpc::policy::FLAGS_nacos_password = "invalid_password";
        brpc::policy::NacosNamingService nns;
        std::vector<brpc::ServerNode> nodes;
        ASSERT_NE(0, nns.GetServers(service_name, &nodes));
    }
}

} //namespace
