Skip to content

Commit

Permalink
Merge pull request #13 from patman15/tests
Browse files Browse the repository at this point in the history
Tests
  • Loading branch information
patman15 committed May 27, 2024
2 parents b089ae5 + 4652c7a commit 3aec8b2
Show file tree
Hide file tree
Showing 21 changed files with 1,349 additions and 11 deletions.
11 changes: 5 additions & 6 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
name: "Lint"
name: "Lint the code"

on:
push:
branches:
- "main"
pull_request:
branches:
- "main"

workflow_dispatch:
schedule:
- cron: '0 5 * * 6'

jobs:
ruff:
name: "Ruff"
Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Run tests for integrations

on:
push:
branches:
- main
pull_request:
workflow_dispatch:
schedule:
- cron: '0 5 * * 6'

jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- name: "Checkout the repository"
uses: "actions/checkout@v4"

- name: "Set up Python"
uses: actions/setup-python@main
with:
python-version: "3.11"
cache: "pip"

- name: Install dependencies
run: pip install -r requirements_test.txt

- name: Run tests and collect coverage
run: pytest --cov-report term-missing --cov-fail-under=100
3 changes: 2 additions & 1 deletion .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ name: Validate with hassfest and HACS
on:
push:
pull_request:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
- cron: '0 5 * * 6'

jobs:
validate:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
custom_components/bms_ble/plugins/dummy_bms.py
#custom_components/bms_ble/plugins/dummy_bms.py
1 change: 1 addition & 0 deletions custom_components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Custom component."""
4 changes: 2 additions & 2 deletions custom_components/bms_ble/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
if config_entry.version == 0:
bms_type = config_entry.data["type"]
if bms_type == "OGTBms":
new = {"type": "homeassistant.components.bms_ble.plugins.ogt_bms"}
new = {"type": "custom_components.bms_ble.plugins.ogt_bms"}
elif bms_type == "DalyBms":
new = {"type": "homeassistant.components.bms_ble.plugins.daly_bms"}
new = {"type": "custom_components.bms_ble.plugins.daly_bms"}
else:
LOGGER.debug("Entry: %s", config_entry.data)
LOGGER.error(
Expand Down
54 changes: 54 additions & 0 deletions custom_components/bms_ble/plugins/dummy_bms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Module to support Dummy BMS."""

import logging
from typing import Any

from bleak.backends.device import BLEDevice

from ..const import (
ATTR_BATTERY_CHARGING,
# ATTR_BATTERY_LEVEL,
ATTR_CURRENT,
# ATTR_CYCLE_CAP,
# ATTR_CYCLE_CHRG,
# ATTR_CYCLES,
ATTR_POWER,
# ATTR_RUNTIME,
# ATTR_TEMPERATURE,
ATTR_VOLTAGE,
)
from .basebms import BaseBMS

LOGGER = logging.getLogger(__name__)


class BMS(BaseBMS):
"""Dummy battery class implementation."""

def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
"""Initialize BMS."""
LOGGER.debug("%s init(), BT address: %s", self.device_id(), ble_device.address)

@staticmethod
def matcher_dict_list() -> list[dict[str, Any]]:
"""Provide BluetoothMatcher definition."""
return [{"local_name": "dummy", "connectable": True}]

@staticmethod
def device_info() -> dict[str, str]:
"""Return device information for the battery management system."""
return {"manufacturer": "Dummy Manufacturer", "model": "dummy model"}

async def disconnect(self) -> None:
"""Disconnect connection to BMS if active."""

async def async_update(self) -> dict[str, int | float | bool]:
"""Update battery status information."""
data = {
ATTR_VOLTAGE: 12,
ATTR_CURRENT: 1.5,
} # set fixed values for dummy battery
self.calc_values(
data, {ATTR_POWER, ATTR_BATTERY_CHARGING}
) # calculate further values from previously set ones
return data
2 changes: 1 addition & 1 deletion hacs.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "BLE Battery Management System (BMS)",
"homeassistant": "2023.8.0",
"homeassistant": "2024.3.0",
"render_readme": true
}
16 changes: 16 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# pyproject.toml

#[tool.setuptools.packages.find]
#where = ["custom_components/"]
#include = ["bms_ble"]

[tool.pytest.ini_options]
minversion = "6.0"
pythonpath = [
".",
"custom_components.bms_ble",
]
testpaths = [
"tests",
]
asyncio_mode = "auto"
24 changes: 24 additions & 0 deletions requirements_test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
pip>=21.3.1
homeassistant==2024.3.0
wheel
home-assistant-bluetooth
habluetooth==2.4.0
bluetooth-adapters
pytest>=8.0.2
pytest-cov>=4.0.0
pytest-socket>=0.5.0
pytest-asyncio
sqlalchemy
freezegun
requests-mock
syrupy>=4.1.0
aiohttp
aiohttp_cors
aiohttp-fast-url-dispatcher
aiohttp-zlib-ng
bleak>=0.19.0
bleak-retry-connector>=3.3.0
bluetooth-data-tools
pyserial-asyncio
pyudev
pytest-homeassistant-custom-component==0.13.107
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the BLE Battery Management System integration."""
110 changes: 110 additions & 0 deletions tests/bluetooth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""Test helpers for bluetooth copied from HA 2024.3.0."""

from typing import Any

from bleak import AdvertisementData, BLEDevice

from homeassistant.components.bluetooth import (
SOURCE_LOCAL,
BluetoothServiceInfoBleak,
async_get_advertisement_callback,
)
from homeassistant.core import HomeAssistant

ADVERTISEMENT_DATA_DEFAULTS = {
"local_name": "",
"manufacturer_data": {},
"service_data": {},
"service_uuids": [],
"rssi": -127,
"platform_data": ((),),
"tx_power": -127,
}

BLE_DEVICE_DEFAULTS = {
"name": None,
"rssi": -127,
"details": None,
}


def generate_advertisement_data(**kwargs: Any) -> AdvertisementData:
"""Generate advertisement data with defaults."""
new = kwargs.copy()
for key, value in ADVERTISEMENT_DATA_DEFAULTS.items():
new.setdefault(key, value)
return AdvertisementData(**new)


def generate_ble_device(
address: str | None = None,
name: str | None = None,
details: Any | None = None,
rssi: int | None = None,
**kwargs: Any,
) -> BLEDevice:
"""Generate a BLEDevice with defaults."""
new = kwargs.copy()
if address is not None:
new["address"] = address
if name is not None:
new["name"] = name
if details is not None:
new["details"] = details
if rssi is not None:
new["rssi"] = rssi
for key, value in BLE_DEVICE_DEFAULTS.items():
new.setdefault(key, value)
return BLEDevice(**new)


def inject_advertisement_with_time_and_source_connectable(
hass: HomeAssistant,
device: BLEDevice,
adv: AdvertisementData,
time: float,
source: str,
connectable: bool,
) -> None:
"""Inject an advertisement into the manager from a specific source at a time and connectable status."""
async_get_advertisement_callback(hass)(
BluetoothServiceInfoBleak(
name=adv.local_name or device.name or device.address,
address=device.address,
rssi=adv.rssi,
manufacturer_data=adv.manufacturer_data,
service_data=adv.service_data,
service_uuids=adv.service_uuids,
source=source,
device=device,
advertisement=adv,
connectable=connectable,
time=time,
)
)


def inject_bluetooth_service_info_bleak(
hass: HomeAssistant, info: BluetoothServiceInfoBleak
) -> None:
"""Inject an advertisement into the manager with connectable status."""
advertisement_data = generate_advertisement_data(
local_name=None if info.name == "" else info.name,
manufacturer_data=info.manufacturer_data,
service_data=info.service_data,
service_uuids=info.service_uuids,
rssi=info.rssi,
)
device = generate_ble_device( # type: ignore[no-untyped-call]
address=info.address,
name=info.name,
details={},
)
inject_advertisement_with_time_and_source_connectable(
hass,
device,
advertisement_data,
info.time,
SOURCE_LOCAL,
connectable=info.connectable,
)
Loading

0 comments on commit 3aec8b2

Please sign in to comment.