]> kaliko git repositories - python-musicpd.git/blobdiff - test.py
Add test for two worded commands (ie. "tagtypes clear")
[python-musicpd.git] / test.py
diff --git a/test.py b/test.py
index 9a566df1ba0953feb31426193343b43a78d94ce4..70ac203ccdfdcd69696bbe9607b9fb2283937a54 100755 (executable)
--- a/test.py
+++ b/test.py
@@ -1,5 +1,6 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
+# pylint: disable=missing-docstring
 """
 Test suite highly borrowed^Wsteal from python-mpd2 [0] project.
 
@@ -8,6 +9,7 @@ Test suite highly borrowed^Wsteal from python-mpd2 [0] project.
 
 
 import itertools
+import os
 import sys
 import types
 import unittest
@@ -31,116 +33,240 @@ warnings.simplefilter('default')
 TEST_MPD_HOST, TEST_MPD_PORT = ('example.com', 10000)
 
 
+class testEnvVar(unittest.TestCase):
+
+    def test_envvar(self):
+        # mock "os.path.exists" here to ensure there are no socket in
+        # XDG_RUNTIME_DIR/mpd or /run/mpd since with test defaults fallbacks
+        # when :
+        #   * neither MPD_HOST nor XDG_RUNTIME_DIR are not set
+        #   * /run/mpd does not expose a socket
+        with mock.patch('os.path.exists', return_value=False):
+            os.environ.pop('MPD_HOST', None)
+            os.environ.pop('MPD_PORT', None)
+            client = musicpd.MPDClient()
+            self.assertEqual(client.host, 'localhost')
+            self.assertEqual(client.port, '6600')
+
+            os.environ.pop('MPD_HOST', None)
+            os.environ['MPD_PORT'] = '6666'
+            client = musicpd.MPDClient()
+            self.assertEqual(client.pwd, None)
+            self.assertEqual(client.host, 'localhost')
+            self.assertEqual(client.port, '6666')
+
+        # Test password extraction
+        os.environ['MPD_HOST'] = 'pa55w04d@example.org'
+        client = musicpd.MPDClient()
+        self.assertEqual(client.pwd, 'pa55w04d')
+        self.assertEqual(client.host, 'example.org')
+
+        # Test host alone
+        os.environ['MPD_HOST'] = 'example.org'
+        client = musicpd.MPDClient()
+        self.assertFalse(client.pwd)
+        self.assertEqual(client.host, 'example.org')
+
+        # Test password extraction (no host)
+        os.environ['MPD_HOST'] = 'pa55w04d@'
+        with mock.patch('os.path.exists', return_value=False):
+            client = musicpd.MPDClient()
+        self.assertEqual(client.pwd, 'pa55w04d')
+        self.assertEqual(client.host, 'localhost')
+
+        # Test badly formatted MPD_HOST
+        os.environ['MPD_HOST'] = '@'
+        with mock.patch('os.path.exists', return_value=False):
+            client = musicpd.MPDClient()
+        self.assertEqual(client.pwd, None)
+        self.assertEqual(client.host, 'localhost')
+
+        # Test unix socket extraction
+        os.environ['MPD_HOST'] = 'pa55w04d@/unix/sock'
+        client = musicpd.MPDClient()
+        self.assertEqual(client.host, '/unix/sock')
+
+        # Test plain abstract socket extraction
+        os.environ['MPD_HOST'] = '@abstract'
+        client = musicpd.MPDClient()
+        self.assertEqual(client.host, '@abstract')
+
+        # Test password and abstract socket extraction
+        os.environ['MPD_HOST'] = 'pass@@abstract'
+        client = musicpd.MPDClient()
+        self.assertEqual(client.pwd, 'pass')
+        self.assertEqual(client.host, '@abstract')
+
+        # Test unix socket fallback
+        os.environ.pop('MPD_HOST', None)
+        os.environ.pop('MPD_PORT', None)
+        os.environ.pop('XDG_RUNTIME_DIR', None)
+        with mock.patch('os.path.exists', return_value=True):
+            client = musicpd.MPDClient()
+            self.assertEqual(client.host, '/run/mpd/socket')
+            os.environ['XDG_RUNTIME_DIR'] = '/run/user/1000'
+            client = musicpd.MPDClient()
+            self.assertEqual(client.host, '/run/user/1000/mpd/socket')
+
+        os.environ.pop('MPD_HOST', None)
+        os.environ.pop('MPD_PORT', None)
+        os.environ['XDG_RUNTIME_DIR'] = '/run/user/1000/'
+        with mock.patch('os.path.exists', return_value=True):
+            client = musicpd.MPDClient()
+            self.assertEqual(client.host, '/run/user/1000/mpd/socket')
+
+        # Test MPD_TIMEOUT
+        os.environ.pop('MPD_TIMEOUT', None)
+        client = musicpd.MPDClient()
+        self.assertEqual(client.mpd_timeout, musicpd.CONNECTION_TIMEOUT)
+        os.environ['MPD_TIMEOUT'] = 'garbage'
+        client = musicpd.MPDClient()
+        self.assertEqual(client.mpd_timeout,
+                         musicpd.CONNECTION_TIMEOUT,
+                         'Garbage\'s not silently ignore to use default value')
+        os.environ['MPD_TIMEOUT'] = '42'
+        client = musicpd.MPDClient()
+        self.assertEqual(client.mpd_timeout, 42)
+
+
 class TestMPDClient(unittest.TestCase):
 
     longMessage = True
-    # last sync: musicpd 0.4.0
+    # last sync: musicpd 0.6.0 unreleased / Fri Feb 19 15:34:53 CET 2021
     commands = {
             # Status Commands
-            "clearerror":         "nothing",
-            "currentsong":        "object",
-            "idle":               "list",
-            "noidle":             None,
-            "status":             "object",
-            "stats":              "object",
+            'clearerror':         'nothing',
+            'currentsong':        'object',
+            'idle':               'list',
+            'noidle':             None,
+            'status':             'object',
+            'stats':              'object',
             # Playback Option Commands
-            "consume":            "nothing",
-            "crossfade":          "nothing",
-            "mixrampdb":          "nothing",
-            "mixrampdelay":       "nothing",
-            "random":             "nothing",
-            "repeat":             "nothing",
-            "setvol":             "nothing",
-            "single":             "nothing",
-            "replay_gain_mode":   "nothing",
-            "replay_gain_status": "item",
-            "volume":             "nothing",
+            'consume':            'nothing',
+            'crossfade':          'nothing',
+            'mixrampdb':          'nothing',
+            'mixrampdelay':       'nothing',
+            'random':             'nothing',
+            'repeat':             'nothing',
+            'setvol':             'nothing',
+            'getvol':             'object',
+            'single':             'nothing',
+            'replay_gain_mode':   'nothing',
+            'replay_gain_status': 'item',
+            'volume':             'nothing',
             # Playback Control Commands
-            "next":               "nothing",
-            "pause":              "nothing",
-            "play":               "nothing",
-            "playid":             "nothing",
-            "previous":           "nothing",
-            "seek":               "nothing",
-            "seekid":             "nothing",
-            "seekcur":            "nothing",
-            "stop":               "nothing",
-            # Playlist Commands
-            "add":                "nothing",
-            "addid":              "item",
-            "clear":              "nothing",
-            "delete":             "nothing",
-            "deleteid":           "nothing",
-            "move":               "nothing",
-            "moveid":             "nothing",
-            "playlist":           "playlist",
-            "playlistfind":       "songs",
-            "playlistid":         "songs",
-            "playlistinfo":       "songs",
-            "playlistsearch":     "songs",
-            "plchanges":          "songs",
-            "plchangesposid":     "changes",
-            "shuffle":            "nothing",
-            "swap":               "nothing",
-            "swapid":             "nothing",
+            'next':               'nothing',
+            'pause':              'nothing',
+            'play':               'nothing',
+            'playid':             'nothing',
+            'previous':           'nothing',
+            'seek':               'nothing',
+            'seekid':             'nothing',
+            'seekcur':            'nothing',
+            'stop':               'nothing',
+            # Queue Commands
+            'add':                'nothing',
+            'addid':              'item',
+            'clear':              'nothing',
+            'delete':             'nothing',
+            'deleteid':           'nothing',
+            'move':               'nothing',
+            'moveid':             'nothing',
+            'playlist':           'playlist',
+            'playlistfind':       'songs',
+            'playlistid':         'songs',
+            'playlistinfo':       'songs',
+            'playlistsearch':     'songs',
+            'plchanges':          'songs',
+            'plchangesposid':     'changes',
+            'prio':               'nothing',
+            'prioid':             'nothing',
+            'rangeid':            'nothing',
+            'shuffle':            'nothing',
+            'swap':               'nothing',
+            'swapid':             'nothing',
+            'addtagid':           'nothing',
+            'cleartagid':         'nothing',
             # Stored Playlist Commands
-            "listplaylist":       "list",
-            "listplaylistinfo":   "songs",
-            "listplaylists":      "playlists",
-            "load":               "nothing",
-            "playlistadd":        "nothing",
-            "playlistclear":      "nothing",
-            "playlistdelete":     "nothing",
-            "playlistmove":       "nothing",
-            "rename":             "nothing",
-            "rm":                 "nothing",
-            "save":               "nothing",
+            'listplaylist':       'list',
+            'listplaylistinfo':   'songs',
+            'listplaylists':      'playlists',
+            'load':               'nothing',
+            'playlistadd':        'nothing',
+            'playlistclear':      'nothing',
+            'playlistdelete':     'nothing',
+            'playlistmove':       'nothing',
+            'rename':             'nothing',
+            'rm':                 'nothing',
+            'save':               'nothing',
             # Database Commands
-            "count":              "object",
-            "find":               "songs",
-            "findadd":            "nothing",
-            "list":               "list",
-            "listall":            "database",
-            "listallinfo":        "database",
-            "lsinfo":             "database",
-            "search":             "songs",
-            "searchadd":          "nothing",
-            "searchaddpl":        "nothing",
-            "update":             "item",
-            "rescan":             "item",
-            "readcomments":       "object",
+            'albumart':           'composite',
+            'count':              'object',
+            'getfingerprint':     'object',
+            'find':               'songs',
+            'findadd':            'nothing',
+            'list':               'list',
+            'listall':            'database',
+            'listallinfo':        'database',
+            'listfiles':          'database',
+            'lsinfo':             'database',
+            'readcomments':       'object',
+            'readpicture':        'composite',
+            'search':             'songs',
+            'searchadd':          'nothing',
+            'searchaddpl':        'nothing',
+            'update':             'item',
+            'rescan':             'item',
+            # Mounts and neighbors
+            'mount':              'nothing',
+            'unmount':            'nothing',
+            'listmounts':         'mounts',
+            'listneighbors':      'neighbors',
             # Sticker Commands
-            "sticker get":        "item",
-            "sticker set":        "nothing",
-            "sticker delete":     "nothing",
-            "sticker list":       "list",
-            "sticker find":       "songs",
+            'sticker get':        'item',
+            'sticker set':        'nothing',
+            'sticker delete':     'nothing',
+            'sticker list':       'list',
+            'sticker find':       'songs',
             # Connection Commands
-            "close":              None,
-            "kill":               None,
-            "password":           "nothing",
-            "ping":               "nothing",
+            'close':              None,
+            'kill':               None,
+            'password':           'nothing',
+            'ping':               'nothing',
+            'binarylimit':        'nothing',
+            'tagtypes':           'list',
+            'tagtypes disable':   'nothing',
+            'tagtypes enable':    'nothing',
+            'tagtypes clear':     'nothing',
+            'tagtypes all':       'nothing',
+            # Partition Commands
+            'partition':          'nothing',
+            'listpartitions':     'list',
+            'newpartition':       'nothing',
+            'delpartition':       'nothing',
+            'moveoutput':         'nothing',
             # Audio Output Commands
-            "disableoutput":      "nothing",
-            "enableoutput":       "nothing",
-            "toggleoutput":       "nothing",
-            "outputs":            "outputs",
+            'disableoutput':      'nothing',
+            'enableoutput':       'nothing',
+            'toggleoutput':       'nothing',
+            'outputs':            'outputs',
+            'outputset':          'nothing',
             # Reflection Commands
-            "commands":           "list",
-            "notcommands":        "list",
-            "tagtypes":           "list",
-            "urlhandlers":        "list",
-            "decoders":           "plugins",
+            'config':             'object',
+            'commands':           'list',
+            'notcommands':        'list',
+            'urlhandlers':        'list',
+            'decoders':           'plugins',
             # Client to Client
-            "subscribe":          "nothing",
-            "unsubscribe":        "nothing",
-            "channels":           "list",
-            "readmessages":       "messages",
-            "sendmessage":        "nothing",
+            'subscribe':          'nothing',
+            'unsubscribe':        'nothing',
+            'channels':           'list',
+            'readmessages':       'messages',
+            'sendmessage':        'nothing',
         }
 
     def setUp(self):
-        self.socket_patch = mock.patch("musicpd.socket")
+        self.socket_patch = mock.patch('musicpd.socket')
         self.socket_mock = self.socket_patch.start()
         self.socket_mock.getaddrinfo.return_value = [range(5)]
 
@@ -151,12 +277,12 @@ class TestMPDClient(unittest.TestCase):
             # attributes across calls).
             # That's probably what we want, since reconnecting is like
             # reinitializing the entire connection, and so, the mock.
-            mock.MagicMock(name="socket.socket"))
+            mock.MagicMock(name='socket.socket'))
 
         self.client = musicpd.MPDClient()
         self.client.connect(TEST_MPD_HOST, TEST_MPD_PORT)
         self.client._sock.reset_mock()
-        self.MPDWillReturn("ACK don't forget to setup your mock\n")
+        self.MPDWillReturn('ACK don\'t forget to setup your mock\n')
 
     def tearDown(self):
         self.socket_patch.stop()
@@ -167,12 +293,34 @@ class TestMPDClient(unittest.TestCase):
         self.client._rfile.readline.side_effect = itertools.chain(
             lines, itertools.repeat(''))
 
+    def MPDWillReturnBinary(self, lines):
+        data = bytearray(b''.join(lines))
+
+        def read(amount):
+            val = bytearray()
+            while amount > 0:
+                amount -= 1
+                # _ = data.pop(0)
+                # print(hex(_))
+                val.append(data.pop(0))
+            return val
+
+        def readline():
+            val = bytearray()
+            while not val.endswith(b'\x0a'):
+                val.append(data.pop(0))
+            return val
+        self.client._rbfile.readline.side_effect = readline
+        self.client._rbfile.read.side_effect = read
+
     def assertMPDReceived(self, *lines):
         self.client._wfile.write.assert_called_with(*lines)
 
     def test_metaclass_commands(self):
+        """Controls client has at least commands as last synchronized in
+        TestMPDClient.commands"""
         for cmd, ret in TestMPDClient.commands.items():
-            self.assertTrue(hasattr(self.client, cmd), msg='fails for{}'.format(cmd))
+            self.assertTrue(hasattr(self.client, cmd), msg='cmd "{}" not available!'.format(cmd))
             if ' ' in cmd:
                 self.assertTrue(hasattr(self.client, cmd.replace(' ', '_')))
 
@@ -203,8 +351,18 @@ class TestMPDClient(unittest.TestCase):
         self.assertMPDReceived('stats\n')
         self.assertIsInstance(stats, dict)
 
+        output = ['outputid: 0\n',
+                  'outputname: default detected output\n',
+                  'plugin: sndio\n',
+                  'outputenabled: 1\n']
+        self.MPDWillReturn(*output, 'OK\n')
+        outputs = self.client.outputs()
+        self.assertMPDReceived('outputs\n')
+        self.assertIsInstance(outputs, list)
+        self.assertEqual([{'outputid': '0', 'outputname': 'default detected output', 'plugin': 'sndio', 'outputenabled': '1'}], outputs)
+
     def test_fetch_songs(self):
-        self.MPDWillReturn("file: my-song.ogg\n", "Pos: 0\n", "Id: 66\n", "OK\n")
+        self.MPDWillReturn('file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n', 'OK\n')
         playlist = self.client.playlistinfo()
 
         self.assertMPDReceived('playlistinfo\n')
@@ -217,7 +375,7 @@ class TestMPDClient(unittest.TestCase):
         self.assertEqual('66', e['id'])
 
     def test_send_and_fetch(self):
-        self.MPDWillReturn("volume: 50\n", "OK\n")
+        self.MPDWillReturn('volume: 50\n', 'OK\n')
         result = self.client.send_status()
         self.assertEqual(None, result)
         self.assertMPDReceived('status\n')
@@ -227,16 +385,20 @@ class TestMPDClient(unittest.TestCase):
         self.assertEqual({'volume': '50'}, status)
 
     def test_iterating(self):
-        self.MPDWillReturn("file: my-song.ogg\n", "Pos: 0\n", "Id: 66\n", "OK\n")
+        self.MPDWillReturn('file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n',
+                           'file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n', 'OK\n')
         self.client.iterate = True
         playlist = self.client.playlistinfo()
         self.assertMPDReceived('playlistinfo\n')
         self.assertIsInstance(playlist, types.GeneratorType)
+        self.assertTrue(self.client._iterating)
         for song in playlist:
+            self.assertRaises(musicpd.IteratingError, self.client.status)
             self.assertIsInstance(song, dict)
             self.assertEqual('my-song.ogg', song['file'])
             self.assertEqual('0', song['pos'])
             self.assertEqual('66', song['id'])
+        self.assertFalse(self.client._iterating)
 
     def test_noidle(self):
         self.MPDWillReturn('OK\n') # nothing changed after idle-ing
@@ -244,7 +406,7 @@ class TestMPDClient(unittest.TestCase):
         self.MPDWillReturn('OK\n') # nothing changed after noidle
         self.assertEqual(self.client.noidle(), [])
         self.assertMPDReceived('noidle\n')
-        self.MPDWillReturn("volume: 50\n", "OK\n")
+        self.MPDWillReturn('volume: 50\n', 'OK\n')
         self.client.status()
         self.assertMPDReceived('status\n')
 
@@ -253,7 +415,7 @@ class TestMPDClient(unittest.TestCase):
         self.client.send_idle()
         self.MPDWillReturn('CHANGED: player\n', 'OK\n')  # noidle response
         self.assertEqual(self.client.noidle(), ['player'])
-        self.MPDWillReturn("volume: 50\n", "OK\n")
+        self.MPDWillReturn('volume: 50\n', 'OK\n')
         status = self.client.status()
         self.assertMPDReceived('status\n')
         self.assertEqual({'volume': '50'}, status)
@@ -264,8 +426,6 @@ class TestMPDClient(unittest.TestCase):
         self.assertRaises(musicpd.CommandError, self.client.noidle)
 
     def test_client_to_client(self):
-        # client to client is at this time in beta!
-
         self.MPDWillReturn('OK\n')
         self.assertIsNone(self.client.subscribe("monty"))
         self.assertMPDReceived('subscribe "monty"\n')
@@ -273,19 +433,19 @@ class TestMPDClient(unittest.TestCase):
         self.MPDWillReturn('channel: monty\n', 'OK\n')
         channels = self.client.channels()
         self.assertMPDReceived('channels\n')
-        self.assertEqual(["monty"], channels)
+        self.assertEqual(['monty'], channels)
 
         self.MPDWillReturn('OK\n')
-        self.assertIsNone(self.client.sendmessage("monty", "SPAM"))
+        self.assertIsNone(self.client.sendmessage('monty', 'SPAM'))
         self.assertMPDReceived('sendmessage "monty" "SPAM"\n')
 
         self.MPDWillReturn('channel: monty\n', 'message: SPAM\n', 'OK\n')
         msg = self.client.readmessages()
         self.assertMPDReceived('readmessages\n')
-        self.assertEqual(msg, [{"channel":"monty", "message": "SPAM"}])
+        self.assertEqual(msg, [{'channel': 'monty', 'message': 'SPAM'}])
 
         self.MPDWillReturn('OK\n')
-        self.assertIsNone(self.client.unsubscribe("monty"))
+        self.assertIsNone(self.client.unsubscribe('monty'))
         self.assertMPDReceived('unsubscribe "monty"\n')
 
         self.MPDWillReturn('OK\n')
@@ -294,35 +454,34 @@ class TestMPDClient(unittest.TestCase):
         self.assertEqual([], channels)
 
     def test_ranges_in_command_args(self):
-        self.MPDWillReturn("OK\n")
+        self.MPDWillReturn('OK\n')
         self.client.playlistinfo((10,))
         self.assertMPDReceived('playlistinfo 10:\n')
 
-        self.MPDWillReturn("OK\n")
-        self.client.playlistinfo(("10",))
+        self.MPDWillReturn('OK\n')
+        self.client.playlistinfo(('10',))
         self.assertMPDReceived('playlistinfo 10:\n')
 
-        self.MPDWillReturn("OK\n")
+        self.MPDWillReturn('OK\n')
         self.client.playlistinfo((10, 12))
         self.assertMPDReceived('playlistinfo 10:12\n')
 
-        self.MPDWillReturn("OK\n")
+        self.MPDWillReturn('OK\n')
         self.client.rangeid(())
         self.assertMPDReceived('rangeid :\n')
 
-
-        for arg in [(10, "t"), (10, 1, 1), (None,1)]:
-            self.MPDWillReturn("OK\n")
+        for arg in [(10, 't'), (10, 1, 1), (None,1)]:
+            self.MPDWillReturn('OK\n')
             with self.assertRaises(musicpd.CommandError):
                 self.client.playlistinfo(arg)
 
     def test_numbers_as_command_args(self):
-        self.MPDWillReturn("OK\n")
-        self.client.find("file", 1)
+        self.MPDWillReturn('OK\n')
+        self.client.find('file', 1)
         self.assertMPDReceived('find "file" "1"\n')
 
     def test_commands_without_callbacks(self):
-        self.MPDWillReturn("\n")
+        self.MPDWillReturn('\n')
         self.client.close()
         self.assertMPDReceived('close\n')
 
@@ -346,20 +505,102 @@ class TestMPDClient(unittest.TestCase):
         self.assertIs(self.client._sock, None)
 
     def test_parse_sticker_get_no_sticker(self):
-        self.MPDWillReturn("ACK [50@0] {sticker} no such sticker\n")
+        self.MPDWillReturn('ACK [50@0] {sticker} no such sticker\n')
         self.assertRaises(musicpd.CommandError,
                           self.client.sticker_get, 'song', 'baz', 'foo')
 
     def test_parse_sticker_list(self):
-        self.MPDWillReturn("sticker: foo=bar\n", "sticker: lom=bok\n", "OK\n")
+        self.MPDWillReturn('sticker: foo=bar\n', 'sticker: lom=bok\n', 'OK\n')
         res = self.client.sticker_list('song', 'baz')
         self.assertEqual(['foo=bar', 'lom=bok'], res)
 
         # Even with only one sticker, we get a dict
-        self.MPDWillReturn("sticker: foo=bar\n", "OK\n")
+        self.MPDWillReturn('sticker: foo=bar\n', 'OK\n')
         res = self.client.sticker_list('song', 'baz')
         self.assertEqual(['foo=bar'], res)
 
+    def test_albumart(self):
+        # here is a 34 bytes long data
+        data = bytes('\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01'
+                     '\x00\x01\x00\x00\xff\xdb\x00C\x00\x05\x03\x04',
+                     encoding='utf8')
+        read_lines = [b'size: 42\nbinary: 34\n', data, b'\nOK\n']
+        self.MPDWillReturnBinary(read_lines)
+        # Reading albumart / offset 0 should return the data
+        res = self.client.albumart('muse/Raised Fist/2002-Dedication/', 0)
+        self.assertEqual(res.get('data'), data)
+
+    def test_reading_binary(self):
+        # readpicture when there are no picture returns empty object
+        self.MPDWillReturnBinary([b'OK\n'])
+        res = self.client.readpicture('muse/Raised Fist/2002-Dedication/', 0)
+        self.assertEqual(res, {})
+
+    def test_command_list(self):
+        self.MPDWillReturn('updating_db: 42\n',
+                           f'{musicpd.NEXT}\n',
+                           'repeat: 0\n',
+                           'random: 0\n',
+                           f'{musicpd.NEXT}\n',
+                           f'{musicpd.NEXT}\n',
+                           'OK\n')
+        self.client.command_list_ok_begin()
+        self.client.update()
+        self.client.status()
+        self.client.repeat(1)
+        self.client.command_list_end()
+        self.assertMPDReceived('command_list_end\n')
+
+    def test_two_word_commands(self):
+        self.MPDWillReturn('OK\n')
+        self.client.tagtypes_clear()
+        self.assertMPDReceived('tagtypes clear\n')
+        self.MPDWillReturn('OK\n')
+        with self.assertRaises(AttributeError):
+            self.client.foo_bar()
+
+class testConnection(unittest.TestCase):
+
+    def test_exposing_fileno(self):
+        with mock.patch('musicpd.socket') as socket_mock:
+            sock = mock.MagicMock(name='socket')
+            socket_mock.socket.return_value = sock
+            cli = musicpd.MPDClient()
+            cli.connect()
+            cli.fileno()
+            cli._sock.fileno.assert_called_with()
+
+    def test_connect_abstract(self):
+        os.environ['MPD_HOST'] = '@abstract'
+        with mock.patch('musicpd.socket') as socket_mock:
+            sock = mock.MagicMock(name='socket')
+            socket_mock.socket.return_value = sock
+            cli = musicpd.MPDClient()
+            cli.connect()
+            sock.connect.assert_called_with('\0abstract')
+
+    def test_connect_unix(self):
+        os.environ['MPD_HOST'] = '/run/mpd/socket'
+        with mock.patch('musicpd.socket') as socket_mock:
+            sock = mock.MagicMock(name='socket')
+            socket_mock.socket.return_value = sock
+            cli = musicpd.MPDClient()
+            cli.connect()
+            sock.connect.assert_called_with('/run/mpd/socket')
+
+
+class testException(unittest.TestCase):
+
+    def test_CommandError_on_newline(self):
+        os.environ['MPD_HOST'] = '/run/mpd/socket'
+        with mock.patch('musicpd.socket') as socket_mock:
+            sock = mock.MagicMock(name='socket')
+            socket_mock.socket.return_value = sock
+            cli = musicpd.MPDClient()
+            cli.connect()
+            with self.assertRaises(musicpd.CommandError):
+                cli.find('(album == "foo\nbar")')
+
 
 if __name__ == '__main__':
     unittest.main()