Repository: opsdroid/opsdroid

Issue:
Handle slacker connector invalid token exception
<!-- Before you post an issue or if you are unsure about something join our gitter channel https://gitter.im/opsdroid/ and ask away! We are more than happy to help you. -->
# Description
This issue was found by @petri with #763

When we use an invalid token with the slacker connector opsdroid crashes badly. This is probably due to the fact that we changed dependencies and the code doesn't handle the exception raised by the aioslacker library.

We should probably refactor the connect method to check if the exception `slacker.Error: invalid_auth` was raised, if so an error message should be logged and opsdroid should still be able to run.


## Steps to Reproduce
- Set a slack connector with a bad token on config.yaml
- run opsdroid


## Expected Functionality
Opsdroid should still run but a message should be logged that the connector won't be active due to bad token.


## Experienced Functionality
Opsdroid crashes with a traceback

```python
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/__main__.py", line 206, in <module>
    init()
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/__main__.py", line 203, in init
    main()
  File "/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/__main__.py", line 196, in main
    opsdroid.load()
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/core.py", line 153, in load
    self.start_connectors(self.modules["connectors"])
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/core.py", line 248, in start_connectors
    self.eventloop.run_until_complete(connector.connect(self))
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 466, in run_until_complete
    return future.result()
  File "/Users/fabiorosado/Documents/GitHub/opsdroid/opsdroid/connector/slack/__init__.py", line 50, in connect
    connection = await self.slacker.rtm.start()
  File "/Users/fabiorosado/.local/share/virtualenvs/opsdroid-13bLHlYD/lib/python3.6/site-packages/aioslacker/__init__.py", line 97, in __request
    raise Error(response.error)
slacker.Error: invalid_auth
Exception ignored in: <bound method BaseEventLoop.__del__ of <_UnixSelectorEventLoop running=False closed=True debug=False>>
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 511, in __del__
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/unix_events.py", line 65, in close
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/unix_events.py", line 146, in remove_signal_handler
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/signal.py", line 47, in signal
TypeError: signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object
```

## Versions
- **Opsdroid version:** latest
- **Python version:** 3.6
- **OS/Docker version:** MacOs Mojave

## Configuration File
Please include your version of the configuration file bellow.

```yaml
connectors:
  - name: slack
     api-token: "jdit-ksd12okr"
```

## Additional Details
Any other details you wish to include such as screenshots, console messages, etc.


<!-- Love opsdroid? Please consider supporting our collective:
 +👉  https://opencollective.com/opsdroid/donate -->


Discussion:
We should check whether the connectors actually connect [here](https://github.com/opsdroid/opsdroid/blob/44df8f609656ab5bafef1ff79c14ab8cc2cbc647/opsdroid/core.py#L248). If they don't we should probably gracefully exit opsdroid. 

The connector itself should return `True` or `False` on connect and catch all errors. Then opsdroid core should handle when a connector returns `False`.
Okay so we need to change all connectors in order to return either true or false  and then handle that return on the core, this might add a few more if/else nested block in the core though so however we implement it should try to avoid that
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.


Resolving patch:
diff --git a/opsdroid/connector/slack/__init__.py b/opsdroid/connector/slack/__init__.py
--- a/opsdroid/connector/slack/__init__.py
+++ b/opsdroid/connector/slack/__init__.py
@@ -58,6 +58,9 @@ async def connect(self):
             _LOGGER.error(error)
             _LOGGER.error("Failed to connect to Slack, retrying in 10")
             await self.reconnect(10)
+        except slacker.Error as error:
+            _LOGGER.error("Unable to connect to Slack due to %s - "
+                          "The Slack Connector will not be available.", error)
         except Exception:
             await self.disconnect()
             raise
@@ -79,7 +82,10 @@ async def disconnect(self):
     async def listen(self):
         """Listen for and parse new messages."""
         while self.listening:
-            await self.receive_from_websocket()
+            try:
+                await self.receive_from_websocket()
+            except AttributeError:
+                break
 
     async def receive_from_websocket(self):
         """Get the next message from the websocket."""


Test patch:
diff --git a/tests/test_connector_slack.py b/tests/test_connector_slack.py
--- a/tests/test_connector_slack.py
+++ b/tests/test_connector_slack.py
@@ -5,6 +5,7 @@
 import unittest.mock as mock
 import asynctest
 import asynctest.mock as amock
+import slacker
 
 from opsdroid.core import OpsDroid
 from opsdroid.connector.slack import ConnectorSlack
@@ -72,6 +73,16 @@ async def test_connect(self):
         self.assertTrue(mocked_websocket_connect.called)
         self.assertTrue(connector.keepalive_websocket.called)
 
+    async def test_connect_auth_fail(self):
+        connector = ConnectorSlack({"api-token": "abc123"}, opsdroid=OpsDroid())
+        opsdroid = amock.CoroutineMock()
+        opsdroid.eventloop = self.loop
+        connector.slacker.rtm.start = amock.CoroutineMock()
+        connector.slacker.rtm.start.side_effect = slacker.Error()
+
+        await connector.connect()
+        self.assertLogs('_LOGGER', 'error')
+
     async def test_reconnect_on_error(self):
         import aiohttp
         connector = ConnectorSlack({"api-token": "abc123"}, opsdroid=OpsDroid())
@@ -101,6 +112,14 @@ async def test_listen_loop(self):
             await connector.listen()
         self.assertTrue(connector.receive_from_websocket.called)
 
+    async def test_listen_break_loop(self):
+        """Test that listening consumes from the socket."""
+        connector = ConnectorSlack({"api-token": "abc123"}, opsdroid=OpsDroid())
+        connector.receive_from_websocket = amock.CoroutineMock()
+        connector.receive_from_websocket.side_effect = AttributeError
+        await connector.listen()
+        self.assertTrue(connector.receive_from_websocket.called)
+
     async def test_receive_from_websocket(self):
         """Test receive_from_websocket receives and reconnects."""
         import websockets