Skip to content

Commit

Permalink
Add tests (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
edeckers authored Feb 28, 2022
1 parent 1c6a774 commit 9052d50
Show file tree
Hide file tree
Showing 30 changed files with 373 additions and 101 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build init lint
.PHONY: build init lint test

build:
src/bin/build.sh
Expand All @@ -8,3 +8,6 @@ init:

lint:
src/bin/lint.sh

test:
src/bin/test.sh
1 change: 1 addition & 0 deletions src/bin/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ function cd_to_source_directory() {

function run_linter() {
pylint huemon
pylint tests
}

cd_to_source_directory
Expand Down
12 changes: 12 additions & 0 deletions src/bin/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash

function cd_to_source_directory() {
cd `dirname ${0}`/..
}

function run_tests() {
python3 -m unittest discover tests -b
}

cd_to_source_directory
run_tests
82 changes: 22 additions & 60 deletions src/huemon/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,89 +5,51 @@

import sys

from functools import reduce
from huemon.api.api import Api
from huemon.api.cached_api import CachedApi
from huemon.api_interface import ApiInterface
from huemon.api.api_factory import create_api
from huemon.commands.command_handler import CommandHandler, create_name_to_command_mapping
from huemon.commands_internal.install_available_command import InstallAvailableCommand
from huemon.config_factory import create_config
from huemon.infrastructure.config_factory import create_config
from huemon.const import EXIT_OK

from huemon.hue_command_interface import HueCommand
from huemon.logger_factory import bootstrap_logger
from huemon.commands.hue_command_interface import HueCommand
from huemon.infrastructure.logger_factory import bootstrap_logger
from huemon.plugin_loader import load_plugins
from huemon.util import exit_fail, get_commands_path

CONFIG = create_config()
COMMAND_PLUGINS_PATH = get_commands_path(CONFIG, "enabled")
HUE_HUB_URL = f"http://{CONFIG['ip']}/api/{CONFIG['key']}"
LOG = bootstrap_logger(CONFIG)
DEFAULT_MAX_CACHE_AGE_SECONDS = 10


def create_command_handlers(config: dict, api: ApiInterface, plugins: dict):
return reduce(
lambda p, c: {**p, c.name(): c(config, api)}, plugins, {})
def load_command_plugins():
LOG.debug("Loading command plugins (path=%s)", COMMAND_PLUGINS_PATH)
command_handler_plugins = \
create_name_to_command_mapping(
CONFIG,
create_api(CONFIG),
load_plugins("command", COMMAND_PLUGINS_PATH, HueCommand))
LOG.debug("Finished loading command plugins (path=%s)", COMMAND_PLUGINS_PATH)

return command_handler_plugins

def create_api(config: dict):
enable_cache = \
"cache" in config and \
"enable" in config["cache"] and \
bool(config["cache"]["enable"])

is_cache_age_configured = \
"cache" in config and \
"max_age_seconds" in config["cache"]
def load_plugins_and_hardwired_handlers():
return {
**load_command_plugins(),
InstallAvailableCommand.name(): InstallAvailableCommand(CONFIG)
}

max_cache_age_seconds = \
int(config["cache"]["max_age_seconds"]) if \
is_cache_age_configured else \
DEFAULT_MAX_CACHE_AGE_SECONDS

api = Api(HUE_HUB_URL)

return CachedApi(api, max_cache_age_seconds) if enable_cache else api


class CommandHandler: # pylint: disable=too-few-public-methods
def __init__(self, handlers):
self.handlers = handlers

def available_commands(self):
return list(self.handlers)

def exec(self, command: str, arguments):
LOG.debug("Running command `%s` (arguments=%s)", command, arguments)
if not command in self.handlers:
exit_fail(
"Received unknown command `%s`, expected one of %s",
command,
self.available_commands())

self.handlers[command].exec(arguments)

LOG.debug("Finished command `%s` (arguments=%s)", command, arguments)
def create_default_command_handler():
return CommandHandler(load_plugins_and_hardwired_handlers())


class Main: # pylint: disable=too-few-public-methods
@staticmethod
def main(argv):
LOG.debug("Running script (parameters=%s)", argv[1:])

LOG.debug("Loading command plugins (path=%s)", COMMAND_PLUGINS_PATH)
command_handler_plugins = \
create_command_handlers(
CONFIG,
create_api(CONFIG),
load_plugins("command", COMMAND_PLUGINS_PATH, HueCommand))
LOG.debug("Finished loading command plugins (path=%s)",
COMMAND_PLUGINS_PATH)

command_handler = CommandHandler({
**command_handler_plugins,
InstallAvailableCommand.name(): InstallAvailableCommand(CONFIG)
})
command_handler = create_default_command_handler()

if len(argv) <= 1:
exit_fail(
Expand Down
4 changes: 2 additions & 2 deletions src/huemon/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import json

from urllib.request import urlopen
from huemon.api_interface import ApiInterface
from huemon.logger_factory import create_logger
from huemon.api.api_interface import ApiInterface
from huemon.infrastructure.logger_factory import create_logger


LOG = create_logger()
Expand Down
29 changes: 29 additions & 0 deletions src/huemon/api/api_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@


from huemon.api.api import Api
from huemon.api.cached_api import CachedApi
from huemon.const import DEFAULT_MAX_CACHE_AGE_SECONDS


def create_hue_hub_url(config: dict):
return f"http://{config['ip']}/api/{config['key']}"


def create_api(config: dict):
enable_cache = \
"cache" in config and \
"enable" in config["cache"] and \
bool(config["cache"]["enable"])

is_cache_age_configured = \
"cache" in config and \
"max_age_seconds" in config["cache"]

max_cache_age_seconds = \
int(config["cache"]["max_age_seconds"]) if \
is_cache_age_configured else \
DEFAULT_MAX_CACHE_AGE_SECONDS

api = Api(create_hue_hub_url(config))

return CachedApi(api, max_cache_age_seconds) if enable_cache else api
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
# LICENSE file in the root directory of this source tree.

class ApiInterface:
def get_system_config(): # pylint: disable=no-method-argument
def get_system_config(self):
pass

def get_lights(): # pylint: disable=no-method-argument
def get_lights(self):
pass

def get_sensors(): # pylint: disable=no-method-argument
def get_sensors(self):
pass
19 changes: 10 additions & 9 deletions src/huemon/api/cached_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,28 @@
from os.path import exists
import tempfile
import time
from huemon.api_interface import ApiInterface
from huemon.logger_factory import create_logger
from huemon.api.api_interface import ApiInterface
from huemon.infrastructure.logger_factory import create_logger

LOG = create_logger()

DEFAULT_MAX_CACHE_AGE_SECONDS = 10
DEFAULT_CACHE_PATH = tempfile.gettempdir()


class CachedApi(ApiInterface):
def __init__(self, api: ApiInterface, max_cache_age_seconds=DEFAULT_MAX_CACHE_AGE_SECONDS):
def __init__(self, api: ApiInterface, max_cache_age_seconds=DEFAULT_MAX_CACHE_AGE_SECONDS, cache_path=DEFAULT_CACHE_PATH):
self.api = api
self.cache_path = cache_path
self.max_cache_age_seconds = max_cache_age_seconds

@staticmethod
def __tf(filename):
return "/".join([tempfile.gettempdir(), filename])
def __tf(self, filename):
return "/".join([self.cache_path, filename])

def __cache(self, resource_type: str, fn_call):
temp_filename = f"zabbix-hue.{resource_type}"
cache_file_path = CachedApi.__tf(f"{temp_filename}.json")
lock_file = CachedApi.__tf(f"{temp_filename}.lock")
cache_file_path = self.__tf(f"{temp_filename}.json")
lock_file = self.__tf(f"{temp_filename}.lock")

does_cache_file_exist = exists(cache_file_path)
cache_age_seconds = time.time(
Expand Down Expand Up @@ -58,7 +59,7 @@ def __cache(self, resource_type: str, fn_call):

with open(cache_file_path) as f_json:
return json.loads(f_json.read())
except: # pylint: disable=bare-except
except: # pylint: disable=bare-except
LOG.debug("Failed to acquire lock, cache hit (file=%s)", lock_file)

if not does_cache_file_exist:
Expand Down
Empty file added src/huemon/commands/__init__.py
Empty file.
37 changes: 37 additions & 0 deletions src/huemon/commands/command_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (c) Ely Deckers.
#
# This source code is licensed under the MPL-2.0 license found in the
# LICENSE file in the root directory of this source tree.

from functools import reduce

from huemon.api.api_interface import ApiInterface
from huemon.infrastructure.logger_factory import create_logger
from huemon.util import exit_fail

LOG = create_logger()


def create_name_to_command_mapping(config: dict, api: ApiInterface, plugins: list):
return reduce(
lambda p, c: {**p, c.name(): c(config, api)}, plugins, {})


class CommandHandler: # pylint: disable=too-few-public-methods
def __init__(self, handlers):
self.handlers = handlers

def available_commands(self):
return list(self.handlers)

def exec(self, command: str, arguments):
LOG.debug("Running command `%s` (arguments=%s)", command, arguments)
if not command in self.handlers:
exit_fail(
"Received unknown command `%s`, expected one of %s",
command,
self.available_commands())

self.handlers[command].exec(arguments)

LOG.debug("Finished command `%s` (arguments=%s)", command, arguments)
File renamed without changes.
8 changes: 4 additions & 4 deletions src/huemon/commands_available/discover_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
# LICENSE file in the root directory of this source tree.

from functools import reduce
from huemon.api_interface import ApiInterface
from huemon.discovery_interface import Discovery
from huemon.hue_command_interface import HueCommand
from huemon.logger_factory import create_logger
from huemon.api.api_interface import ApiInterface
from huemon.discoveries.discovery_interface import Discovery
from huemon.commands.hue_command_interface import HueCommand
from huemon.infrastructure.logger_factory import create_logger
from huemon.plugin_loader import load_plugins
from huemon.util import assert_exists, assert_num_args, get_discoveries_path

Expand Down
6 changes: 3 additions & 3 deletions src/huemon/commands_available/light_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
# This source code is licensed under the MPL-2.0 license found in the
# LICENSE file in the root directory of this source tree.

from huemon.api_interface import ApiInterface
from huemon.api.api_interface import ApiInterface
from huemon.commands_available.sensor_command import SensorCommand
from huemon.hue_command_interface import HueCommand
from huemon.logger_factory import create_logger
from huemon.commands.hue_command_interface import HueCommand
from huemon.infrastructure.logger_factory import create_logger
from huemon.util import assert_exists, assert_num_args

LOG = create_logger()
Expand Down
6 changes: 3 additions & 3 deletions src/huemon/commands_available/sensor_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
# LICENSE file in the root directory of this source tree.

from functools import reduce
from huemon.api_interface import ApiInterface
from huemon.hue_command_interface import HueCommand
from huemon.logger_factory import create_logger
from huemon.api.api_interface import ApiInterface
from huemon.commands.hue_command_interface import HueCommand
from huemon.infrastructure.logger_factory import create_logger
from huemon.util import assert_exists, assert_num_args

LOG = create_logger()
Expand Down
6 changes: 3 additions & 3 deletions src/huemon/commands_available/system_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
# This source code is licensed under the MPL-2.0 license found in the
# LICENSE file in the root directory of this source tree.

from huemon.api_interface import ApiInterface
from huemon.hue_command_interface import HueCommand
from huemon.logger_factory import create_logger
from huemon.api.api_interface import ApiInterface
from huemon.commands.hue_command_interface import HueCommand
from huemon.infrastructure.logger_factory import create_logger
from huemon.util import assert_exists, assert_num_args

LOG = create_logger()
Expand Down
4 changes: 2 additions & 2 deletions src/huemon/commands_internal/install_available_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from pathlib import Path
from genericpath import isdir, isfile

from huemon.hue_command_interface import HueCommand
from huemon.logger_factory import create_logger
from huemon.commands.hue_command_interface import HueCommand
from huemon.infrastructure.logger_factory import create_logger
from huemon.util import assert_num_args, exit_fail, get_commands_path, get_discoveries_path

LOG = create_logger()
Expand Down
7 changes: 7 additions & 0 deletions src/huemon/const.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
# Copyright (c) Ely Deckers.
#
# This source code is licensed under the MPL-2.0 license found in the
# LICENSE file in the root directory of this source tree.

DEFAULT_MAX_CACHE_AGE_SECONDS = 10

EXIT_OK = 0
EXIT_FAIL = 1
File renamed without changes.
4 changes: 2 additions & 2 deletions src/huemon/discoveries_available/batteries_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
# This source code is licensed under the MPL-2.0 license found in the
# LICENSE file in the root directory of this source tree.

from huemon.api_interface import ApiInterface
from huemon.discovery_interface import Discovery
from huemon.api.api_interface import ApiInterface
from huemon.discoveries.discovery_interface import Discovery


class BatteriesDiscovery(Discovery):
Expand Down
4 changes: 2 additions & 2 deletions src/huemon/discoveries_available/lights_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
# This source code is licensed under the MPL-2.0 license found in the
# LICENSE file in the root directory of this source tree.

from huemon.api_interface import ApiInterface
from huemon.discovery_interface import Discovery
from huemon.api.api_interface import ApiInterface
from huemon.discoveries.discovery_interface import Discovery


class LightsDiscovery(Discovery):
Expand Down
6 changes: 3 additions & 3 deletions src/huemon/discoveries_available/sensors_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
# This source code is licensed under the MPL-2.0 license found in the
# LICENSE file in the root directory of this source tree.

from huemon.api_interface import ApiInterface
from huemon.discovery_interface import Discovery
from huemon.logger_factory import create_logger
from huemon.api.api_interface import ApiInterface
from huemon.discoveries.discovery_interface import Discovery
from huemon.infrastructure.logger_factory import create_logger
from huemon.util import exit_fail

LOG = create_logger()
Expand Down
Empty file.
Loading

0 comments on commit 9052d50

Please sign in to comment.