Task 1 — Presence: ping family phones, derive household state (EMPTY/KIDS_HOME/ADULTS_HOME/ALL_HOME), configurable departure delay, per-member roles, auto-arm actions via MQTT. Task 2 — Detection: MobileNet-SSD v2 via OpenCV DNN for person/vehicle classification. Vehicle color/size fingerprinting for known car matching. Zone-based filtering per camera. Model download script. Task 3 — Health: periodic disk/MQTT/subsystem checks, auto-prune oldest non-starred recordings on disk pressure, daily digest builder. 126 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
82 lines
3.2 KiB
Python
82 lines
3.2 KiB
Python
"""Tests for presence detection."""
|
|
|
|
import time
|
|
from unittest.mock import patch
|
|
|
|
from vigilar.config import PresenceConfig, PresenceMember
|
|
from vigilar.constants import HouseholdState
|
|
from vigilar.presence.models import MemberPresence, derive_household_state
|
|
|
|
|
|
class TestDeriveHouseholdState:
|
|
def test_empty_when_no_members_home(self):
|
|
members = [
|
|
MemberPresence(name="Dad", role="adult", is_home=False, last_seen=0),
|
|
MemberPresence(name="Mom", role="adult", is_home=False, last_seen=0),
|
|
]
|
|
assert derive_household_state(members) == HouseholdState.EMPTY
|
|
|
|
def test_all_home(self):
|
|
members = [
|
|
MemberPresence(name="Dad", role="adult", is_home=True, last_seen=0),
|
|
MemberPresence(name="Mom", role="adult", is_home=True, last_seen=0),
|
|
MemberPresence(name="Kid", role="child", is_home=True, last_seen=0),
|
|
]
|
|
assert derive_household_state(members) == HouseholdState.ALL_HOME
|
|
|
|
def test_kids_home_only(self):
|
|
members = [
|
|
MemberPresence(name="Dad", role="adult", is_home=False, last_seen=0),
|
|
MemberPresence(name="Kid", role="child", is_home=True, last_seen=0),
|
|
]
|
|
assert derive_household_state(members) == HouseholdState.KIDS_HOME
|
|
|
|
def test_adults_home_not_all(self):
|
|
members = [
|
|
MemberPresence(name="Dad", role="adult", is_home=True, last_seen=0),
|
|
MemberPresence(name="Kid", role="child", is_home=False, last_seen=0),
|
|
]
|
|
assert derive_household_state(members) == HouseholdState.ADULTS_HOME
|
|
|
|
def test_adults_home_no_children_configured(self):
|
|
members = [
|
|
MemberPresence(name="Dad", role="adult", is_home=True, last_seen=0),
|
|
]
|
|
assert derive_household_state(members) == HouseholdState.ALL_HOME
|
|
|
|
def test_empty_list(self):
|
|
assert derive_household_state([]) == HouseholdState.EMPTY
|
|
|
|
|
|
class TestPingHost:
|
|
@patch("vigilar.presence.monitor.subprocess.run")
|
|
def test_ping_success(self, mock_run):
|
|
from vigilar.presence.monitor import ping_host
|
|
mock_run.return_value = type("R", (), {"returncode": 0})()
|
|
assert ping_host("192.168.1.50") is True
|
|
|
|
@patch("vigilar.presence.monitor.subprocess.run")
|
|
def test_ping_failure(self, mock_run):
|
|
from vigilar.presence.monitor import ping_host
|
|
mock_run.return_value = type("R", (), {"returncode": 1})()
|
|
assert ping_host("192.168.1.50") is False
|
|
|
|
@patch("vigilar.presence.monitor.subprocess.run")
|
|
def test_ping_timeout(self, mock_run):
|
|
import subprocess as sp
|
|
from vigilar.presence.monitor import ping_host
|
|
mock_run.side_effect = sp.TimeoutExpired(cmd="ping", timeout=2)
|
|
assert ping_host("192.168.1.50") is False
|
|
|
|
|
|
class TestDepartureDelay:
|
|
def test_not_departed_within_delay(self):
|
|
from vigilar.presence.monitor import should_mark_away
|
|
last_seen = time.monotonic() - 300 # 5 min ago
|
|
assert should_mark_away(last_seen, departure_delay_m=10) is False
|
|
|
|
def test_departed_after_delay(self):
|
|
from vigilar.presence.monitor import should_mark_away
|
|
last_seen = time.monotonic() - 700 # 11+ min ago
|
|
assert should_mark_away(last_seen, departure_delay_m=10) is True
|