// 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 #include #include #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>(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& service() { return mock_service_; } StrictMock& reporting_client() { return reporting_client_; } // NOTE: This should only be used for testing hard-to-achieve edge cases. StrictMock& GetMockedGraph() { if (!is_graph_mocked_) { graph_ = std::make_unique>(); is_graph_mocked_ = true; } return static_cast&>(*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 mock_service_; StrictMock reporting_client_; bool is_graph_mocked_ = false; }; class DnsSdQuerierImplTest : public testing::Test { public: DnsSdQuerierImplTest() : querier(std::make_unique()), 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& changes, const DomainName& name, size_t expected_size) { ValidateRecordChangeResult(changes, name, expected_size, PendingQueryChange::kStartQuery); } void ValidateRecordChangeStopsQuery( const std::vector& 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 callback; std::unique_ptr querier; DomainName ptr_domain; DomainName name; DomainName name2; private: void ValidateRecordChangeResult( const std::vector& 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 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 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 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 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(created), static_cast(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> before_changes{}; std::vector> 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> 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> 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> 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> 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> 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> 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> before_changes{}; before_changes.emplace_back(instance4); before_changes.emplace_back(instance2); before_changes.emplace_back(instance3); std::vector> 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> 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> 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