"""Webhook sessions should behave like cron sessions in the project chip layer.""" def _agent_row(session_id="webhook-route-1", *, source="webhook", title="Webhook Run"): return { "id": session_id, "title": title, "model": "test-model", "source": source, "raw_source": source, "message_count": 1, "actual_message_count": 1, "actual_user_message_count": 1, "last_activity": 10.0, "started_at": 9.0, } def test_webhook_source_normalizes_to_webhook_category(): from api.agent_sessions import is_cli_session_row, normalize_agent_session_source normalized = normalize_agent_session_source("webhook") assert normalized == { "raw_source": "webhook", "session_source": "webhook", "source_label": "Webhook", } assert is_cli_session_row({**normalized, "source": "webhook", "title": "Webhook Session"}) is False def test_ensure_webhook_project_creates_dedicated_project(): from api.models import WEBHOOK_PROJECT_NAME, ensure_webhook_project, load_projects, save_projects projects = load_projects() save_projects([p for p in projects if p.get("name") != WEBHOOK_PROJECT_NAME]) pid1 = ensure_webhook_project() pid2 = ensure_webhook_project() assert pid1 == pid2 projects = load_projects() webhook_projects = [p for p in projects if p.get("name") == WEBHOOK_PROJECT_NAME] assert len(webhook_projects) == 1 assert webhook_projects[0]["project_id"] == pid1 assert webhook_projects[0]["color"] == "#0ea5e9" assert len(pid1) == 12 def test_is_webhook_session_detects_source_metadata_not_session_id(): from api.models import is_webhook_session assert is_webhook_session("any-id", source_tag="webhook") is True assert is_webhook_session("any-id", source_tag=" WebHook ") is True assert is_webhook_session("webhook:route:delivery") is False assert is_webhook_session("cron_abc", source_tag="cron") is False assert is_webhook_session("regular-session", source_tag="cli") is False assert is_webhook_session("") is False def test_project_assigned_webhook_rows_are_returned_but_default_hidden(): from api.models import ( _hide_from_default_sidebar, _include_project_hidden_background_sidebar_sessions, ) assert _hide_from_default_sidebar({"source_tag": "webhook", "session_id": "webhook:route:1"}) is True assert _hide_from_default_sidebar({"session_id": "webhook:route:1"}) is False visible = [ {"session_id": "webui-1", "title": "Normal", "message_count": 1, "project_id": None}, ] candidates = visible + [ { "session_id": "webhook-visible", "source_tag": "webhook", "title": "Webhook output", "message_count": 2, "project_id": "webhook-project", }, { "session_id": "webhook-unassigned", "source_tag": "webhook", "title": "Webhook output", "message_count": 2, "project_id": None, }, { "session_id": "webhook-empty", "source_tag": "webhook", "title": "Webhook output", "message_count": 0, "project_id": "webhook-project", }, ] rows = _include_project_hidden_background_sidebar_sessions(candidates, visible) by_id = {row["session_id"]: row for row in rows} assert set(by_id) == {"webui-1", "webhook-visible"} assert by_id["webhook-visible"]["default_hidden"] is True def test_webhook_rows_get_webhook_project_id(monkeypatch, tmp_path): import api.models as models db = tmp_path / "state.db" db.write_text("", encoding="utf-8") monkeypatch.setattr(models, "read_importable_agent_session_rows", lambda *_a, **_kw: [_agent_row()]) monkeypatch.setattr(models, "get_last_workspace", lambda profile=None: tmp_path) monkeypatch.setattr(models, "ensure_cron_project", lambda **_kw: "cron-project-id") monkeypatch.setattr(models, "ensure_webhook_project", lambda **_kw: "webhook-project-id", raising=False) monkeypatch.setattr(models.Session, "load_metadata_only", lambda _sid: None) rows = models._load_cli_sessions_uncached(tmp_path, db, _cli_profile=None, cron_project_limit=False) assert len(rows) == 1 assert rows[0]["source_tag"] == "webhook" assert rows[0]["session_source"] == "webhook" assert rows[0]["source_label"] == "Webhook" assert rows[0]["project_id"] == "webhook-project-id" assert rows[0]["is_cli_session"] is False def test_webhook_second_pass_keeps_older_project_rows_available(monkeypatch, tmp_path): import api.models as models db = tmp_path / "state.db" db.write_text("", encoding="utf-8") calls = [] def fake_read_rows(_db_path, **kwargs): calls.append(kwargs) if kwargs.get("include_sources") == ("webhook",): return [_agent_row("webhook-older", title="Older webhook")] return [] monkeypatch.setattr(models, "read_importable_agent_session_rows", fake_read_rows) monkeypatch.setattr(models, "get_last_workspace", lambda profile=None: tmp_path) monkeypatch.setattr(models, "ensure_cron_project", lambda **_kw: "cron-project-id") monkeypatch.setattr(models, "ensure_webhook_project", lambda **_kw: "webhook-project-id", raising=False) monkeypatch.setattr(models.Session, "load_metadata_only", lambda _sid: None) rows = models._load_cli_sessions_uncached(tmp_path, db, _cli_profile=None, cron_project_limit=False) assert [call.get("include_sources") for call in calls] == [ None, ("webhook",), ("kanban",), ] assert len(rows) == 1 assert rows[0]["session_id"] == "webhook-older" assert rows[0]["project_id"] == "webhook-project-id"