// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "discovery/dnssd/impl/querier_impl.h"

#include <memory>
#include <string>
#include <utility>

#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/types/optional.h"
#include "discovery/common/testing/mock_reporting_client.h"
#include "discovery/dnssd/impl/conversion_layer.h"
#include "discovery/dnssd/testing/fake_network_interface_config.h"
#include "discovery/mdns/mdns_records.h"
#include "discovery/mdns/testing/mdns_test_util.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "platform/test/fake_clock.h"
#include "platform/test/fake_task_runner.h"
#include "util/osp_logging.h"

namespace openscreen {
namespace discovery {
namespace {

NetworkInterfaceIndex kNetworkInterface = 0;

class MockCallback : public DnsSdQuerier::Callback {
 public:
  MOCK_METHOD1(OnEndpointCreated, void(const DnsSdInstanceEndpoint&));
  MOCK_METHOD1(OnEndpointUpdated, void(const DnsSdInstanceEndpoint&));
  MOCK_METHOD1(OnEndpointDeleted, void(const DnsSdInstanceEndpoint&));
};

class MockMdnsService : public MdnsService {
 public:
  MOCK_METHOD4(
      StartQuery,
      void(const DomainName&, DnsType, DnsClass, MdnsRecordChangedCallback*));

  MOCK_METHOD4(
      StopQuery,
      void(const DomainName&, DnsType, DnsClass, MdnsRecordChangedCallback*));

  MOCK_METHOD1(ReinitializeQueries, void(const DomainName& name));

  // Unused.
  MOCK_METHOD3(StartProbe,
               Error(MdnsDomainConfirmedProvider*, DomainName, IPAddress));
  MOCK_METHOD1(RegisterRecord, Error(const MdnsRecord&));
  MOCK_METHOD1(UnregisterRecord, Error(const MdnsRecord&));
  MOCK_METHOD2(UpdateRegisteredRecord,
               Error(const MdnsRecord&, const MdnsRecord&));
};

class MockDnsDataGraph : public DnsDataGraph {
 public:
  MOCK_METHOD2(StartTracking,
               void(const DomainName& domain,
                    DomainChangeCallback on_start_tracking));
  MOCK_METHOD2(StopTracking,
               void(const DomainName& domain,
                    DomainChangeCallback on_start_tracking));

  MOCK_CONST_METHOD2(
      CreateEndpoints,
      std::vector<ErrorOr<DnsSdInstanceEndpoint>>(DomainGroup,
                                                  const DomainName&));

  MOCK_METHOD4(ApplyDataRecordChange,
               Error(MdnsRecord,
                     RecordChangedEvent,
                     DomainChangeCallback,
                     DomainChangeCallback));

  MOCK_CONST_METHOD0(GetTrackedDomainCount, size_t());

  MOCK_CONST_METHOD1(IsTracked, bool(const DomainName&));
};

}  // namespace

using testing::_;
using testing::ByMove;
using testing::Return;
using testing::StrictMock;

class QuerierImplTesting : public QuerierImpl {
 public:
  QuerierImplTesting()
      : QuerierImpl(&mock_service_,
                    &task_runner_,
                    &reporting_client_,
                    &network_config_),
        clock_(Clock::now()),
        task_runner_(&clock_) {}

  StrictMock<MockMdnsService>& service() { return mock_service_; }

  StrictMock<MockReportingClient>& reporting_client() {
    return reporting_client_;
  }

  // NOTE: This should only be used for testing hard-to-achieve edge cases.
  StrictMock<MockDnsDataGraph>& GetMockedGraph() {
    if (!is_graph_mocked_) {
      graph_ = std::make_unique<StrictMock<MockDnsDataGraph>>();
      is_graph_mocked_ = true;
    }

    return static_cast<StrictMock<MockDnsDataGraph>&>(*graph_);
  }

  size_t GetTrackedDomainCount() { return graph_->GetTrackedDomainCount(); }

  bool IsDomainTracked(const DomainName& domain) {
    return graph_->IsTracked(domain);
  }

  using QuerierImpl::OnRecordChanged;

 private:
  FakeClock clock_;
  FakeTaskRunner task_runner_;
  FakeNetworkInterfaceConfig network_config_;
  StrictMock<MockMdnsService> mock_service_;
  StrictMock<MockReportingClient> reporting_client_;

  bool is_graph_mocked_ = false;
};

class DnsSdQuerierImplTest : public testing::Test {
 public:
  DnsSdQuerierImplTest()
      : querier(std::make_unique<QuerierImplTesting>()),
        ptr_domain(DomainName{"_service", "_udp", domain}),
        name(DomainName{instance, "_service", "_udp", domain}),
        name2(DomainName{instance2, "_service", "_udp", domain}) {
    EXPECT_FALSE(querier->IsQueryRunning(service));

    EXPECT_CALL(querier->service(),
                StartQuery(_, DnsType::kANY, DnsClass::kANY, _))
        .Times(1);
    querier->StartQuery(service, &callback);
    EXPECT_TRUE(querier->IsQueryRunning(service));
    testing::Mock::VerifyAndClearExpectations(&querier->service());

    EXPECT_TRUE(querier->IsQueryRunning(service));
    testing::Mock::VerifyAndClearExpectations(&querier->service());
  }

 protected:
  void ValidateRecordChangeStartsQuery(
      const std::vector<PendingQueryChange>& changes,
      const DomainName& name,
      size_t expected_size) {
    ValidateRecordChangeResult(changes, name, expected_size,
                               PendingQueryChange::kStartQuery);
  }

  void ValidateRecordChangeStopsQuery(
      const std::vector<PendingQueryChange>& changes,
      const DomainName& name,
      size_t expected_size) {
    ValidateRecordChangeResult(changes, name, expected_size,
                               PendingQueryChange::kStopQuery);
  }

  void CreateServiceInstance(const DomainName& service_domain,
                             MockCallback* cb) {
    MdnsRecord ptr = GetFakePtrRecord(service_domain);
    MdnsRecord srv = GetFakeSrvRecord(service_domain);
    MdnsRecord txt = GetFakeTxtRecord(service_domain);
    MdnsRecord a = GetFakeARecord(service_domain);
    MdnsRecord aaaa = GetFakeAAAARecord(service_domain);

    auto result = querier->OnRecordChanged(ptr, RecordChangedEvent::kCreated);
    ValidateRecordChangeStartsQuery(result, service_domain, 1);

    // NOTE: This verbose iterator handling is used to avoid gcc failures.
    auto it = service_domain.labels().begin();
    it++;
    std::string service_name = *it;
    it++;
    std::string service_protocol = *it;
    std::string service_id = "";
    service_id.append(std::move(service_name))
        .append(".")
        .append(std::move(service_protocol));
    ASSERT_TRUE(querier->IsQueryRunning(service_id));

    result = querier->OnRecordChanged(srv, RecordChangedEvent::kCreated);
    EXPECT_EQ(result.size(), size_t{0});

    result = querier->OnRecordChanged(a, RecordChangedEvent::kCreated);
    EXPECT_EQ(result.size(), size_t{0});

    result = querier->OnRecordChanged(aaaa, RecordChangedEvent::kCreated);
    EXPECT_EQ(result.size(), size_t{0});

    EXPECT_CALL(*cb, OnEndpointCreated(_)).Times(1);
    result = querier->OnRecordChanged(txt, RecordChangedEvent::kCreated);
    EXPECT_EQ(result.size(), size_t{0});
    testing::Mock::VerifyAndClearExpectations(cb);
  }

  std::string instance = "instance";
  std::string instance2 = "instance2";
  std::string service = "_service._udp";
  std::string service2 = "_service2._udp";
  std::string domain = "local";
  StrictMock<MockCallback> callback;
  std::unique_ptr<QuerierImplTesting> querier;
  DomainName ptr_domain;
  DomainName name;
  DomainName name2;

 private:
  void ValidateRecordChangeResult(
      const std::vector<PendingQueryChange>& changes,
      const DomainName& name,
      size_t expected_size,
      PendingQueryChange::ChangeType change_type) {
    EXPECT_EQ(changes.size(), expected_size);
    auto it = std::find_if(
        changes.begin(), changes.end(),
        [&name, change_type](const PendingQueryChange& change) {
          return change.dns_type == DnsType::kANY &&
                 change.dns_class == DnsClass::kANY &&
                 change.change_type == change_type && change.name == name;
        });
    EXPECT_TRUE(it != changes.end());
  }
};

// Common Use Cases
//
// The below tests validate the common use cases for QuerierImpl, which we
// expect will be hit for reasonable actors on the network. For these tests, the
// real DnsDataGraph object will be used.

TEST_F(DnsSdQuerierImplTest, TestStartStopQueryCallsMdnsQueries) {
  DomainName other_service_id(
      DomainName{instance2, "_service2", "_udp", domain});

  StrictMock<MockCallback> callback2;
  EXPECT_FALSE(querier->IsQueryRunning(service2));

  EXPECT_CALL(querier->service(),
              StartQuery(_, DnsType::kANY, DnsClass::kANY, _))
      .Times(1);
  querier->StartQuery(service2, &callback2);
  EXPECT_TRUE(querier->IsQueryRunning(service2));

  EXPECT_CALL(querier->service(),
              StopQuery(_, DnsType::kANY, DnsClass::kANY, _))
      .Times(1);
  querier->StopQuery(service2, &callback2);
  EXPECT_FALSE(querier->IsQueryRunning(service2));
}

TEST_F(DnsSdQuerierImplTest, TestStartDuplicateQueryFiresCallbacksWhenAble) {
  StrictMock<MockCallback> callback2;
  CreateServiceInstance(name, &callback);

  EXPECT_CALL(callback2, OnEndpointCreated(_)).Times(1);
  querier->StartQuery(service, &callback2);
  testing::Mock::VerifyAndClearExpectations(&callback2);
}

TEST_F(DnsSdQuerierImplTest, TestStopQueryStopsTrackingRecords) {
  CreateServiceInstance(name, &callback);

  DomainName ptr_domain(++name.labels().begin(), name.labels().end());
  EXPECT_CALL(querier->service(),
              StopQuery(ptr_domain, DnsType::kANY, DnsClass::kANY, _))
      .Times(1);
  EXPECT_CALL(querier->service(),
              StopQuery(name, DnsType::kANY, DnsClass::kANY, _))
      .Times(1);
  querier->StopQuery(service, &callback);
  EXPECT_FALSE(querier->IsDomainTracked(ptr_domain));
  EXPECT_FALSE(querier->IsDomainTracked(name));
  EXPECT_EQ(querier->GetTrackedDomainCount(), size_t{0});
  testing::Mock::VerifyAndClearExpectations(&callback);

  EXPECT_CALL(querier->service(),
              StartQuery(_, DnsType::kANY, DnsClass::kANY, _))
      .Times(1);
  querier->StartQuery(service, &callback);
  EXPECT_TRUE(querier->IsQueryRunning(service));
}

TEST_F(DnsSdQuerierImplTest, TestStopNonexistantQueryHasNoEffect) {
  StrictMock<MockCallback> callback2;
  querier->StopQuery(service, &callback2);
}

TEST_F(DnsSdQuerierImplTest, TestAFollowingAAAAFiresSecondCallback) {
  MdnsRecord ptr = GetFakePtrRecord(name);
  MdnsRecord srv = GetFakeSrvRecord(name);
  MdnsRecord txt = GetFakeTxtRecord(name);
  MdnsRecord a = GetFakeARecord(name);
  MdnsRecord aaaa = GetFakeAAAARecord(name);

  std::vector<DnsSdInstanceEndpoint> endpoints;
  auto changes = querier->OnRecordChanged(ptr, RecordChangedEvent::kCreated);
  ValidateRecordChangeStartsQuery(changes, name, 1);

  changes = querier->OnRecordChanged(srv, RecordChangedEvent::kCreated);
  EXPECT_EQ(changes.size(), size_t{0});
  changes = querier->OnRecordChanged(txt, RecordChangedEvent::kCreated);
  EXPECT_EQ(changes.size(), size_t{0});

  EXPECT_CALL(callback, OnEndpointCreated(_))
      .WillOnce([&endpoints](const DnsSdInstanceEndpoint& ep) mutable {
        endpoints.push_back(ep);
      });
  changes = querier->OnRecordChanged(aaaa, RecordChangedEvent::kCreated);
  EXPECT_EQ(changes.size(), size_t{0});
  testing::Mock::VerifyAndClearExpectations(&callback);

  EXPECT_CALL(callback, OnEndpointUpdated(_))
      .WillOnce([&endpoints](const DnsSdInstanceEndpoint& ep) mutable {
        endpoints.push_back(ep);
      });
  changes = querier->OnRecordChanged(a, RecordChangedEvent::kCreated);
  EXPECT_EQ(changes.size(), size_t{0});
  testing::Mock::VerifyAndClearExpectations(&callback);

  ASSERT_EQ(endpoints.size(), size_t{2});
  DnsSdInstanceEndpoint& created = endpoints[0];
  DnsSdInstanceEndpoint& updated = endpoints[1];
  EXPECT_EQ(static_cast<DnsSdInstance>(created),
            static_cast<DnsSdInstance>(updated));

  ASSERT_EQ(created.addresses().size(), size_t{1});
  EXPECT_TRUE(created.addresses()[0].IsV6());

  ASSERT_EQ(updated.addresses().size(), size_t{2});
  EXPECT_TRUE(created.addresses()[0] == updated.addresses()[0] ||
              created.addresses()[0] == updated.addresses()[1]);
  EXPECT_TRUE(updated.addresses()[0].IsV4() || updated.addresses()[1].IsV4());
}

TEST_F(DnsSdQuerierImplTest, TestGenerateTwoRecordsCallsCallbackTwice) {
  DomainName third{"android", "local"};
  MdnsRecord ptr1 = GetFakePtrRecord(name);
  MdnsRecord srv1 = GetFakeSrvRecord(name, third);
  MdnsRecord txt1 = GetFakeTxtRecord(name);
  MdnsRecord ptr2 = GetFakePtrRecord(name2);
  MdnsRecord srv2 = GetFakeSrvRecord(name2, third);
  MdnsRecord txt2 = GetFakeTxtRecord(name2);
  MdnsRecord a = GetFakeARecord(third);

  auto changes = querier->OnRecordChanged(ptr1, RecordChangedEvent::kCreated);
  ValidateRecordChangeStartsQuery(changes, name, 1);

  changes = querier->OnRecordChanged(srv1, RecordChangedEvent::kCreated);
  ValidateRecordChangeStartsQuery(changes, third, 1);

  changes = querier->OnRecordChanged(txt1, RecordChangedEvent::kCreated);
  EXPECT_EQ(changes.size(), size_t{0});

  changes = querier->OnRecordChanged(ptr2, RecordChangedEvent::kCreated);
  ValidateRecordChangeStartsQuery(changes, name2, 1);

  changes = querier->OnRecordChanged(srv2, RecordChangedEvent::kCreated);
  EXPECT_EQ(changes.size(), size_t{0});

  changes = querier->OnRecordChanged(txt2, RecordChangedEvent::kCreated);
  EXPECT_EQ(changes.size(), size_t{0});

  EXPECT_CALL(callback, OnEndpointCreated(_)).Times(2);
  changes = querier->OnRecordChanged(a, RecordChangedEvent::kCreated);
  EXPECT_EQ(changes.size(), size_t{0});
  testing::Mock::VerifyAndClearExpectations(&callback);

  EXPECT_CALL(callback, OnEndpointDeleted(_)).Times(2);
  changes = querier->OnRecordChanged(a, RecordChangedEvent::kExpired);
  EXPECT_EQ(changes.size(), size_t{0});
}

TEST_F(DnsSdQuerierImplTest, TestCreateDeletePtrRecordResults) {
  const auto ptr = GetFakePtrRecord(name);

  auto result = querier->OnRecordChanged(ptr, RecordChangedEvent::kCreated);
  ValidateRecordChangeStartsQuery(result, name, 1);

  result = querier->OnRecordChanged(ptr, RecordChangedEvent::kExpired);
  ValidateRecordChangeStopsQuery(result, name, 1);
}

TEST_F(DnsSdQuerierImplTest, CallbackCalledWhenPtrDeleted) {
  MdnsRecord ptr = GetFakePtrRecord(name);
  MdnsRecord srv = GetFakeSrvRecord(name, name2);
  MdnsRecord txt = GetFakeTxtRecord(name);
  MdnsRecord a = GetFakeARecord(name2);

  auto changes = querier->OnRecordChanged(ptr, RecordChangedEvent::kCreated);
  ValidateRecordChangeStartsQuery(changes, name, 1);

  changes = querier->OnRecordChanged(srv, RecordChangedEvent::kCreated);
  ValidateRecordChangeStartsQuery(changes, name2, 1);

  changes = querier->OnRecordChanged(txt, RecordChangedEvent::kCreated);
  EXPECT_EQ(changes.size(), size_t{0});

  EXPECT_CALL(callback, OnEndpointCreated(_));
  changes = querier->OnRecordChanged(a, RecordChangedEvent::kCreated);
  EXPECT_EQ(changes.size(), size_t{0});

  EXPECT_CALL(callback, OnEndpointDeleted(_));
  changes = querier->OnRecordChanged(ptr, RecordChangedEvent::kExpired);
  ValidateRecordChangeStopsQuery(changes, name, 2);
  ValidateRecordChangeStopsQuery(changes, name2, 2);
}

TEST_F(DnsSdQuerierImplTest, HardRefresh) {
  MdnsRecord ptr = GetFakePtrRecord(name);
  MdnsRecord srv = GetFakeSrvRecord(name, name2);
  MdnsRecord txt = GetFakeTxtRecord(name);
  MdnsRecord a = GetFakeARecord(name2);

  querier->OnRecordChanged(ptr, RecordChangedEvent::kCreated);
  querier->OnRecordChanged(srv, RecordChangedEvent::kCreated);
  querier->OnRecordChanged(txt, RecordChangedEvent::kCreated);

  EXPECT_CALL(callback, OnEndpointCreated(_));
  querier->OnRecordChanged(a, RecordChangedEvent::kCreated);
  testing::Mock::VerifyAndClearExpectations(&callback);

  EXPECT_CALL(querier->service(),
              StopQuery(ptr_domain, DnsType::kANY, DnsClass::kANY, _))
      .Times(1);
  EXPECT_CALL(querier->service(),
              StopQuery(name, DnsType::kANY, DnsClass::kANY, _))
      .Times(1);
  EXPECT_CALL(querier->service(),
              StopQuery(name2, DnsType::kANY, DnsClass::kANY, _))
      .Times(1);
  EXPECT_CALL(querier->service(), ReinitializeQueries(_)).Times(1);
  EXPECT_CALL(querier->service(),
              StartQuery(ptr_domain, DnsType::kANY, DnsClass::kANY, _))
      .Times(1);
  querier->ReinitializeQueries(service);
  testing::Mock::VerifyAndClearExpectations(querier.get());
}

// Edge Cases
//
// The below tests validate against edge cases that either either difficult to
// achieve, are not expected to be possible under normal circumstances but
// should be validated against for safety, or should only occur when either a
// bad actor or a misbehaving publisher is present on the network. To simplify
// these tests, the DnsDataGraph object will be mocked.
TEST_F(DnsSdQuerierImplTest, ErrorsOnlyAfterChangesAreLogged) {
  MockDnsDataGraph& mock_graph = querier->GetMockedGraph();
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> before_changes{};
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> after_changes{};
  after_changes.emplace_back(Error::Code::kItemNotFound);
  after_changes.emplace_back(Error::Code::kItemNotFound);
  after_changes.emplace_back(Error::Code::kItemAlreadyExists);

  // Calls before and after applying record changes, then the error it logs.
  EXPECT_CALL(mock_graph, CreateEndpoints(_, _))
      .WillOnce(Return(ByMove(std::move(before_changes))))
      .WillOnce(Return(ByMove(std::move(after_changes))));
  EXPECT_CALL(querier->reporting_client(), OnRecoverableError(_)).Times(3);

  // Call to apply record changes. The specifics are unimportant.
  EXPECT_CALL(mock_graph, ApplyDataRecordChange(_, _, _, _))
      .WillOnce(Return(Error::None()));

  // Call with any record. The mocks make the specifics unimportant.
  querier->OnRecordChanged(GetFakePtrRecord(name),
                           RecordChangedEvent::kCreated);
}

TEST_F(DnsSdQuerierImplTest, ErrorsOnlyBeforeChangesNotLogged) {
  MockDnsDataGraph& mock_graph = querier->GetMockedGraph();
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> before_changes{};
  before_changes.emplace_back(Error::Code::kItemNotFound);
  before_changes.emplace_back(Error::Code::kItemNotFound);
  before_changes.emplace_back(Error::Code::kItemAlreadyExists);
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> after_changes{};

  // Calls before and after applying record changes.
  EXPECT_CALL(mock_graph, CreateEndpoints(_, _))
      .WillOnce(Return(ByMove(std::move(before_changes))))
      .WillOnce(Return(ByMove(std::move(after_changes))));

  // Call to apply record changes. The specifics are unimportant.
  EXPECT_CALL(mock_graph, ApplyDataRecordChange(_, _, _, _))
      .WillOnce(Return(Error::None()));

  // Call with any record. The mocks make the specifics unimportant.
  querier->OnRecordChanged(GetFakePtrRecord(name),
                           RecordChangedEvent::kCreated);
}

TEST_F(DnsSdQuerierImplTest, ErrorsBeforeAndAfterChangesNotLogged) {
  MockDnsDataGraph& mock_graph = querier->GetMockedGraph();
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> before_changes{};
  before_changes.emplace_back(Error::Code::kItemNotFound);
  before_changes.emplace_back(Error::Code::kItemNotFound);
  before_changes.emplace_back(Error::Code::kItemAlreadyExists);
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> after_changes{};
  after_changes.emplace_back(Error::Code::kItemNotFound);
  after_changes.emplace_back(Error::Code::kItemAlreadyExists);
  after_changes.emplace_back(Error::Code::kItemNotFound);

  // Calls before and after applying record changes.
  EXPECT_CALL(mock_graph, CreateEndpoints(_, _))
      .WillOnce(Return(ByMove(std::move(before_changes))))
      .WillOnce(Return(ByMove(std::move(after_changes))));

  // Call to apply record changes. The specifics are unimportant.
  EXPECT_CALL(mock_graph, ApplyDataRecordChange(_, _, _, _))
      .WillOnce(Return(Error::None()));

  // Call with any record. The mocks make the specifics unimportant.
  querier->OnRecordChanged(GetFakePtrRecord(name),
                           RecordChangedEvent::kCreated);
}

TEST_F(DnsSdQuerierImplTest, OrderOfErrorsDoesNotAffectResults) {
  MockDnsDataGraph& mock_graph = querier->GetMockedGraph();
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> before_changes{};
  before_changes.emplace_back(Error::Code::kIndexOutOfBounds);
  before_changes.emplace_back(Error::Code::kItemAlreadyExists);
  before_changes.emplace_back(Error::Code::kOperationCancelled);
  before_changes.emplace_back(Error::Code::kItemNotFound);
  before_changes.emplace_back(Error::Code::kOperationInProgress);
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> after_changes{};
  after_changes.emplace_back(Error::Code::kOperationInProgress);
  after_changes.emplace_back(Error::Code::kUnknownError);
  after_changes.emplace_back(Error::Code::kItemNotFound);
  after_changes.emplace_back(Error::Code::kItemAlreadyExists);
  after_changes.emplace_back(Error::Code::kOperationCancelled);

  // Calls before and after applying record changes, then the error it logs.
  EXPECT_CALL(mock_graph, CreateEndpoints(_, _))
      .WillOnce(Return(ByMove(std::move(before_changes))))
      .WillOnce(Return(ByMove(std::move(after_changes))));
  EXPECT_CALL(querier->reporting_client(), OnRecoverableError(_)).Times(1);

  // Call to apply record changes. The specifics are unimportant.
  EXPECT_CALL(mock_graph, ApplyDataRecordChange(_, _, _, _))
      .WillOnce(Return(Error::None()));

  // Call with any record. The mocks make the specifics unimportant.
  querier->OnRecordChanged(GetFakePtrRecord(name),
                           RecordChangedEvent::kCreated);
}

TEST_F(DnsSdQuerierImplTest, ResultsWithMultipleAddressRecordsHandled) {
  IPEndpoint endpointa{{192, 168, 86, 23}, 80};
  IPEndpoint endpointb{{1, 2, 3, 4, 5, 6, 7, 8}, 80};
  IPEndpoint endpointc{{192, 168, 0, 1}, 80};
  IPEndpoint endpointd{{192, 168, 0, 2}, 80};
  IPEndpoint endpointe{{192, 168, 0, 3}, 80};

  DnsSdInstanceEndpoint instance1("instance1", "_service._udp", "local", {},
                                  kNetworkInterface, {endpointa, endpointb});
  DnsSdInstanceEndpoint instance2("instance2", "_service2._udp", "local", {},
                                  kNetworkInterface, {endpointa, endpointb});
  DnsSdInstanceEndpoint instance3("instance3", "_service._udp", "local", {},
                                  kNetworkInterface, {endpointc});
  DnsSdInstanceEndpoint instance4("instance1", "_service3._udp", "local", {},
                                  kNetworkInterface, {endpointd, endpointe});
  DnsSdInstanceEndpoint instance5("instance1", "_service3._udp", "local", {},
                                  kNetworkInterface, {endpointe});

  MockDnsDataGraph& mock_graph = querier->GetMockedGraph();
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> before_changes{};
  before_changes.emplace_back(instance4);
  before_changes.emplace_back(instance2);
  before_changes.emplace_back(instance3);
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> after_changes{};
  after_changes.emplace_back(instance5);
  after_changes.emplace_back(instance3);
  after_changes.emplace_back(instance1);

  // Calls before and after applying record changes, then the error it logs.
  EXPECT_CALL(mock_graph, CreateEndpoints(_, _))
      .WillOnce(Return(ByMove(std::move(before_changes))))
      .WillOnce(Return(ByMove(std::move(after_changes))));
  EXPECT_CALL(callback, OnEndpointCreated(instance1));
  EXPECT_CALL(callback, OnEndpointUpdated(instance5));
  EXPECT_CALL(callback, OnEndpointDeleted(instance2));

  // Call to apply record changes. The specifics are unimportant.
  EXPECT_CALL(mock_graph, ApplyDataRecordChange(_, _, _, _))
      .WillOnce(Return(Error::None()));

  // Call with any record. The mocks make the specifics unimportant.
  querier->OnRecordChanged(GetFakePtrRecord(name),
                           RecordChangedEvent::kCreated);
}

TEST_F(DnsSdQuerierImplTest, MixOfErrorsAndSuccessesHandledCorrectly) {
  DnsSdInstanceEndpoint instance1("instance1", "_service._udp", "local", {},
                                  kNetworkInterface, {{{192, 168, 2, 24}, 80}});
  DnsSdInstanceEndpoint instance2("instance2", "_service2._udp", "local", {},
                                  kNetworkInterface, {{{192, 168, 17, 2}, 80}});
  DnsSdInstanceEndpoint instance3("instance3", "_service._udp", "local", {},
                                  kNetworkInterface, {{{127, 0, 0, 1}, 80}});
  DnsSdInstanceEndpoint instance4("instance1", "_service3._udp", "local", {},
                                  kNetworkInterface, {{{127, 0, 0, 1}, 80}});
  DnsSdInstanceEndpoint instance5("instance1", "_service3._udp", "local", {},
                                  kNetworkInterface,
                                  {{{127, 0, 0, 1}, 80}, {{127, 0, 0, 2}, 80}});

  MockDnsDataGraph& mock_graph = querier->GetMockedGraph();
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> before_changes{};
  before_changes.emplace_back(Error::Code::kIndexOutOfBounds);
  before_changes.emplace_back(instance2);
  before_changes.emplace_back(Error::Code::kItemAlreadyExists);
  before_changes.emplace_back(Error::Code::kOperationCancelled);
  before_changes.emplace_back(instance1);
  before_changes.emplace_back(Error::Code::kItemNotFound);
  before_changes.emplace_back(Error::Code::kOperationInProgress);
  before_changes.emplace_back(instance4);
  std::vector<ErrorOr<DnsSdInstanceEndpoint>> after_changes{};
  after_changes.emplace_back(instance1);
  after_changes.emplace_back(Error::Code::kOperationInProgress);
  after_changes.emplace_back(Error::Code::kUnknownError);
  after_changes.emplace_back(Error::Code::kItemNotFound);
  after_changes.emplace_back(Error::Code::kItemAlreadyExists);
  after_changes.emplace_back(instance3);
  after_changes.emplace_back(instance5);
  after_changes.emplace_back(Error::Code::kOperationCancelled);

  // Calls before and after applying record changes, then the error it logs.
  EXPECT_CALL(mock_graph, CreateEndpoints(_, _))
      .WillOnce(Return(ByMove(std::move(before_changes))))
      .WillOnce(Return(ByMove(std::move(after_changes))));
  EXPECT_CALL(querier->reporting_client(), OnRecoverableError(_)).Times(1);
  EXPECT_CALL(callback, OnEndpointCreated(instance3));
  EXPECT_CALL(callback, OnEndpointUpdated(instance5));
  EXPECT_CALL(callback, OnEndpointDeleted(instance2));

  // Call to apply record changes. The specifics are unimportant.
  EXPECT_CALL(mock_graph, ApplyDataRecordChange(_, _, _, _))
      .WillOnce(Return(Error::None()));

  // Call with any record. The mocks make the specifics unimportant.
  querier->OnRecordChanged(GetFakePtrRecord(name),
                           RecordChangedEvent::kCreated);
}

}  // namespace discovery
}  // namespace openscreen
