chore: Projektgeruest, Manifest, Konstanten und CI

This commit is contained in:
Michael Sauer
2026-09-16 14:50:01 +02:00
parent 4d77e90eee
commit 18583c6593
11 changed files with 147 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
name: CI
on:
push:
pull_request:
jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: astral-sh/setup-uv@v5
with:
cache-dependency-glob: "requirements_dev.txt"
- run: uv venv --python 3.14 .venv && uv pip install --python .venv/bin/python -r requirements_dev.txt
- run: .venv/bin/ruff check . && .venv/bin/ruff format --check .
- run: .venv/bin/pytest -q --cov --cov-report=term-missing
hassfest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: home-assistant/actions/hassfest@master
hacs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: hacs/action@main
with:
category: integration
+1
View File
@@ -0,0 +1 @@
"""The ninlight integration."""
+34
View File
@@ -0,0 +1,34 @@
"""Constants for the ninlight integration. Deliberately free of Home Assistant imports."""
from __future__ import annotations
from datetime import timedelta
from typing import Final
DOMAIN: Final = "ninlight"
# Public default token of the ninlight firmware (CMakeLists.txt of the firmware repo).
DEFAULT_TOKEN: Final = "aabbccddeeff0011223344"
CONF_SCAN_INTERVAL: Final = "scan_interval"
CONF_LAMP_BASE_URL: Final = "lamp_base_url"
DEFAULT_SCAN_INTERVAL: Final = 30
MIN_SCAN_INTERVAL: Final = 10
MAX_SCAN_INTERVAL: Final = 300
MAX_TRANSITION_MS: Final = 60_000
FIRMWARE_DIR_NAME: Final = "ninlight_firmware"
CATALOG_SCAN_INTERVAL: Final = timedelta(minutes=5)
SIGNAL_CATALOG_UPDATED: Final = f"{DOMAIN}_catalog_updated"
FIRMWARE_URL_PREFIX: Final = "/api/ninlight/firmware/"
# len(secrets.token_urlsafe(32))
FIRMWARE_KEY_LENGTH: Final = 43
# The lamp rejects fw_update URLs longer than this (nl_api.c: get_string(cmd, "url", 8, 255)).
MAX_LAMP_URL_LENGTH: Final = 255
OTA_POLL_INTERVAL_S: Final = 5.0
# The firmware aborts a stalled download after 10 min; a dead peer can hang ~7 min in TCP.
OTA_TIMEOUT_S: Final = 12 * 60.0
+14
View File
@@ -0,0 +1,14 @@
{
"domain": "ninlight",
"name": "ninlight",
"codeowners": ["@ninharp"],
"config_flow": true,
"dependencies": ["http"],
"documentation": "https://github.com/ninharp/ha-ninlight",
"integration_type": "device",
"iot_class": "local_polling",
"issue_tracker": "https://github.com/ninharp/ha-ninlight/issues",
"requirements": [],
"version": "0.1.0",
"zeroconf": [{ "type": "_ninlight._tcp.local." }]
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "ninlight",
"render_readme": true,
"homeassistant": "2026.9.0"
}
+27
View File
@@ -0,0 +1,27 @@
[tool.pytest.ini_options]
testpaths = ["tests_unit", "tests"]
asyncio_mode = "auto"
addopts = "-p no:cacheprovider"
[tool.coverage.run]
source = ["custom_components/ninlight"]
branch = true
[tool.coverage.report]
fail_under = 95
show_missing = true
[tool.ruff]
target-version = "py314"
line-length = 100
exclude = ["docs/"]
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM", "RUF", "ASYNC"]
# E501: line length is the formatter's job; long literals in test tables stay on one line.
# RUF012: HA entities declare _attr_* lists as plain class attributes by convention.
ignore = ["E501", "RUF012"]
[tool.ruff.lint.isort]
known-first-party = ["custom_components.ninlight", "tests", "tests_unit"]
force-sort-within-sections = true
+4
View File
@@ -0,0 +1,4 @@
homeassistant==2026.9.0
pytest-homeassistant-custom-component==0.13.363
pytest-cov
ruff>=0.9
View File
+13
View File
@@ -0,0 +1,13 @@
"""Fixtures for tests that run against a Home Assistant instance."""
from __future__ import annotations
from collections.abc import Generator
import pytest
@pytest.fixture(autouse=True)
def auto_enable_custom_integrations(enable_custom_integrations: None) -> Generator[None]:
"""Load custom_components/ in every Home Assistant test."""
yield
View File
+20
View File
@@ -0,0 +1,20 @@
"""The manifest must agree with the constants the code relies on."""
from __future__ import annotations
import json
from pathlib import Path
from custom_components.ninlight.const import DOMAIN
MANIFEST = Path(__file__).parents[1] / "custom_components" / "ninlight" / "manifest.json"
def test_manifest_matches_constants() -> None:
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
assert manifest["domain"] == DOMAIN
assert manifest["zeroconf"] == [{"type": "_ninlight._tcp.local."}]
assert manifest["iot_class"] == "local_polling"
assert manifest["config_flow"] is True
assert manifest["requirements"] == []
assert manifest["dependencies"] == ["http"]