From e23d60d8952823ab2d822ffb8c3fbcc1804ae53d Mon Sep 17 00:00:00 2001 From: Rafal Zielinski Date: Thu, 2 Oct 2025 16:09:10 +0100 Subject: [PATCH] fix: some minor fixes Signed-off-by: Rafal Zielinski --- .gitea/workflows/release.yml | 13 ++++++++- .gitea/workflows/test.yml | 25 +++++++++++++++-- .gitignore | 34 +++++++++++++++++++++++ pytest.ini | 11 ++++++++ requirements_test.txt | 7 +++-- setup.cfg | 26 ++++++++++++++++++ tests/conftest.py | 18 ++++++++----- tests/test_config_flow.py | 4 +++ tests/test_init.py | 52 +++++++++++++++++++++++++++++------- tests/test_simple.py | 17 ++++++++++++ 10 files changed, 185 insertions(+), 22 deletions(-) create mode 100644 .gitignore create mode 100644 pytest.ini create mode 100644 setup.cfg create mode 100644 tests/test_simple.py diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 5c0f23a..1e5fcaa 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -15,6 +15,17 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.11' + cache: 'pip' + cache-dependency-path: 'requirements_test.txt' + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-release-${{ hashFiles('requirements_test.txt') }} + restore-keys: | + ${{ runner.os }}-pip-release- + ${{ runner.os }}-pip- - name: Install dependencies run: | @@ -23,7 +34,7 @@ jobs: - name: Run tests run: | - python -m pytest tests/ -v + python -m pytest tests/ -v --asyncio-mode=auto - name: Update version in manifest run: | diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 79aba02..12bc019 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -21,6 +21,17 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: 'requirements_test.txt' + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements_test.txt') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.python-version }}- + ${{ runner.os }}-pip- - name: Install dependencies run: | @@ -29,7 +40,7 @@ jobs: - name: Run pytest run: | - python -m pytest tests/ -v --tb=short + python -m pytest tests/ -v --tb=short --asyncio-mode=auto - name: Validate manifest run: | @@ -45,6 +56,16 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.11' + cache: 'pip' + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-lint-${{ hashFiles('**/requirements*.txt') }} + restore-keys: | + ${{ runner.os }}-pip-lint- + ${{ runner.os }}-pip- - name: Install dependencies run: | @@ -65,7 +86,7 @@ jobs: - name: Run mypy run: | - mypy custom_components/ + mypy custom_components/ --ignore-missing-imports continue-on-error: true hacs: diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93d39d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +.pytest_cache/ +.coverage +htmlcov/ +.tox/ +.mypy_cache/ +.DS_Store \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..78d113c --- /dev/null +++ b/pytest.ini @@ -0,0 +1,11 @@ +[tool:pytest] +asyncio_mode = auto +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = -v --tb=short --strict-markers --strict-config +markers = + asyncio: mark test as async + integration: mark test as integration test + unit: mark test as unit test diff --git a/requirements_test.txt b/requirements_test.txt index 58866f3..ec8b641 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,8 @@ pytest>=7.4.0 -pytest-homeassistant-custom-component>=0.13.0 pytest-asyncio>=0.21.1 +pytest-homeassistant-custom-component>=0.13.0 aiohttp>=3.8.0 -voluptuous>=0.13.1 \ No newline at end of file +voluptuous>=0.13.1 +homeassistant>=2023.5.0 +async-timeout>=4.0.0 +aiofiles>=22.1.0 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..1e349c0 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,26 @@ +[tool:pytest] +asyncio_mode = auto +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = -v --tb=short --strict-markers --strict-config +markers = + asyncio: mark test as async + integration: mark test as integration test + unit: mark test as unit test + +[flake8] +max-line-length = 88 +extend-ignore = E203, W503 +exclude = .git,__pycache__,.pytest_cache,.venv,venv,env + +[mypy] +python_version = 3.13 +ignore_missing_imports = True +follow_imports = silent +warn_redundant_casts = True +warn_unused_ignores = True +disallow_any_generics = True +check_untyped_defs = True +no_implicit_reexport = True diff --git a/tests/conftest.py b/tests/conftest.py index a6f1d8e..21ae336 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ import pytest from unittest.mock import AsyncMock, MagicMock, patch from homeassistant.core import HomeAssistant -from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.config_entries import ConfigEntry from custom_components.adguard_control_hub.const import ( CONF_HOST, @@ -50,8 +50,8 @@ def mock_adguard_api(): @pytest.fixture -def mock_config_entry(): - """Mock config entry.""" +def mock_config_entry_data(): + """Mock config entry data.""" return { CONF_HOST: "192.168.1.100", CONF_PORT: 3000, @@ -63,7 +63,11 @@ def mock_config_entry(): @pytest.fixture -def mock_setup_entry(): - """Mock setup entry.""" - with patch("custom_components.adguard_control_hub.AdGuardHomeAPI") as mock_api_class: - yield mock_api_class +def mock_config_entry(mock_config_entry_data): + """Mock config entry.""" + entry = MagicMock(spec=ConfigEntry) + entry.data = mock_config_entry_data + entry.entry_id = "test_entry_id" + entry.title = "AdGuard Home (192.168.1.100)" + entry.domain = DOMAIN + return entry diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 8c3361b..b0cf29d 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -13,6 +13,7 @@ from custom_components.adguard_control_hub.config_flow import ( from custom_components.adguard_control_hub.const import CONF_SSL, CONF_VERIFY_SSL, DOMAIN +@pytest.mark.asyncio async def test_form(hass: HomeAssistant) -> None: """Test we get the form.""" result = await hass.config_entries.flow.async_init( @@ -22,6 +23,7 @@ async def test_form(hass: HomeAssistant) -> None: assert result["errors"] is None +@pytest.mark.asyncio async def test_form_valid_connection(hass: HomeAssistant, mock_adguard_api) -> None: """Test we get the form with valid connection.""" with patch( @@ -58,6 +60,7 @@ async def test_form_valid_connection(hass: HomeAssistant, mock_adguard_api) -> N } +@pytest.mark.asyncio async def test_form_cannot_connect(hass: HomeAssistant) -> None: """Test we handle cannot connect error.""" with patch( @@ -80,6 +83,7 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "cannot_connect"} +@pytest.mark.asyncio async def test_form_invalid_auth(hass: HomeAssistant) -> None: """Test we handle invalid auth error.""" with patch( diff --git a/tests/test_init.py b/tests/test_init.py index 5abf37e..e83569f 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,5 +1,5 @@ """Test AdGuard Control Hub initialization.""" -from unittest.mock import patch +from unittest.mock import patch, AsyncMock import pytest from homeassistant.config_entries import ConfigEntryState @@ -9,39 +9,71 @@ from homeassistant.core import HomeAssistant from custom_components.adguard_control_hub.const import DOMAIN -async def test_setup_entry(hass: HomeAssistant, mock_config_entry, mock_adguard_api) -> None: +@pytest.mark.asyncio +async def test_setup_entry(hass: HomeAssistant, mock_config_entry_data, mock_adguard_api) -> None: """Test successful setup of entry.""" with patch( "custom_components.adguard_control_hub.AdGuardHomeAPI" ) as mock_api_class: mock_api_class.return_value = mock_adguard_api - entry = hass.config_entries.async_add(mock_config_entry) + # Create a mock config entry + from homeassistant.config_entries import ConfigEntry + entry = ConfigEntry( + version=1, + minor_version=1, + domain=DOMAIN, + title="Test AdGuard", + data=mock_config_entry_data, + options={}, + source="test", + unique_id="test_unique_id", + ) + + # Add entry to hass + hass.config_entries._entries[entry.entry_id] = entry with patch("custom_components.adguard_control_hub.PLATFORMS", []): - await hass.config_entries.async_setup(entry.entry_id) + result = await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert entry.state == ConfigEntryState.LOADED + assert result is True assert DOMAIN in hass.data -async def test_unload_entry(hass: HomeAssistant, mock_config_entry, mock_adguard_api) -> None: +@pytest.mark.asyncio +async def test_unload_entry(hass: HomeAssistant, mock_config_entry_data, mock_adguard_api) -> None: """Test successful unload of entry.""" with patch( "custom_components.adguard_control_hub.AdGuardHomeAPI" ) as mock_api_class: mock_api_class.return_value = mock_adguard_api - entry = hass.config_entries.async_add(mock_config_entry) + # Create a mock config entry + from homeassistant.config_entries import ConfigEntry + entry = ConfigEntry( + version=1, + minor_version=1, + domain=DOMAIN, + title="Test AdGuard", + data=mock_config_entry_data, + options={}, + source="test", + unique_id="test_unique_id", + ) + + # Add entry to hass + hass.config_entries._entries[entry.entry_id] = entry with patch("custom_components.adguard_control_hub.PLATFORMS", []): + # Setup first await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert entry.state == ConfigEntryState.LOADED + assert DOMAIN in hass.data - await hass.config_entries.async_unload(entry.entry_id) + # Then unload + result = await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() - assert entry.state == ConfigEntryState.NOT_LOADED + assert result is True diff --git a/tests/test_simple.py b/tests/test_simple.py new file mode 100644 index 0000000..9df23a5 --- /dev/null +++ b/tests/test_simple.py @@ -0,0 +1,17 @@ +"""Simple test to verify pytest configuration.""" +import pytest + + +@pytest.mark.asyncio +async def test_async_works(): + """Test that async functions work with pytest.""" + async def simple_async_function(): + return True + + result = await simple_async_function() + assert result is True + + +def test_sync_works(): + """Test that sync functions work with pytest.""" + assert True is True