"""Tests for the manual-trigger automation route (POST /automations/{id}/trigger). The AutomationEngine is replaced with a lightweight fake so the route layer is tested without driving the real evaluation loop or scene application. """ import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from ledgrab.api import dependencies as deps from ledgrab.api.routes.automations import router from ledgrab.storage.automation import ManualTriggerRule from ledgrab.storage.automation_store import AutomationStore class FakeEngine: """Stand-in exposing only what the trigger route calls.""" def __init__(self, result=("triggered", [])): self.result = result self.calls = [] async def fire_manual_trigger(self, automation): self.calls.append(automation.id) return self.result @pytest.fixture def _route_db(tmp_path): from ledgrab.storage.database import Database db = Database(tmp_path / "test.db") yield db db.close() @pytest.fixture def automation_store(_route_db) -> AutomationStore: store = AutomationStore(_route_db) store.create_automation( name="Manual one", enabled=True, rule_logic="or", rules=[ManualTriggerRule()], scene_preset_id=None, ) return store @pytest.fixture def fake_engine(): return FakeEngine() @pytest.fixture def client(automation_store, fake_engine): app = FastAPI() app.include_router(router) from ledgrab.api.auth import verify_api_key app.dependency_overrides[verify_api_key] = lambda: "test-user" app.dependency_overrides[deps.get_automation_store] = lambda: automation_store app.dependency_overrides[deps.get_automation_engine] = lambda: fake_engine # Routes may fire entity events through the processor manager; give it a stub. deps._deps["processor_manager"] = None return TestClient(app, raise_server_exceptions=False) def _first_id(store: AutomationStore) -> str: return store.get_all_automations()[0].id class TestTriggerRoute: def test_trigger_returns_status(self, client, automation_store, fake_engine): aid = _first_id(automation_store) resp = client.post(f"/api/v1/automations/{aid}/trigger") assert resp.status_code == 200 assert resp.json() == {"status": "triggered", "errors": []} assert fake_engine.calls == [aid] def test_trigger_skipped(self, client, automation_store, fake_engine): fake_engine.result = ("skipped", []) aid = _first_id(automation_store) resp = client.post(f"/api/v1/automations/{aid}/trigger") assert resp.status_code == 200 assert resp.json()["status"] == "skipped" def test_trigger_partial_errors(self, client, automation_store, fake_engine): fake_engine.result = ("partial", ["dev1: timeout"]) aid = _first_id(automation_store) resp = client.post(f"/api/v1/automations/{aid}/trigger") assert resp.status_code == 200 body = resp.json() assert body["status"] == "partial" assert body["errors"] == ["dev1: timeout"] def test_trigger_unknown_id_404(self, client, fake_engine): resp = client.post("/api/v1/automations/auto_ghost/trigger") assert resp.status_code == 404 assert fake_engine.calls == []