mirror of
https://github.com/tomaae/homeassistant-mikrotik_router.git
synced 2025-07-28 06:24:29 +02:00
added config flow test and workflow for tests
This commit is contained in:
parent
f17fefb5d8
commit
1dd4f52524
10 changed files with 1706 additions and 5 deletions
2
.github/generate_releasenotes.py
vendored
2
.github/generate_releasenotes.py
vendored
|
@ -70,6 +70,8 @@ def get_integration_commits(github, skip=True):
|
||||||
continue
|
continue
|
||||||
if " workflow" in msg:
|
if " workflow" in msg:
|
||||||
continue
|
continue
|
||||||
|
if " test" in msg:
|
||||||
|
continue
|
||||||
if "docs" in msg:
|
if "docs" in msg:
|
||||||
continue
|
continue
|
||||||
if "dev debug" in msg:
|
if "dev debug" in msg:
|
||||||
|
|
22
.github/generate_requirements.py
vendored
Normal file
22
.github/generate_requirements.py
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = configparser.ConfigParser()
|
||||||
|
parser.read("Pipfile")
|
||||||
|
|
||||||
|
packages = "packages"
|
||||||
|
with open("requirements.txt", "w") as f:
|
||||||
|
for key in parser[packages]:
|
||||||
|
value = parser[packages][key]
|
||||||
|
f.write(key + value.replace('"', "") + "\n")
|
||||||
|
|
||||||
|
devpackages = "dev-packages"
|
||||||
|
with open("requirements_tests.txt", "w") as f:
|
||||||
|
for key in parser[devpackages]:
|
||||||
|
value = parser[devpackages][key]
|
||||||
|
f.write(key + value.replace('"', "") + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
|
@ -12,7 +12,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
black:
|
black:
|
||||||
name: Black
|
name: Python Code Format Check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
@ -29,10 +29,14 @@ jobs:
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.8
|
||||||
|
- name: Generate Requirements lists
|
||||||
|
run: |
|
||||||
|
python3 .github/generate_requirements.py
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
pip install -r requirements_tests.txt
|
||||||
- name: Lint with flake8
|
- name: Lint with flake8
|
||||||
run: |
|
run: |
|
||||||
pip install flake8
|
pip install flake8
|
||||||
|
@ -40,10 +44,10 @@ jobs:
|
||||||
flake8 . --count --select=E9,F63,F7,F82 --ignore W503,E722 --show-source --statistics
|
flake8 . --count --select=E9,F63,F7,F82 --ignore W503,E722 --show-source --statistics
|
||||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||||
flake8 . --count --exit-zero --max-complexity=15 --max-line-length=127 --statistics
|
flake8 . --count --exit-zero --max-complexity=15 --max-line-length=127 --statistics
|
||||||
#- name: Test with pytest
|
- name: Test with pytest
|
||||||
# run: |
|
run: |
|
||||||
# pip install pytest
|
pip install pytest
|
||||||
# pytest
|
pytest
|
||||||
sonarcloud:
|
sonarcloud:
|
||||||
name: SonarCloud
|
name: SonarCloud
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
27
Pipfile
Normal file
27
Pipfile
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
wheel = ">=0.34"
|
||||||
|
pygithub = ">=1.47"
|
||||||
|
homeassistant = ">=0.108.4"
|
||||||
|
sqlalchemy = "==1.3.16"
|
||||||
|
codecov = "==2.0.15"
|
||||||
|
mock-open = "==1.3.1"
|
||||||
|
mypy = "==0.770"
|
||||||
|
pre-commit = "==2.2.0"
|
||||||
|
pylint = "==2.4.4"
|
||||||
|
astroid = "==2.3.3"
|
||||||
|
pylint-strict-informational = "==0.1"
|
||||||
|
pytest-aiohttp = "==0.3.0"
|
||||||
|
pytest-cov = "==2.8.1"
|
||||||
|
pytest-sugar = "==0.9.2"
|
||||||
|
pytest-timeout = "==1.3.3"
|
||||||
|
pytest = "==5.3.5"
|
||||||
|
requests_mock = "==1.7.0"
|
||||||
|
responses = "==0.10.6"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
librouteros = "==3.0.0"
|
|
@ -30,6 +30,7 @@ from .const import (
|
||||||
CONF_SCAN_INTERVAL,
|
CONF_SCAN_INTERVAL,
|
||||||
DEFAULT_SCAN_INTERVAL,
|
DEFAULT_SCAN_INTERVAL,
|
||||||
DEFAULT_UNIT_OF_MEASUREMENT,
|
DEFAULT_UNIT_OF_MEASUREMENT,
|
||||||
|
CONF_TRACK_HOSTS_TIMEOUT,
|
||||||
)
|
)
|
||||||
from .exceptions import ApiEntryNotFound
|
from .exceptions import ApiEntryNotFound
|
||||||
from .helper import parse_api
|
from .helper import parse_api
|
||||||
|
|
20
tests/__init__.py
Normal file
20
tests/__init__.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
"""Tests for the Mikrotik Router component."""
|
||||||
|
|
||||||
|
from custom_components.mikrotik_router import config_flow
|
||||||
|
|
||||||
|
MOCK_DATA = {
|
||||||
|
config_flow.CONF_NAME: config_flow.DEFAULT_DEVICE_NAME,
|
||||||
|
config_flow.CONF_HOST: config_flow.DEFAULT_HOST,
|
||||||
|
config_flow.CONF_USERNAME: config_flow.DEFAULT_USERNAME,
|
||||||
|
config_flow.CONF_PASSWORD: config_flow.DEFAULT_PASSWORD,
|
||||||
|
config_flow.CONF_PORT: config_flow.DEFAULT_PORT,
|
||||||
|
config_flow.CONF_SSL: config_flow.DEFAULT_SSL,
|
||||||
|
}
|
||||||
|
|
||||||
|
MOCK_OPTIONS = {
|
||||||
|
config_flow.CONF_SCAN_INTERVAL: config_flow.DEFAULT_SCAN_INTERVAL,
|
||||||
|
config_flow.CONF_UNIT_OF_MEASUREMENT: config_flow.DEFAULT_UNIT_OF_MEASUREMENT,
|
||||||
|
config_flow.CONF_TRACK_IFACE_CLIENTS: config_flow.DEFAULT_TRACK_IFACE_CLIENTS,
|
||||||
|
config_flow.CONF_TRACK_HOSTS: config_flow.DEFAULT_TRACK_HOSTS,
|
||||||
|
config_flow.CONF_TRACK_HOSTS_TIMEOUT: config_flow.DEFAULT_TRACK_HOST_TIMEOUT,
|
||||||
|
}
|
1148
tests/common.py
Normal file
1148
tests/common.py
Normal file
File diff suppressed because it is too large
Load diff
253
tests/conftest.py
Normal file
253
tests/conftest.py
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
"""Set up some common test helper things."""
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import requests_mock as _requests_mock
|
||||||
|
|
||||||
|
from homeassistant import util
|
||||||
|
from homeassistant.auth.const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY
|
||||||
|
from homeassistant.auth.providers import homeassistant, legacy_api_password
|
||||||
|
from homeassistant.components.websocket_api.auth import (
|
||||||
|
TYPE_AUTH,
|
||||||
|
TYPE_AUTH_OK,
|
||||||
|
TYPE_AUTH_REQUIRED,
|
||||||
|
)
|
||||||
|
from homeassistant.components.websocket_api.http import URL
|
||||||
|
from homeassistant.exceptions import ServiceNotFound
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
from homeassistant.util import location
|
||||||
|
|
||||||
|
from tests.ignore_uncaught_exceptions import (
|
||||||
|
IGNORE_UNCAUGHT_EXCEPTIONS,
|
||||||
|
IGNORE_UNCAUGHT_JSON_EXCEPTIONS,
|
||||||
|
)
|
||||||
|
|
||||||
|
pytest.register_assert_rewrite("tests.common")
|
||||||
|
|
||||||
|
from tests.common import ( # noqa: E402, isort:skip
|
||||||
|
CLIENT_ID,
|
||||||
|
INSTANCES,
|
||||||
|
MockUser,
|
||||||
|
async_test_home_assistant,
|
||||||
|
mock_coro,
|
||||||
|
mock_storage as mock_storage,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
def check_real(func):
|
||||||
|
"""Force a function to require a keyword _test_real to be passed in."""
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
async def guard_func(*args, **kwargs):
|
||||||
|
real = kwargs.pop("_test_real", None)
|
||||||
|
|
||||||
|
if not real:
|
||||||
|
raise Exception(
|
||||||
|
'Forgot to mock or pass "_test_real=True" to %s', func.__name__
|
||||||
|
)
|
||||||
|
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
|
||||||
|
return guard_func
|
||||||
|
|
||||||
|
|
||||||
|
# Guard a few functions that would make network connections
|
||||||
|
location.async_detect_location_info = check_real(location.async_detect_location_info)
|
||||||
|
util.get_local_ip = lambda: "127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def verify_cleanup():
|
||||||
|
"""Verify that the test has cleaned up resources correctly."""
|
||||||
|
yield
|
||||||
|
|
||||||
|
if len(INSTANCES) >= 2:
|
||||||
|
count = len(INSTANCES)
|
||||||
|
for inst in INSTANCES:
|
||||||
|
inst.stop()
|
||||||
|
pytest.exit(f"Detected non stopped instances ({count}), aborting test run")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def hass_storage():
|
||||||
|
"""Fixture to mock storage."""
|
||||||
|
with mock_storage() as stored_data:
|
||||||
|
yield stored_data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def hass(loop, hass_storage, request):
|
||||||
|
"""Fixture to provide a test instance of Home Assistant."""
|
||||||
|
|
||||||
|
def exc_handle(loop, context):
|
||||||
|
"""Handle exceptions by rethrowing them, which will fail the test."""
|
||||||
|
exceptions.append(context["exception"])
|
||||||
|
orig_exception_handler(loop, context)
|
||||||
|
|
||||||
|
exceptions = []
|
||||||
|
hass = loop.run_until_complete(async_test_home_assistant(loop))
|
||||||
|
orig_exception_handler = loop.get_exception_handler()
|
||||||
|
loop.set_exception_handler(exc_handle)
|
||||||
|
|
||||||
|
yield hass
|
||||||
|
|
||||||
|
loop.run_until_complete(hass.async_stop(force=True))
|
||||||
|
for ex in exceptions:
|
||||||
|
if (
|
||||||
|
request.module.__name__,
|
||||||
|
request.function.__name__,
|
||||||
|
) in IGNORE_UNCAUGHT_EXCEPTIONS:
|
||||||
|
continue
|
||||||
|
if isinstance(ex, ServiceNotFound):
|
||||||
|
continue
|
||||||
|
if (
|
||||||
|
isinstance(ex, TypeError)
|
||||||
|
and "is not JSON serializable" in str(ex)
|
||||||
|
and (request.module.__name__, request.function.__name__)
|
||||||
|
in IGNORE_UNCAUGHT_JSON_EXCEPTIONS
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def requests_mock():
|
||||||
|
"""Fixture to provide a requests mocker."""
|
||||||
|
with _requests_mock.mock() as m:
|
||||||
|
yield m
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_device_tracker_conf():
|
||||||
|
"""Prevent device tracker from reading/writing data."""
|
||||||
|
devices = []
|
||||||
|
|
||||||
|
async def mock_update_config(path, id, entity):
|
||||||
|
devices.append(entity)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.device_tracker.legacy"
|
||||||
|
".DeviceTracker.async_update_config",
|
||||||
|
side_effect=mock_update_config,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.device_tracker.legacy.async_load_config",
|
||||||
|
side_effect=lambda *args: mock_coro(devices),
|
||||||
|
):
|
||||||
|
yield devices
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def hass_access_token(hass, hass_admin_user):
|
||||||
|
"""Return an access token to access Home Assistant."""
|
||||||
|
refresh_token = hass.loop.run_until_complete(
|
||||||
|
hass.auth.async_create_refresh_token(hass_admin_user, CLIENT_ID)
|
||||||
|
)
|
||||||
|
return hass.auth.async_create_access_token(refresh_token)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def hass_owner_user(hass, local_auth):
|
||||||
|
"""Return a Home Assistant admin user."""
|
||||||
|
return MockUser(is_owner=True).add_to_hass(hass)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def hass_admin_user(hass, local_auth):
|
||||||
|
"""Return a Home Assistant admin user."""
|
||||||
|
admin_group = hass.loop.run_until_complete(
|
||||||
|
hass.auth.async_get_group(GROUP_ID_ADMIN)
|
||||||
|
)
|
||||||
|
return MockUser(groups=[admin_group]).add_to_hass(hass)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def hass_read_only_user(hass, local_auth):
|
||||||
|
"""Return a Home Assistant read only user."""
|
||||||
|
read_only_group = hass.loop.run_until_complete(
|
||||||
|
hass.auth.async_get_group(GROUP_ID_READ_ONLY)
|
||||||
|
)
|
||||||
|
return MockUser(groups=[read_only_group]).add_to_hass(hass)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def hass_read_only_access_token(hass, hass_read_only_user):
|
||||||
|
"""Return a Home Assistant read only user."""
|
||||||
|
refresh_token = hass.loop.run_until_complete(
|
||||||
|
hass.auth.async_create_refresh_token(hass_read_only_user, CLIENT_ID)
|
||||||
|
)
|
||||||
|
return hass.auth.async_create_access_token(refresh_token)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def legacy_auth(hass):
|
||||||
|
"""Load legacy API password provider."""
|
||||||
|
prv = legacy_api_password.LegacyApiPasswordAuthProvider(
|
||||||
|
hass,
|
||||||
|
hass.auth._store,
|
||||||
|
{"type": "legacy_api_password", "api_password": "test-password"},
|
||||||
|
)
|
||||||
|
hass.auth._providers[(prv.type, prv.id)] = prv
|
||||||
|
return prv
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def local_auth(hass):
|
||||||
|
"""Load local auth provider."""
|
||||||
|
prv = homeassistant.HassAuthProvider(
|
||||||
|
hass, hass.auth._store, {"type": "homeassistant"}
|
||||||
|
)
|
||||||
|
hass.auth._providers[(prv.type, prv.id)] = prv
|
||||||
|
return prv
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def hass_client(hass, aiohttp_client, hass_access_token):
|
||||||
|
"""Return an authenticated HTTP client."""
|
||||||
|
|
||||||
|
async def auth_client():
|
||||||
|
"""Return an authenticated client."""
|
||||||
|
return await aiohttp_client(
|
||||||
|
hass.http.app, headers={"Authorization": f"Bearer {hass_access_token}"}
|
||||||
|
)
|
||||||
|
|
||||||
|
return auth_client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def hass_ws_client(aiohttp_client, hass_access_token, hass):
|
||||||
|
"""Websocket client fixture connected to websocket server."""
|
||||||
|
|
||||||
|
async def create_client(hass=hass, access_token=hass_access_token):
|
||||||
|
"""Create a websocket client."""
|
||||||
|
assert await async_setup_component(hass, "websocket_api", {})
|
||||||
|
|
||||||
|
client = await aiohttp_client(hass.http.app)
|
||||||
|
|
||||||
|
with patch("homeassistant.components.http.auth.setup_auth"):
|
||||||
|
websocket = await client.ws_connect(URL)
|
||||||
|
auth_resp = await websocket.receive_json()
|
||||||
|
assert auth_resp["type"] == TYPE_AUTH_REQUIRED
|
||||||
|
|
||||||
|
if access_token is None:
|
||||||
|
await websocket.send_json(
|
||||||
|
{"type": TYPE_AUTH, "access_token": "incorrect"}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await websocket.send_json(
|
||||||
|
{"type": TYPE_AUTH, "access_token": access_token}
|
||||||
|
)
|
||||||
|
|
||||||
|
auth_ok = await websocket.receive_json()
|
||||||
|
assert auth_ok["type"] == TYPE_AUTH_OK
|
||||||
|
|
||||||
|
# wrap in client
|
||||||
|
websocket.client = client
|
||||||
|
return websocket
|
||||||
|
|
||||||
|
return create_client
|
38
tests/ignore_uncaught_exceptions.py
Normal file
38
tests/ignore_uncaught_exceptions.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
"""List of modules that have uncaught exceptions today. Will be shrunk over time."""
|
||||||
|
IGNORE_UNCAUGHT_EXCEPTIONS = [
|
||||||
|
("tests.components.dyson.test_air_quality", "test_purecool_aiq_attributes"),
|
||||||
|
("tests.components.dyson.test_air_quality", "test_purecool_aiq_update_state"),
|
||||||
|
(
|
||||||
|
"tests.components.dyson.test_air_quality",
|
||||||
|
"test_purecool_component_setup_only_once",
|
||||||
|
),
|
||||||
|
("tests.components.dyson.test_air_quality", "test_purecool_aiq_without_discovery"),
|
||||||
|
(
|
||||||
|
"tests.components.dyson.test_air_quality",
|
||||||
|
"test_purecool_aiq_empty_environment_state",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tests.components.dyson.test_climate",
|
||||||
|
"test_setup_component_with_parent_discovery",
|
||||||
|
),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecoollink_attributes"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_turn_on"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_set_speed"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_turn_off"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_set_dyson_speed"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_oscillate"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_set_night_mode"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_set_auto_mode"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_set_angle"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_set_flow_direction_front"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_set_timer"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_update_state"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_update_state_filter_inv"),
|
||||||
|
("tests.components.dyson.test_fan", "test_purecool_component_setup_only_once"),
|
||||||
|
("tests.components.dyson.test_sensor", "test_purecool_component_setup_only_once"),
|
||||||
|
("tests.components.ios.test_init", "test_creating_entry_sets_up_sensor"),
|
||||||
|
("tests.components.ios.test_init", "test_not_configuring_ios_not_creates_entry"),
|
||||||
|
("tests.components.local_file.test_camera", "test_file_not_readable"),
|
||||||
|
]
|
||||||
|
|
||||||
|
IGNORE_UNCAUGHT_JSON_EXCEPTIONS = []
|
186
tests/test_config_flow.py
Normal file
186
tests/test_config_flow.py
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import librouteros
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant import data_entry_flow
|
||||||
|
from custom_components import mikrotik_router
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_USERNAME,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_SSL,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import MOCK_DATA
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
DEMO_USER_INPUT = {
|
||||||
|
CONF_NAME: "Home router",
|
||||||
|
CONF_HOST: "0.0.0.0",
|
||||||
|
CONF_USERNAME: "username",
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
CONF_PORT: 8278,
|
||||||
|
CONF_SSL: True,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEMO_CONFIG_ENTRY = {
|
||||||
|
CONF_NAME: "Home router",
|
||||||
|
CONF_HOST: "0.0.0.0",
|
||||||
|
CONF_USERNAME: "username",
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
CONF_PORT: 8278,
|
||||||
|
CONF_SSL: True,
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_SCAN_INTERVAL: 60,
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_UNIT_OF_MEASUREMENT: "Mbps",
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_TRACK_IFACE_CLIENTS: True,
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS: True,
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS_TIMEOUT: 180,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="api")
|
||||||
|
def mock_mikrotik_api():
|
||||||
|
"""Mock an api."""
|
||||||
|
with patch("librouteros.connect"):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="auth_error")
|
||||||
|
def mock_api_authentication_error():
|
||||||
|
"""Mock an api."""
|
||||||
|
with patch(
|
||||||
|
"librouteros.connect",
|
||||||
|
side_effect=librouteros.exceptions.TrapError("invalid user name or password"),
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="conn_error")
|
||||||
|
def mock_api_connection_error():
|
||||||
|
"""Mock an api."""
|
||||||
|
with patch(
|
||||||
|
"librouteros.connect", side_effect=librouteros.exceptions.ConnectionClosed
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import(hass, api):
|
||||||
|
"""Test import step."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
mikrotik_router.DOMAIN, context={"source": "import"}, data=MOCK_DATA
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["title"] == "Mikrotik"
|
||||||
|
assert result["data"][CONF_NAME] == "Mikrotik"
|
||||||
|
assert result["data"][CONF_HOST] == "10.0.0.1"
|
||||||
|
assert result["data"][CONF_USERNAME] == "admin"
|
||||||
|
assert result["data"][CONF_PASSWORD] == "admin"
|
||||||
|
assert result["data"][CONF_PORT] == 0
|
||||||
|
assert result["data"][CONF_SSL] is False
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_works(hass, api):
|
||||||
|
"""Test config flow."""
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
mikrotik_router.DOMAIN, context={"source": "user"}
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], user_input=DEMO_USER_INPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["title"] == "Home router"
|
||||||
|
assert result["data"][CONF_NAME] == "Home router"
|
||||||
|
assert result["data"][CONF_HOST] == "0.0.0.0"
|
||||||
|
assert result["data"][CONF_USERNAME] == "username"
|
||||||
|
assert result["data"][CONF_PASSWORD] == "password"
|
||||||
|
assert result["data"][CONF_PORT] == 8278
|
||||||
|
assert result["data"][CONF_SSL] is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options(hass):
|
||||||
|
"""Test updating options."""
|
||||||
|
entry = MockConfigEntry(domain=mikrotik_router.DOMAIN, data=DEMO_CONFIG_ENTRY)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "device_tracker"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_SCAN_INTERVAL: 30,
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_UNIT_OF_MEASUREMENT: "Kbps",
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_TRACK_IFACE_CLIENTS: True,
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS: False,
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS_TIMEOUT: 180,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["data"] == {
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_SCAN_INTERVAL: 30,
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_UNIT_OF_MEASUREMENT: "Kbps",
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_TRACK_IFACE_CLIENTS: True,
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS: False,
|
||||||
|
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS_TIMEOUT: 180,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_name_exists(hass, api):
|
||||||
|
"""Test name already configured."""
|
||||||
|
|
||||||
|
entry = MockConfigEntry(domain=mikrotik_router.DOMAIN, data=DEMO_CONFIG_ENTRY)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
user_input = DEMO_USER_INPUT.copy()
|
||||||
|
user_input[CONF_HOST] = "0.0.0.1"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
mikrotik_router.DOMAIN, context={"source": "user"}
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], user_input=user_input
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] == {"base": "name_exists"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_connection_error(hass, conn_error):
|
||||||
|
"""Test error when connection is unsuccessful."""
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
mikrotik_router.DOMAIN, context={"source": "user"}
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], user_input=DEMO_USER_INPUT
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] == {"host": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_wrong_credentials(hass, auth_error):
|
||||||
|
"""Test error when credentials are wrong."""
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
mikrotik_router.DOMAIN, context={"source": "user"}
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], user_input=DEMO_USER_INPUT
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] == {"host": "cannot_connect"}
|
Loading…
Add table
Add a link
Reference in a new issue