Release v3.0.0

- Bump version to 3.0.0 and update docs

- Fix Debian payload to include TUI and install /usr/bin/sensorpajen-tui wrapper

- Make systemd unit upgrades safer and ignore deb build artifacts
This commit is contained in:
2025-12-29 15:34:03 +01:00
parent 54d55cf0f6
commit fcaaf29307
50 changed files with 963 additions and 2421 deletions

View File

@@ -1,8 +1,225 @@
import pytest
from sensorpajen.tui.app import SensorpajenApp
import tempfile
import json
import sqlite3
from pathlib import Path
from datetime import datetime
from sensorpajen.config import SensorConfig
from sensorpajen.discovery_manager import DiscoveryManager
def test_tui_app_init():
# Just test that we can instantiate it
app = SensorpajenApp()
assert app.discovery_manager is not None
assert app.sensor_config is not None
def test_tui_sensor_config_edit():
"""Integration test: Test that editing a sensor works end-to-end"""
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / "sensors.json"
db_file = Path(tmpdir) / "test.db"
# Create initial config
initial_data = {
"sensors": [
{"mac": "AA:BB:CC:DD:EE:FF", "name": "Living Room Sensor"}
]
}
config_file.write_text(json.dumps(initial_data, indent=2))
# Initialize database
conn = sqlite3.connect(str(db_file))
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS discovered_sensors (
mac TEXT PRIMARY KEY,
name TEXT,
rssi INTEGER,
first_seen TIMESTAMP,
last_seen TIMESTAMP,
count INTEGER DEFAULT 0,
last_temp REAL,
last_humidity REAL,
last_battery_percent INTEGER,
last_battery_voltage INTEGER,
status TEXT DEFAULT 'pending',
reviewed BOOLEAN DEFAULT 0,
ignored_at TIMESTAMP,
ignore_reason TEXT
)
""")
now = datetime.now().isoformat()
cursor.execute("""
INSERT INTO discovered_sensors
(mac, name, rssi, first_seen, last_seen, count, last_temp, last_humidity,
last_battery_percent, last_battery_voltage, status, reviewed)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'approved', 1)
""", ("AA:BB:CC:DD:EE:FF", "Living Room Sensor", -65, now, now, 50, 23.5, 55, 85, 2950))
conn.commit()
conn.close()
# Load config and discovery manager (simulating TUI)
config = SensorConfig(str(config_file))
dm = DiscoveryManager(str(db_file), config)
# Verify initial state
assert config.sensors["AA:BB:CC:DD:EE:FF"] == "Living Room Sensor"
# Edit sensor (simulate user action in TUI)
config.add_sensor("AA:BB:CC:DD:EE:FF", "Bedroom Sensor")
# Verify in-memory update
assert config.sensors["AA:BB:CC:DD:EE:FF"] == "Bedroom Sensor"
# Verify disk update
saved_data = json.loads(config_file.read_text())
assert saved_data["sensors"][0]["name"] == "Bedroom Sensor"
# Simulate refresh_data() - create new config instance and verify
config2 = SensorConfig(str(config_file))
assert config2.sensors["AA:BB:CC:DD:EE:FF"] == "Bedroom Sensor"
def test_sensor_config_edit_updates_memory():
"""Test that editing a sensor updates both disk and memory"""
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / "sensors.json"
# Create initial config
initial_data = {
"sensors": [
{"mac": "AA:BB:CC:DD:EE:FF", "name": "Original Name"}
]
}
config_file.write_text(json.dumps(initial_data, indent=2))
# Load config
config = SensorConfig(str(config_file))
assert config.sensors["AA:BB:CC:DD:EE:FF"] == "Original Name"
# Edit sensor
config.add_sensor("AA:BB:CC:DD:EE:FF", "Updated Name")
# Check in-memory is updated
assert config.sensors["AA:BB:CC:DD:EE:FF"] == "Updated Name"
# Check disk is updated
saved_data = json.loads(config_file.read_text())
assert saved_data["sensors"][0]["name"] == "Updated Name"
# Reload from disk and verify
config2 = SensorConfig(str(config_file))
assert config2.sensors["AA:BB:CC:DD:EE:FF"] == "Updated Name"
def test_sensor_config_remove_sensor():
"""Test that removing a sensor works correctly"""
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / "sensors.json"
# Create config with multiple sensors
initial_data = {
"sensors": [
{"mac": "AA:BB:CC:DD:EE:FF", "name": "Sensor 1"},
{"mac": "AA:BB:CC:DD:EE:11", "name": "Sensor 2"}
]
}
config_file.write_text(json.dumps(initial_data, indent=2))
# Load and verify
config = SensorConfig(str(config_file))
assert len(config.sensors) == 2
# Remove one sensor
config.remove_sensor("AA:BB:CC:DD:EE:FF")
# Check in-memory removal
assert "AA:BB:CC:DD:EE:FF" not in config.sensors
assert "AA:BB:CC:DD:EE:11" in config.sensors
# Check disk update
saved_data = json.loads(config_file.read_text())
assert len(saved_data["sensors"]) == 1
assert saved_data["sensors"][0]["mac"] == "AA:BB:CC:DD:EE:11"
def test_sensor_config_reload():
"""Test that reload() re-reads from disk"""
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / "sensors.json"
# Create initial config
initial_data = {
"sensors": [
{"mac": "AA:BB:CC:DD:EE:FF", "name": "Original Name"}
]
}
config_file.write_text(json.dumps(initial_data, indent=2))
# Load config
config = SensorConfig(str(config_file))
assert config.sensors["AA:BB:CC:DD:EE:FF"] == "Original Name"
# Manually modify file on disk
new_data = {
"sensors": [
{"mac": "AA:BB:CC:DD:EE:FF", "name": "Externally Modified"}
]
}
config_file.write_text(json.dumps(new_data, indent=2))
# Reload should pick up the changes
config.load()
assert config.sensors["AA:BB:CC:DD:EE:FF"] == "Externally Modified"
def test_discovery_manager_approve_sensor():
"""Test that approving a sensor works correctly"""
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / "sensors.json"
db_file = Path(tmpdir) / "test.db"
# Create empty config
config_file.write_text(json.dumps({"sensors": []}, indent=2))
# Initialize database with pending sensor
conn = sqlite3.connect(str(db_file))
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS discovered_sensors (
mac TEXT PRIMARY KEY,
name TEXT,
rssi INTEGER,
first_seen TIMESTAMP,
last_seen TIMESTAMP,
count INTEGER DEFAULT 0,
last_temp REAL,
last_humidity REAL,
last_battery_percent INTEGER,
last_battery_voltage INTEGER,
status TEXT DEFAULT 'pending',
reviewed BOOLEAN DEFAULT 0,
ignored_at TIMESTAMP,
ignore_reason TEXT
)
""")
now = datetime.now().isoformat()
cursor.execute("""
INSERT INTO discovered_sensors
(mac, name, rssi, first_seen, last_seen, count, status)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", ("AA:BB:CC:DD:EE:33", "Unknown Sensor", -80, now, now, 1, "pending"))
conn.commit()
conn.close()
# Load config and DM
config = SensorConfig(str(config_file))
dm = DiscoveryManager(str(db_file), config)
# Verify sensor is pending
pending = dm.get_pending()
assert len(pending) == 1
assert pending[0].mac == "AA:BB:CC:DD:EE:33"
# Approve and add to config (simulate TUI action)
config.add_sensor("AA:BB:CC:DD:EE:33", "Kitchen Sensor")
dm.approve("AA:BB:CC:DD:EE:33")
# Verify sensor is no longer pending (filtered by config)
pending = dm.get_pending()
assert len(pending) == 0
# Verify it's in config
assert config.sensors["AA:BB:CC:DD:EE:33"] == "Kitchen Sensor"