diff --git a/.travis.yml b/.travis.yml index 080578e..c44e707 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: python python: - "3.6" install: - - "pip install flake8" + - "pip install flake8 pytest" + - "pip install -r requirements.txt" script: - "flake8 haaska.py" + - "python -m pytest test.py" diff --git a/README.md b/README.md index 6c44bcc..013cb0b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # haaska: Home Assistant Alexa Skill Adapter + [![Build Status](https://travis-ci.org/mike-grant/haaska.svg?branch=master)](https://travis-ci.org/mike-grant/haaska) --- @@ -10,6 +11,13 @@ This provides voice control for a connected home managed by Home Assistant, thro ### Getting Started To get started, head over to the [haaska Wiki](https://github.com/mike-grant/haaska/wiki). +### Development + +Run tests + +``` +python -m pytest test.py +``` ### Thanks and Acknowledgement @@ -22,4 +30,4 @@ This fork of haaska was created by [@mike-grant](https://github.com/mike-grant). Documentation and additional maintenance is done by [@anthonylavado](https://github.com/anthonylavado), and contributors like you. ### License -haaska is provided under the [MIT License](license.md). +haaska is provided under the [MIT License](LICENSE). diff --git a/haaska.py b/haaska.py index e0d829e..07b6cb7 100644 --- a/haaska.py +++ b/haaska.py @@ -32,72 +32,74 @@ class HomeAssistant(object): def __init__(self, config): self.config = config - self.url = config.url.rstrip('/') - agent_str = 'Home Assistant Alexa Smart Home Skill - %s - %s' - agent_fmt = agent_str % (os.environ['AWS_DEFAULT_REGION'], - requests.utils.default_user_agent()) + self.session = requests.Session() - self.session.headers = {'Authorization': - f'Bearer {config.bearer_token}', - 'content-type': 'application/json', - 'User-Agent': agent_fmt} + self.session.headers = { + 'Authorization': f'Bearer {config.bearer_token}', + 'content-type': 'application/json', + 'User-Agent': self.get_user_agent() + } self.session.verify = config.ssl_verify self.session.cert = config.ssl_client - def build_url(self, relurl): - return '%s/%s' % (self.config.url, relurl) + def build_url(self, endpoint): + return f'{self.config.url}/api/{endpoint}' + + def get_user_agent(self): + library = "Home Assistant Alexa Smart Home Skill" + aws_region = os.environ.get("AWS_DEFAULT_REGION") + default_user_agent = requests.utils.default_user_agent() + return f"{library} - {aws_region} - {default_user_agent}" - def get(self, relurl): - r = self.session.get(self.build_url(relurl)) + def get(self, endpoint): + r = self.session.get(self.build_url(endpoint)) r.raise_for_status() return r.json() - def post(self, relurl, d, wait=False): + def post(self, endpoint, data, wait=False): read_timeout = None if wait else 0.01 - r = None try: - logger.debug('calling %s with %s', relurl, str(d)) - r = self.session.post(self.build_url(relurl), - data=json.dumps(d), + logger.debug(f'calling {endpoint} with {data}') + r = self.session.post(self.build_url(endpoint), + data=json.dumps(data), timeout=(None, read_timeout)) r.raise_for_status() + return r.json() except requests.exceptions.ReadTimeout: # Allow response timeouts after request was sent - logger.debug('request for %s sent without waiting for response', - relurl) - return r + logger.debug( + f'request for {endpoint} sent without waiting for response') + return None class Configuration(object): - def __init__(self, filename=None, optsDict=None): + def __init__(self, filename=None, opts_dict=None): self._json = {} if filename is not None: with open(filename) as f: self._json = json.load(f) - if optsDict is not None: - self._json = optsDict + if opts_dict is not None: + self._json = opts_dict - opts = {} - opts['url'] = self.get(['url', 'ha_url'], - default='http://localhost:8123/api') - opts['ssl_verify'] = self.get(['ssl_verify', 'ha_cert'], default=True) - opts['bearer_token'] = self.get(['bearer_token'], default='') - opts['ssl_client'] = self.get(['ssl_client'], default='') - opts['debug'] = self.get(['debug'], default=False) - self.opts = opts + self.url = self.get_url(self.get(['url', 'ha_url'])) + self.ssl_verify = self.get(['ssl_verify', 'ha_cert'], default=True) + self.bearer_token = self.get(['bearer_token'], default='') + self.ssl_client = self.get(['ssl_client'], default='') + self.debug = self.get(['debug'], default=False) - def __getattr__(self, name): - return self.opts[name] - - def get(self, keys, default): + def get(self, keys, default=None): for key in keys: if key in self._json: return self._json[key] return default - def dump(self): - return json.dumps(self.opts, indent=2, separators=(',', ': ')) + def get_url(self, url): + """Returns Home Assistant base url without '/api' or trailing slash""" + if not url: + raise ValueError('Property "url" is missing in config') + + return url.rstrip("/api").rstrip("/") def event_handler(event, context): @@ -106,4 +108,4 @@ def event_handler(event, context): logger.setLevel(logging.DEBUG) ha = HomeAssistant(config) - return ha.post('alexa/smart_home', event, wait=True).json() + return ha.post('alexa/smart_home', event, wait=True) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..60ddf4b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pytest==4.0.2 +requests==2.21.0 \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..c2c80c8 --- /dev/null +++ b/test.py @@ -0,0 +1,38 @@ +import os +import pytest + +from haaska import HomeAssistant, Configuration + +@pytest.fixture +def configuration(): + return Configuration(opts_dict={ + "url": "http://localhost:8123", + "bearer_token": "", + "debug": False, + "ssl_verify": True, + "ssl_client": [] + }) + +@pytest.fixture +def home_assistant(configuration): + return HomeAssistant(configuration) + +def test_ha_build_url(home_assistant): + url = home_assistant.build_url("test") + assert url == "http://localhost:8123/api/test" + +def test_get_user_agent(home_assistant): + os.environ["AWS_DEFAULT_REGION"] = "test" + user_agent = home_assistant.get_user_agent() + assert user_agent.startswith("Home Assistant Alexa Smart Home Skill - test - python-requests/") + +def test_config_get(configuration): + assert configuration.get(["debug"]) == False + assert configuration.get(["test"]) == None + assert configuration.get(["test"], default="default") == "default" + +def test_config_get_url(configuration): + expected = "http://hass.example.com:8123" + assert configuration.get_url("http://hass.example.com:8123/") == expected + assert configuration.get_url("http://hass.example.com:8123/api") == expected + assert configuration.get_url("http://hass.example.com:8123/api/") == expected \ No newline at end of file