blob: 626ca92a0346be57424a9f23bd8925cc78b6f81a [file] [log] [blame]
// 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