refactor: Refactoring most of the project
Some checks failed
🧪 Integration Testing / 🔧 Test Integration (2025.9.4, 3.13) (push) Failing after 24s
Some checks failed
🧪 Integration Testing / 🔧 Test Integration (2025.9.4, 3.13) (push) Failing after 24s
Signed-off-by: Rafal Zielinski <sq4ind@gmail.com>
This commit is contained in:
@@ -1,55 +0,0 @@
|
|||||||
name: 🛡️ Code Quality & Security Check
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main, develop ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
code-quality:
|
|
||||||
name: 🔍 Code Quality Analysis
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 📥 Checkout Code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: 🐍 Set up Python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: '3.11'
|
|
||||||
|
|
||||||
- name: 📦 Install Dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install flake8 black isort mypy bandit safety
|
|
||||||
pip install homeassistant==2025.9.4
|
|
||||||
pip install -r requirements-dev.txt || echo "No dev requirements found"
|
|
||||||
|
|
||||||
- name: 🎨 Check Code Formatting (Black)
|
|
||||||
run: |
|
|
||||||
black --check --diff custom_components/
|
|
||||||
|
|
||||||
- name: 📊 Import Sorting (isort)
|
|
||||||
run: |
|
|
||||||
isort --check-only --diff custom_components/
|
|
||||||
|
|
||||||
- name: 🔍 Linting (Flake8)
|
|
||||||
run: |
|
|
||||||
flake8 custom_components/ --count --select=E9,F63,F7,F82 --show-source --statistics
|
|
||||||
flake8 custom_components/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
|
||||||
|
|
||||||
- name: 🔒 Security Scan (Bandit)
|
|
||||||
run: |
|
|
||||||
bandit -r custom_components/ -f json -o bandit-report.json || true
|
|
||||||
bandit -r custom_components/ --severity-level medium
|
|
||||||
|
|
||||||
- name: 🛡️ Dependency Security Check (Safety)
|
|
||||||
run: |
|
|
||||||
safety check --json --output safety-report.json || true
|
|
||||||
safety check
|
|
||||||
|
|
||||||
- name: 🏷️ Type Checking (MyPy)
|
|
||||||
run: |
|
|
||||||
mypy custom_components/ --ignore-missing-imports --no-strict-optional
|
|
@@ -1,31 +0,0 @@
|
|||||||
name: 🚀 Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: 📦 Create Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 📥 Checkout Code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: 🏷️ Get Version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
VERSION=${GITHUB_REF#refs/tags/v}
|
|
||||||
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
|
|
||||||
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: 📦 Create Archive
|
|
||||||
run: |
|
|
||||||
cd custom_components
|
|
||||||
zip -r ../adguard-control-hub-${{ steps.version.outputs.VERSION }}.zip adguard_hub/
|
|
||||||
|
|
||||||
- name: 🚀 Create Release
|
|
||||||
run: |
|
|
||||||
echo "Creating release for ${{ steps.version.outputs.TAG }}"
|
|
41
.gitignore
vendored
41
.gitignore
vendored
@@ -1,31 +1,18 @@
|
|||||||
# Python
|
.venv
|
||||||
|
venv/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
*.pyc
|
*.pyc
|
||||||
|
*.pyo
|
||||||
# Home Assistant
|
*.pyd
|
||||||
*.log
|
.Python
|
||||||
*.db
|
|
||||||
*.db-journal
|
|
||||||
.HA_VERSION
|
|
||||||
|
|
||||||
# IDE
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
|
|
||||||
# OS
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
.directory
|
|
||||||
|
|
||||||
# Testing
|
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.coverage
|
.coverage
|
||||||
htmlcov/
|
.mypy_cache/
|
||||||
|
*.egg-info/
|
||||||
# Virtual environments
|
dist/
|
||||||
venv/
|
build/
|
||||||
env/
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.log
|
||||||
|
.env
|
34
README.md
34
README.md
@@ -1,13 +1,7 @@
|
|||||||
# 🛡️ AdGuard Control Hub
|
# 🛡️ AdGuard Control Hub
|
||||||
|
|
||||||
[](https://github.com/custom-components/hacs)
|
|
||||||
[](https://git.sq4ind.eu/sq4ind/adguard-control-hub/releases)
|
|
||||||
[](LICENSE)
|
|
||||||
|
|
||||||
**The ultimate Home Assistant integration for AdGuard Home**
|
**The ultimate Home Assistant integration for AdGuard Home**
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
### 🎯 Smart Client Management
|
### 🎯 Smart Client Management
|
||||||
@@ -27,16 +21,12 @@
|
|||||||
- Automation-friendly services for advanced workflows
|
- Automation-friendly services for advanced workflows
|
||||||
- Real-time DNS and blocking statistics
|
- Real-time DNS and blocking statistics
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Installation
|
## 📦 Installation
|
||||||
|
|
||||||
### 🔧 Method 1: HACS (Recommended)
|
### 🔧 Method 1: HACS (Recommended)
|
||||||
|
|
||||||
1. Open Home Assistant and go to **HACS > Integrations**
|
1. Open Home Assistant and go to **HACS > Integrations**
|
||||||
2. Click menu (⋮) → **Custom repositories**
|
2. Click menu (⋮) → **Custom repositories**
|
||||||
3. Add repository URL:
|
3. Add repository URL: `https://git.sq4ind.eu/sq4ind/adguard-control-hub`
|
||||||
`https://git.sq4ind.eu/sq4ind/adguard-control-hub`
|
|
||||||
4. Set category to **Integration**, click **Add**
|
4. Set category to **Integration**, click **Add**
|
||||||
5. Search for **AdGuard Control Hub**
|
5. Search for **AdGuard Control Hub**
|
||||||
6. Click **Install**, then restart Home Assistant
|
6. Click **Install**, then restart Home Assistant
|
||||||
@@ -44,24 +34,18 @@
|
|||||||
8. Search and select **AdGuard Control Hub**, enter your AdGuard Home details
|
8. Search and select **AdGuard Control Hub**, enter your AdGuard Home details
|
||||||
|
|
||||||
### 🛠️ Method 2: Manual Installation
|
### 🛠️ Method 2: Manual Installation
|
||||||
|
|
||||||
1. Download the latest release zip from your Gitea repository
|
1. Download the latest release zip from your Gitea repository
|
||||||
2. Extract `custom_components/adguard_hub` into your Home Assistant config directory
|
2. Extract `custom_components/adguard_hub` into your Home Assistant config directory
|
||||||
3. Restart Home Assistant
|
3. Restart Home Assistant
|
||||||
4. Add integration via UI as per above
|
4. Add integration via UI as per above
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚙️ Configuration
|
## ⚙️ Configuration
|
||||||
|
|
||||||
- **Host**: IP or hostname of your AdGuard Home
|
- **Host**: IP or hostname of your AdGuard Home
|
||||||
- **Port**: Default 3000 unless customized
|
- **Port**: Default 3000 unless customized
|
||||||
- **Username & Password**: Admin credentials for AdGuard Home
|
- **Username & Password**: Admin credentials for AdGuard Home
|
||||||
- **SSL**: Enable if AdGuard Home runs HTTPS
|
- **SSL**: Enable if AdGuard Home runs HTTPS
|
||||||
- **Verify SSL**: Disable for self-signed certificates
|
- **Verify SSL**: Disable for self-signed certificates
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎬 Use Cases & Examples
|
## 🎬 Use Cases & Examples
|
||||||
|
|
||||||
### Parental Controls - Kids Bedtime Automation
|
### Parental Controls - Kids Bedtime Automation
|
||||||
@@ -106,8 +90,6 @@ data:
|
|||||||
clients: ["all"]
|
clients: ["all"]
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📱 Dashboard Examples
|
## 📱 Dashboard Examples
|
||||||
|
|
||||||
**Main Control Panel:**
|
**Main Control Panel:**
|
||||||
@@ -143,24 +125,12 @@ tap_action:
|
|||||||
clients: ["all"]
|
clients: ["all"]
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🤝 Support & Contribution
|
## 🤝 Support & Contribution
|
||||||
|
|
||||||
- Full documentation and setup examples in the repository wiki.
|
- Full documentation and setup examples in the repository wiki.
|
||||||
- Report issues or request features via the repository's issue tracker.
|
- Report issues or request features via the repository's issue tracker.
|
||||||
- Contributions welcome—please read the contribution guidelines.
|
- Contributions welcome—please read the contribution guidelines.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 License
|
## 📄 License
|
||||||
|
This project is licensed under the MIT License. See the LICENSE file for details.
|
||||||
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Made with ❤️ and professional care, so you control your AdGuard Home network integration!
|
Made with ❤️ and professional care, so you control your AdGuard Home network integration!
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[](https://github.com/custom-components/hacs) | [Releases](https://git.sq4ind.eu/sq4ind/adguard-control-hub/releases) | [Issues](https://git.sq4ind.eu/sq4ind/adguard-control-hub/issues) | [License](LICENSE)
|
|
1
custom_components/__init__.py
Normal file
1
custom_components/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Custom components for Home Assistant."""
|
@@ -109,7 +109,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
"""Handle a config flow for AdGuard Control Hub."""
|
"""Handle a config flow for AdGuard Control Hub."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
MINOR_VERSION = 1
|
||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
self, user_input: Optional[Dict[str, Any]] = None
|
self, user_input: Optional[Dict[str, Any]] = None
|
||||||
|
@@ -182,6 +182,7 @@ class AdGuardControlHubServices:
|
|||||||
]
|
]
|
||||||
|
|
||||||
for service in services:
|
for service in services:
|
||||||
|
if self.hass.services.has_service(DOMAIN, service):
|
||||||
self.hass.services.remove(DOMAIN, service)
|
self.hass.services.remove(DOMAIN, service)
|
||||||
|
|
||||||
def _get_api_for_entry(self, entry_id: str) -> AdGuardHomeAPI:
|
def _get_api_for_entry(self, entry_id: str) -> AdGuardHomeAPI:
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"content_in_root": false,
|
"content_in_root": false,
|
||||||
"filename": "adguard_hub",
|
"filename": "adguard_hub",
|
||||||
"country": ["US", "GB", "CA", "AU", "DE", "FR", "NL", "SE", "NO", "DK"],
|
"country": ["US", "GB", "CA", "AU", "DE", "FR", "NL", "SE", "NO", "DK"],
|
||||||
"homeassistant": "2023.1.0",
|
"homeassistant": "2025.1.0",
|
||||||
"render_readme": true,
|
"render_readme": true,
|
||||||
"iot_class": "Local Polling"
|
"iot_class": "Local Polling"
|
||||||
}
|
}
|
70
info.md
70
info.md
@@ -1,63 +1,15 @@
|
|||||||
# 🛡️ AdGuard Control Hub
|
# AdGuard Control Hub
|
||||||
|
|
||||||
**Transform your AdGuard Home into a smart network management powerhouse!**
|
The complete Home Assistant integration for AdGuard Home network management.
|
||||||
|
|
||||||
## 🎯 What Makes This Special?
|
## Features
|
||||||
|
- Smart client management and discovery
|
||||||
|
- Granular service blocking controls
|
||||||
|
- Emergency unblock capabilities
|
||||||
|
- Real-time statistics and monitoring
|
||||||
|
- Rich automation services
|
||||||
|
|
||||||
Unlike the basic AdGuard integration, **AdGuard Control Hub** gives you complete control over every aspect of your network filtering:
|
## Installation
|
||||||
|
Install via HACS or manually extract to `custom_components/adguard_hub/`
|
||||||
|
|
||||||
### 🏠 **Smart Home Integration**
|
Restart Home Assistant and add via Integrations UI.
|
||||||
- **Individual Device Control**: Every AdGuard client becomes a Home Assistant switch
|
|
||||||
- **Service-Level Blocking**: Block YouTube on kids' tablets while allowing it on work computers
|
|
||||||
- **Automated Parenting**: Bedtime routines that automatically restrict internet access
|
|
||||||
- **Work Productivity**: Focus modes that eliminate distracting websites during work hours
|
|
||||||
|
|
||||||
### 🎛️ **Rich Dashboard Controls**
|
|
||||||
- **One-Click Emergency Override**: Temporary unblock for urgent situations
|
|
||||||
- **Family Management Panel**: Control all kids' devices from a single card
|
|
||||||
- **Guest Network Controls**: Different rules for visitor devices
|
|
||||||
- **Real-Time Statistics**: See exactly what's being blocked and when
|
|
||||||
|
|
||||||
### 🤖 **Automation Paradise**
|
|
||||||
- **Time-Based Rules**: Different restrictions for school nights vs weekends
|
|
||||||
- **Presence Detection**: Automatically adjust rules when people arrive/leave
|
|
||||||
- **Bulk Operations**: Update multiple devices with pattern matching
|
|
||||||
- **Custom Schedules**: Per-device blocking schedules with precise time control
|
|
||||||
|
|
||||||
## 🛠️ **What You Get**
|
|
||||||
|
|
||||||
### **Entities Created**
|
|
||||||
- `switch.adguard_protection` - Global protection toggle
|
|
||||||
- `switch.adguard_client_*` - Individual device protection
|
|
||||||
- `switch.adguard_client_*_service_*` - Per-device service blocking
|
|
||||||
- `sensor.adguard_*` - DNS statistics and monitoring
|
|
||||||
- `binary_sensor.adguard_*` - Status indicators
|
|
||||||
|
|
||||||
### **Services Available**
|
|
||||||
- `adguard_hub.add_client` - Add new devices
|
|
||||||
- `adguard_hub.block_services` - Block specific services
|
|
||||||
- `adguard_hub.bulk_update_clients` - Manage multiple devices
|
|
||||||
- `adguard_hub.emergency_unblock` - Temporary access override
|
|
||||||
|
|
||||||
## 🎬 **Perfect For**
|
|
||||||
|
|
||||||
- **Parents** wanting automated screen time controls
|
|
||||||
- **Remote Workers** needing focus mode automation
|
|
||||||
- **Tech Enthusiasts** wanting complete network visibility
|
|
||||||
- **Families** needing different rules for different people
|
|
||||||
- **Anyone** who wants their network to be truly "smart"
|
|
||||||
|
|
||||||
## 📋 **Requirements**
|
|
||||||
|
|
||||||
- Home Assistant 2023.1+
|
|
||||||
- AdGuard Home with API access enabled
|
|
||||||
- Admin credentials for your AdGuard Home instance
|
|
||||||
|
|
||||||
## 🚀 **Quick Start**
|
|
||||||
|
|
||||||
1. Install via HACS or manually
|
|
||||||
2. Add integration: Settings → Devices & Services → Add Integration
|
|
||||||
3. Enter your AdGuard Home IP, port, username, and password
|
|
||||||
4. Watch as all your devices appear as controllable entities!
|
|
||||||
|
|
||||||
**Ready to take control of your network? Let's get started! 🚀**
|
|
@@ -1,6 +1,6 @@
|
|||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 127
|
line-length = 127
|
||||||
target-version = ['py311']
|
target-version = ['py313']
|
||||||
include = '\.pyi?$'
|
include = '\.pyi?$'
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
@@ -13,7 +13,7 @@ use_parentheses = true
|
|||||||
ensure_newline_before_comments = true
|
ensure_newline_before_comments = true
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
python_version = "3.11"
|
python_version = "3.13"
|
||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unused_configs = true
|
warn_unused_configs = true
|
||||||
disallow_untyped_defs = true
|
disallow_untyped_defs = true
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
# Development dependencies
|
# Development dependencies
|
||||||
black==23.12.0
|
black==24.3.0
|
||||||
flake8==6.1.0
|
flake8==7.0.0
|
||||||
isort==5.13.2
|
isort==5.13.2
|
||||||
mypy==1.8.0
|
mypy==1.9.0
|
||||||
bandit==1.7.5
|
bandit==1.7.7
|
||||||
safety==2.3.5
|
safety==3.1.0
|
||||||
pytest==7.4.3
|
pytest==8.1.1
|
||||||
pytest-homeassistant-custom-component==0.13.104
|
pytest-homeassistant-custom-component==0.13.281
|
||||||
pytest-cov==4.1.0
|
pytest-cov==5.0.0
|
||||||
|
|
||||||
# Home Assistant testing
|
# Home Assistant testing
|
||||||
homeassistant==2023.12.0
|
homeassistant==2025.9.4
|
@@ -1 +1 @@
|
|||||||
"""Tests for AdGuard Control Hub."""
|
"""Tests for AdGuard Control Hub integration."""
|
@@ -1,3 +1,4 @@
|
|||||||
|
"""Test configuration and fixtures."""
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@@ -5,3 +6,11 @@ import pytest
|
|||||||
def auto_enable_custom_integrations(enable_custom_integrations):
|
def auto_enable_custom_integrations(enable_custom_integrations):
|
||||||
"""Enable custom integrations for all tests."""
|
"""Enable custom integrations for all tests."""
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_platform_setup():
|
||||||
|
"""Mock platform setup to avoid actual platform loading."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
with patch("homeassistant.config_entries.ConfigEntry.async_forward_entry_setups"):
|
||||||
|
yield
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
"""Test API functionality."""
|
"""Test API functionality."""
|
||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
from custom_components.adguard_hub.api import AdGuardHomeAPI
|
from custom_components.adguard_hub.api import AdGuardHomeAPI
|
||||||
|
|
||||||
|
|
||||||
@@ -13,10 +13,19 @@ def mock_session():
|
|||||||
response.json = AsyncMock(return_value={"status": "ok"})
|
response.json = AsyncMock(return_value={"status": "ok"})
|
||||||
response.status = 200
|
response.status = 200
|
||||||
response.content_length = 100
|
response.content_length = 100
|
||||||
session.request = AsyncMock(return_value=response)
|
|
||||||
|
# Create async context manager for session.request
|
||||||
|
async def mock_request(*args, **kwargs):
|
||||||
|
return response
|
||||||
|
|
||||||
|
session.request = MagicMock()
|
||||||
|
session.request.return_value.__aenter__ = AsyncMock(return_value=response)
|
||||||
|
session.request.return_value.__aexit__ = AsyncMock(return_value=None)
|
||||||
|
|
||||||
return session
|
return session
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
async def test_api_connection(mock_session):
|
async def test_api_connection(mock_session):
|
||||||
"""Test API connection."""
|
"""Test API connection."""
|
||||||
api = AdGuardHomeAPI(
|
api = AdGuardHomeAPI(
|
||||||
@@ -31,6 +40,7 @@ async def test_api_connection(mock_session):
|
|||||||
assert result is True
|
assert result is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
async def test_api_get_status(mock_session):
|
async def test_api_get_status(mock_session):
|
||||||
"""Test getting status."""
|
"""Test getting status."""
|
||||||
api = AdGuardHomeAPI(
|
api = AdGuardHomeAPI(
|
||||||
@@ -41,3 +51,33 @@ async def test_api_get_status(mock_session):
|
|||||||
|
|
||||||
status = await api.get_status()
|
status = await api.get_status()
|
||||||
assert status == {"status": "ok"}
|
assert status == {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_context_manager():
|
||||||
|
"""Test API as async context manager."""
|
||||||
|
async with AdGuardHomeAPI(host="test-host", port=3000) as api:
|
||||||
|
assert api is not None
|
||||||
|
assert api.host == "test-host"
|
||||||
|
assert api.port == 3000
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_error_handling():
|
||||||
|
"""Test API error handling."""
|
||||||
|
from custom_components.adguard_hub.api import AdGuardConnectionError
|
||||||
|
|
||||||
|
# Test with a session that raises an exception
|
||||||
|
session = MagicMock()
|
||||||
|
session.request = MagicMock()
|
||||||
|
session.request.return_value.__aenter__ = AsyncMock(side_effect=Exception("Connection error"))
|
||||||
|
session.request.return_value.__aexit__ = AsyncMock(return_value=None)
|
||||||
|
|
||||||
|
api = AdGuardHomeAPI(
|
||||||
|
host="test-host",
|
||||||
|
port=3000,
|
||||||
|
session=session
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(Exception): # Should raise AdGuardHomeError
|
||||||
|
await api.get_status()
|
||||||
|
@@ -1,19 +1,38 @@
|
|||||||
"""Test config flow for AdGuard Control Hub."""
|
"""Test config flow for AdGuard Control Hub."""
|
||||||
import pytest
|
import pytest
|
||||||
from homeassistant import config_entries, setup
|
from unittest.mock import AsyncMock, patch
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
|
||||||
from custom_components.adguard_hub.const import DOMAIN
|
from custom_components.adguard_hub.const import DOMAIN
|
||||||
|
|
||||||
async def test_config_flow_success(hass):
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_config_flow_success(hass: HomeAssistant):
|
||||||
"""Test successful config flow."""
|
"""Test successful config flow."""
|
||||||
|
with patch("custom_components.adguard_hub.config_flow.validate_input") as mock_validate:
|
||||||
|
mock_validate.return_value = {
|
||||||
|
"title": "AdGuard Control Hub (192.168.1.100)",
|
||||||
|
"host": "192.168.1.100",
|
||||||
|
}
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == FlowResultType.FORM
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
async def test_config_flow_cannot_connect(hass):
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_config_flow_cannot_connect(hass: HomeAssistant):
|
||||||
"""Test config flow with connection error."""
|
"""Test config flow with connection error."""
|
||||||
|
from custom_components.adguard_hub.config_flow import CannotConnect
|
||||||
|
|
||||||
|
with patch("custom_components.adguard_hub.config_flow.validate_input") as mock_validate:
|
||||||
|
mock_validate.side_effect = CannotConnect("Connection failed")
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_USER},
|
context={"source": config_entries.SOURCE_USER},
|
||||||
@@ -25,5 +44,28 @@ async def test_config_flow_cannot_connect(hass):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == FlowResultType.FORM
|
||||||
assert result["errors"]["base"] == "cannot_connect"
|
assert result["errors"]["base"] == "cannot_connect"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_config_flow_invalid_auth(hass: HomeAssistant):
|
||||||
|
"""Test config flow with authentication error."""
|
||||||
|
from custom_components.adguard_hub.config_flow import InvalidAuth
|
||||||
|
|
||||||
|
with patch("custom_components.adguard_hub.config_flow.validate_input") as mock_validate:
|
||||||
|
mock_validate.side_effect = InvalidAuth("Auth failed")
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_USER},
|
||||||
|
data={
|
||||||
|
"host": "192.168.1.100",
|
||||||
|
"port": 3000,
|
||||||
|
"username": "wrong",
|
||||||
|
"password": "wrong",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["errors"]["base"] == "invalid_auth"
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry, SOURCE_USER
|
||||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
||||||
|
|
||||||
from custom_components.adguard_hub import async_setup_entry, async_unload_entry
|
from custom_components.adguard_hub import async_setup_entry, async_unload_entry
|
||||||
@@ -12,9 +12,10 @@ from custom_components.adguard_hub.const import DOMAIN
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_config_entry():
|
def mock_config_entry():
|
||||||
"""Mock config entry."""
|
"""Mock config entry compatible with HA 2025.9.4."""
|
||||||
return ConfigEntry(
|
return ConfigEntry(
|
||||||
version=1,
|
version=1,
|
||||||
|
minor_version=1,
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
title="Test AdGuard",
|
title="Test AdGuard",
|
||||||
data={
|
data={
|
||||||
@@ -23,8 +24,12 @@ def mock_config_entry():
|
|||||||
CONF_USERNAME: "admin",
|
CONF_USERNAME: "admin",
|
||||||
CONF_PASSWORD: "password",
|
CONF_PASSWORD: "password",
|
||||||
},
|
},
|
||||||
source="user",
|
options={},
|
||||||
|
source=SOURCE_USER,
|
||||||
entry_id="test_entry_id",
|
entry_id="test_entry_id",
|
||||||
|
unique_id="192.168.1.100:3000",
|
||||||
|
discovery_keys=set(),
|
||||||
|
subentries_data={},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -145,11 +150,11 @@ async def test_api_error_handling(mock_api):
|
|||||||
await mock_api.get_clients()
|
await mock_api.get_clients()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
def test_services_registration(hass: HomeAssistant):
|
||||||
async def test_services_registration(hass: HomeAssistant):
|
|
||||||
"""Test that services are properly registered."""
|
"""Test that services are properly registered."""
|
||||||
from custom_components.adguard_hub.services import AdGuardControlHubServices
|
from custom_components.adguard_hub.services import AdGuardControlHubServices
|
||||||
|
|
||||||
|
# Create services without running inside an existing event loop
|
||||||
services = AdGuardControlHubServices(hass)
|
services = AdGuardControlHubServices(hass)
|
||||||
services.register_services()
|
services.register_services()
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user