blob: 4bc0f27f1abea2be8ef63733decd666b7190c9ca [file] [log] [blame]
// Copyright 2014 The Chromium OS 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 "peerd/avahi_service_publisher.h"
#include <memory>
#include <vector>
#include <base/memory/ref_counted.h>
#include <dbus/mock_bus.h>
#include <dbus/mock_exported_object.h>
#include <dbus/mock_object_proxy.h>
#include <dbus/object_path.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "peerd/dbus_constants.h"
#include "peerd/service.h"
#include "peerd/test_util.h"
using dbus::Bus;
using dbus::MockBus;
using dbus::MockExportedObject;
using dbus::ObjectPath;
using dbus::Response;
using peerd::Service;
using peerd::dbus_constants::avahi::kGroupInterface;
using peerd::dbus_constants::avahi::kGroupMethodAddService;
using peerd::dbus_constants::avahi::kGroupMethodCommit;
using peerd::dbus_constants::avahi::kGroupMethodFree;
using peerd::dbus_constants::avahi::kGroupMethodReset;
using peerd::dbus_constants::avahi::kServerInterface;
using peerd::dbus_constants::avahi::kServerMethodEntryGroupNew;
using peerd::dbus_constants::avahi::kServerPath;
using peerd::dbus_constants::avahi::kServiceName;
using peerd::test_util::IsDBusMethodCallTo;
using peerd::test_util::MakeMockCompletionAction;
using peerd::test_util::MakeMockDBusObject;
using peerd::test_util::ReturnsEmptyResponse;
using std::unique_ptr;
using testing::AnyNumber;
using testing::Invoke;
using testing::Property;
using testing::Return;
using testing::Unused;
using testing::_;
namespace {
const char kHost[] = "this_is_a_hostname";
const char kGroupPath[] = "/this/is/a/group/path";
const char kServiceId[] = "service-id";
const char kServicePath[] = "/a/peerd/service/path";
Response* ReturnsGroupPath(dbus::MethodCall* method_call, Unused, Unused) {
method_call->SetSerial(87);
scoped_ptr<Response> response = Response::FromMethodCall(method_call);
dbus::MessageWriter writer(response.get());
chromeos::dbus_utils::AppendValueToWriter(&writer, ObjectPath(kGroupPath));
// The mock wraps this back in a scoped_ptr in the function calling us.
return response.release();
}
} // namespace
namespace peerd {
class AvahiServicePublisherTest : public ::testing::Test {
public:
void SetUp() override {
avahi_proxy_ = new dbus::MockObjectProxy(
mock_bus_.get(), kServiceName, ObjectPath(kServerPath));
group_proxy_ = new dbus::MockObjectProxy(
mock_bus_.get(), kServiceName, ObjectPath(kGroupPath));
publisher_.reset(new AvahiServicePublisher(
kHost, "uuid", mock_bus_, avahi_proxy_.get(),
base::Bind(&AvahiServicePublisherTest::OnGroupFailure,
base::Unretained(this))));
// Ignore threading concerns.
EXPECT_CALL(*mock_bus_, AssertOnOriginThread()).Times(AnyNumber());
EXPECT_CALL(*mock_bus_, AssertOnDBusThread()).Times(AnyNumber());
// We might try to create new EntryGroups as part of the test.
EXPECT_CALL(*avahi_proxy_, MockCallMethodAndBlockWithErrorDetails(
IsDBusMethodCallTo(kServerInterface,
kServerMethodEntryGroupNew), _, _))
.WillRepeatedly(Invoke(&ReturnsGroupPath));
EXPECT_CALL(*mock_bus_,
GetObjectProxy(kServiceName,
Property(&ObjectPath::value, kGroupPath)))
.WillRepeatedly(Return(group_proxy_.get()));
// When we create EntryGroups, we destroy them eventually.
EXPECT_CALL(*group_proxy_, Detach()).Times(AnyNumber());
// We'll need peerd::Service objects as part of this test, they need
// MockExportedObjects.
EXPECT_CALL(*mock_bus_, GetExportedObject(_))
.WillRepeatedly(Return(service_object_.get()));
EXPECT_CALL(*service_object_, ExportMethod(_, _, _, _))
.WillRepeatedly(Invoke(&test_util::HandleMethodExport));
EXPECT_CALL(*service_object_, Unregister()).Times(AnyNumber());
}
unique_ptr<Service> CreateServiceWithInfo(const std::string& service_id,
const Service::ServiceInfo& info) {
unique_ptr<Service> new_service{new Service{mock_bus_, nullptr,
ObjectPath{kServicePath}}};
chromeos::ErrorPtr error;
EXPECT_TRUE(new_service->RegisterAsync(
&error, service_id, {}, info, {},
MakeMockCompletionAction()));
EXPECT_EQ(nullptr, error.get());
return new_service;
}
unique_ptr<Service> CreateService(const std::string& service_id) {
return CreateServiceWithInfo(service_id, {});
}
void ExpectServiceAdded() {
// Each new service triggers two AddService calls and two Commit calls.
// The first of each is for the service itself. The second set is for
// the master Serbus record.
EXPECT_CALL(*group_proxy_, MockCallMethodAndBlockWithErrorDetails(
IsDBusMethodCallTo(kGroupInterface, kGroupMethodAddService), _, _))
.Times(2)
.WillRepeatedly(Invoke(&ReturnsEmptyResponse));
EXPECT_CALL(*group_proxy_, MockCallMethodAndBlockWithErrorDetails(
IsDBusMethodCallTo(kGroupInterface, kGroupMethodCommit), _, _))
.Times(2)
.WillRepeatedly(Invoke(&ReturnsEmptyResponse));
}
void ExpectServiceModified() {
// When modifying existing services, we reset both the service in
// question and the master record.
EXPECT_CALL(*group_proxy_, MockCallMethodAndBlockWithErrorDetails(
IsDBusMethodCallTo(kGroupInterface, kGroupMethodReset), _, _))
.Times(2)
.WillRepeatedly(Invoke(&ReturnsEmptyResponse));
ExpectServiceAdded();
}
void ExpectFinalServiceFree() {
// The final service free also removes the master record.
EXPECT_CALL(*group_proxy_, MockCallMethodAndBlockWithErrorDetails(
IsDBusMethodCallTo(kGroupInterface, kGroupMethodFree), _, _))
.Times(2)
.WillRepeatedly(Invoke(&ReturnsEmptyResponse));
}
MOCK_METHOD0(OnGroupFailure, void(void));
scoped_refptr<MockBus> mock_bus_{new MockBus{Bus::Options{}}};
scoped_refptr<dbus::MockObjectProxy> avahi_proxy_;
scoped_refptr<dbus::MockObjectProxy> group_proxy_;
scoped_refptr<dbus::MockExportedObject> service_object_{
new MockExportedObject{mock_bus_.get(), ObjectPath{kServicePath}}};
unique_ptr<AvahiServicePublisher> publisher_;
};
TEST_F(AvahiServicePublisherTest, ShouldFreeAddedGroups) {
auto service = CreateService(kServiceId);
ExpectServiceAdded();
EXPECT_TRUE(publisher_->OnServiceUpdated(nullptr, *service));
ExpectFinalServiceFree();
publisher_.reset();
}
TEST_F(AvahiServicePublisherTest, CanEncodeTxtRecords) {
auto service = CreateServiceWithInfo(
kServiceId, {{"a", "w"}, {"b", "foo"}});
std::vector<std::vector<uint8_t>> result{{'a', '=', 'w'},
{'b', '=', 'f', 'o', 'o'}};
EXPECT_EQ(result,
AvahiServicePublisher::GetTxtRecord(service->GetServiceInfo()));
}
TEST_F(AvahiServicePublisherTest, CanHandleServiceUpdates) {
auto service = CreateService(kServiceId);
ExpectServiceAdded();
EXPECT_TRUE(publisher_->OnServiceUpdated(nullptr, *service));
ExpectServiceModified();
// The service publisher doesn't actually know anything about the previous
// values inside the service. So we don't need to mutate the service.
EXPECT_TRUE(publisher_->OnServiceUpdated(nullptr, *service));
ExpectFinalServiceFree();
}
TEST_F(AvahiServicePublisherTest, CanRemoveService) {
auto service = CreateService(kServiceId);
ExpectServiceAdded();
EXPECT_TRUE(publisher_->OnServiceUpdated(nullptr, *service));
ExpectFinalServiceFree();
EXPECT_TRUE(publisher_->OnServiceRemoved(nullptr, kServiceId));
publisher_.reset(); // Note that we should *not* see a Group.Free() call.
}
} // namespace peerd