From 9a42b6bbf3668746261cc6d7c8aa24ceef060095 Mon Sep 17 00:00:00 2001 From: Mateusz Gozdek Date: Mon, 10 Jan 2022 10:48:13 +0100 Subject: [PATCH] login1: Add NewWithConnection method This method allows passing existing D-Bus connection, which allows to re-use connection between clients and to mock D-Bus connection for testing purposes. Signed-off-by: Mateusz Gozdek --- login1/dbus.go | 56 +++++++++++++++++++++++----- login1/dbus_test.go | 90 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 9 deletions(-) diff --git a/login1/dbus.go b/login1/dbus.go index ca71308c..4cc6bb95 100644 --- a/login1/dbus.go +++ b/login1/dbus.go @@ -32,8 +32,33 @@ const ( // Conn is a connection to systemds dbus endpoint. type Conn struct { - conn *dbus.Conn - object dbus.BusObject + conn Connection + connManager connectionManager + object Caller +} + +// Objector describes functionality required from a given D-Bus connection. +type Connection interface { + Object(string, dbus.ObjectPath) dbus.BusObject + Signal(ch chan<- *dbus.Signal) + // TODO: This should be replaced with AddMatchSignal. + // See https://github.com/coreos/go-systemd/issues/388 for details. + BusObject() dbus.BusObject +} + +// ConnectionManager explicitly wraps dependencies on established D-Bus connection. +type connectionManager interface { + Hello() error + Auth(authMethods []dbus.Auth) error + Close() error + + Connection +} + +// Caller describes required functionality from D-Bus object. +type Caller interface { + // TODO: This method should eventually be removed, as it provides no context support. + Call(method string, flags dbus.Flags, args ...interface{}) *dbus.Call } // New establishes a connection to the system bus and authenticates. @@ -47,20 +72,32 @@ func New() (*Conn, error) { return c, nil } +// NewWithConnection creates new login1 client using given D-Bus connection. +func NewWithConnection(connection Connection) (*Conn, error) { + if connection == nil { + return nil, fmt.Errorf("no connection given") + } + + return &Conn{ + conn: connection, + object: connection.Object(dbusDest, dbusPath), + }, nil +} + // Close closes the dbus connection func (c *Conn) Close() { if c == nil { return } - if c.conn != nil { - c.conn.Close() + if c.conn != nil && c.connManager != nil { + c.connManager.Close() } } func (c *Conn) initConnection() error { var err error - c.conn, err = dbus.SystemBusPrivate() + c.connManager, err = dbus.SystemBusPrivate() if err != nil { return err } @@ -70,18 +107,19 @@ func (c *Conn) initConnection() error { // libc) methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} - err = c.conn.Auth(methods) + err = c.connManager.Auth(methods) if err != nil { - c.conn.Close() + c.connManager.Close() return err } - err = c.conn.Hello() + err = c.connManager.Hello() if err != nil { - c.conn.Close() + c.connManager.Close() return err } + c.conn = c.connManager c.object = c.conn.Object("org.freedesktop.login1", dbus.ObjectPath(dbusPath)) return nil diff --git a/login1/dbus_test.go b/login1/dbus_test.go index 21522c9c..8816fd8b 100644 --- a/login1/dbus_test.go +++ b/login1/dbus_test.go @@ -20,6 +20,8 @@ import ( "regexp" "testing" + "github.com/godbus/dbus/v5" + "github.com/coreos/go-systemd/v22/login1" ) @@ -88,3 +90,91 @@ func TestListUsers(t *testing.T) { } } } + +func Test_Creating_new_connection_with_custom_connection(t *testing.T) { + t.Parallel() + + t.Run("connects_to_global_login1_path_and_interface", func(t *testing.T) { + t.Parallel() + + objectConstructorCalled := false + + connectionWithContextCheck := &mockConnection{ + ObjectF: func(dest string, path dbus.ObjectPath) dbus.BusObject { + objectConstructorCalled = true + + expectedDest := "org.freedesktop.login1" + + if dest != expectedDest { + t.Fatalf("Expected D-Bus destination %q, got %q", expectedDest, dest) + } + + expectedPath := dbus.ObjectPath("/org/freedesktop/login1") + + if path != expectedPath { + t.Fatalf("Expected D-Bus path %q, got %q", expectedPath, path) + } + + return nil + }, + } + + if _, err := login1.NewWithConnection(connectionWithContextCheck); err != nil { + t.Fatalf("Unexpected error creating connection: %v", err) + } + + if !objectConstructorCalled { + t.Fatalf("Expected object constructor to be called") + } + }) + + t.Run("returns_error_when_no_custom_connection_is_given", func(t *testing.T) { + t.Parallel() + + testConn, err := login1.NewWithConnection(nil) + if err == nil { + t.Fatalf("Expected error creating connection with no connector") + } + + if testConn != nil { + t.Fatalf("Expected connection to be nil when New returns error") + } + }) +} + +// mockConnection is a test helper for mocking dbus.Conn. +type mockConnection struct { + ObjectF func(string, dbus.ObjectPath) dbus.BusObject +} + +// Auth ... +func (m *mockConnection) Auth(authMethods []dbus.Auth) error { + return nil +} + +// Hello ... +func (m *mockConnection) Hello() error { + return nil +} + +// Signal ... +func (m *mockConnection) Signal(ch chan<- *dbus.Signal) {} + +// Object ... +func (m *mockConnection) Object(dest string, path dbus.ObjectPath) dbus.BusObject { + if m.ObjectF == nil { + return nil + } + + return m.ObjectF(dest, path) +} + +// Close ... +func (m *mockConnection) Close() error { + return nil +} + +// BusObject ... +func (m *mockConnection) BusObject() dbus.BusObject { + return nil +}