"""v0.7-fire-tracker-4 tests.""" from __future__ import annotations import asyncio import time import uuid import pytest @pytest.fixture(autouse=True) def _isolate_db(tmp_path, monkeypatch): db_path = str(tmp_path / f"meshai-{uuid.uuid4().hex}.sqlite") monkeypatch.setenv("MESHAI_DB_PATH", db_path) from meshai.persistence import db as pdb pdb.close_thread_connection() pdb._initialised.discard(db_path) from meshai.persistence import init_db init_db(db_path) yield db_path pdb.close_thread_connection() pdb._initialised.discard(db_path) def _seed_fire(*, irwin_id, name, lat, lon, acres=None, contained=None, county="Test", state="ID"): from meshai.persistence import get_db get_db().execute( "INSERT INTO fires(irwin_id, incident_name, current_acres, " "current_contained_pct, lat, lon, county, state, last_event_at) " "VALUES (?,?,?,?,?,?,?,?,?)", (irwin_id, name, acres, contained, lat, lon, county, state, int(time.time())), ) # =========================================================================== # Bug A regression: scope_type defined before use # =========================================================================== def test_router_scope_type_defined_before_env_check(): """The env_reporter check at the top of generate_llm_response reads scope_type. Pre-fix it was UnboundLocalError on every env query.""" import re import importlib.util from pathlib import Path # Resolve the module's file path without importing it -- router.py # transitively imports optional LLM backend deps (openai/anthropic) # that may not be installed in every test environment, and this test # only needs the source text, not a working import. spec = importlib.util.find_spec("meshai.router") src = Path(spec.origin).read_text() # Find the "if should_inject_mesh and scope_type" line + the # nearest preceding `scope_type, scope_value = ` assignment. env_use_line = None for i, line in enumerate(src.splitlines(), start=1): if "should_inject_mesh and scope_type" in line: env_use_line = i break assert env_use_line is not None # There must be an assignment on or before this line. preceding = "\n".join(src.splitlines()[: env_use_line - 1]) assert re.search(r"scope_type[, ]+scope_value\s*=", preceding) \ or "scope_type:" in preceding # =========================================================================== # Natural-language fire DMs route to the LLM (no ?status fallback) # =========================================================================== def test_natural_language_fire_question_routes_to_llm(tmp_path): """The LLM DM path is the sole interface for natural-language fire questions. Pre-revised commit there was a `?status` intent that rewrote the query in-router; this test confirms the rewrite is gone and that a plain English question is forwarded verbatim.""" import asyncio from meshai.router import MessageRouter, RouteType from meshai.config_loader import load_config from meshai.history import ConversationHistory from meshai.commands.dispatcher import create_dispatcher # load_config() defaults HistoryConfig.database to the relative path # "conversations.db", resolved against the process CWD. Left alone, # every test in the suite that calls load_config() with no override # shares that one file for the whole pytest session, so conversation # rows written by an unrelated, earlier-running test file can leak # into this test's routing decision. Point both the config dir and # the history database at this test's own tmp_path to make it # hermetic regardless of run order. cfg = load_config(tmp_path / "config") cfg.history.database = str(tmp_path / "conversations.db") history = ConversationHistory(cfg.history) async def _run(): await history.initialize() dispatcher = create_dispatcher( prefix=cfg.commands.prefix, disabled_commands=cfg.commands.disabled_commands, custom_commands=cfg.commands.custom_commands, ) class FakeConnector: my_node_id = "!THIS_BOT" class FakeMessage: text = "how's the cache peak fire?" sender_id = "!T" sender_name = "t" is_dm = True channel = 0 router = MessageRouter( config=cfg, connector=FakeConnector(), history=history, dispatcher=dispatcher, llm_backend=None, # we only inspect the route() decision ) result = await router.route(FakeMessage()) return result result = asyncio.run(_run()) assert result.route_type == RouteType.LLM # Critical: the query must be the verbatim user text, not a rewrite # synthesized by an in-router intent helper. assert result.query == "how's the cache peak fire?" def test_status_helpers_removed_from_router(): """Hard guard against ?status helpers sneaking back in. If anyone adds a structured-command path to router.py for fires, this test fails and the author has to talk to Matt first.""" import importlib.util from pathlib import Path # See test_router_scope_type_defined_before_env_check above for why # this resolves the path via find_spec rather than importing router.py # or hardcoding a deployment-container path. spec = importlib.util.find_spec("meshai.router") src = Path(spec.origin).read_text() assert "_maybe_rewrite_status_query" not in src assert "_lookup_fire_fuzzy" not in src assert "?status" not in src