The all-profiles sidebar scan walks every profile's state.db in one request. When it lazily minted a "Webhooks" / "Cron Jobs" project for a profile that had webhook/cron rows, ensure_webhook_project() and ensure_cron_project() tagged the new entry with get_active_profile_name() -- the UI-selected profile -- instead of the profile whose rows triggered it. A webhook-only profile therefore leaked a "Webhooks" project onto whichever profile was selected; deleting it did nothing because the next sidebar poll re-minted it, and switching profiles minted one per profile. ensure_webhook_project / ensure_cron_project / _profile_has_user_projects now take an explicit profile (default: active profile), and the scan passes _cli_profile through, so the opt-in gate and the tag both key on the scanned profile. Existing stubs in tests updated to accept the kwargs.
155 lines
5.7 KiB
Python
155 lines
5.7 KiB
Python
"""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"
|