oclaw/tests/test_sa_ui_session_owner.py
oliver d14e9d3596 feat(persistence): PostgreSQL assistant store, chat persist fixes, gateway scripts
- Add SQLAlchemy Core repos, pg adapter/compat, assistant_store factory, Alembic bootstrap and migration/cutover scripts.

- Harden chat_message writes (NUL scrub for PG), turn_uuid on attempt failure, WS turn_runner fallbacks and gateway executed_turn_uuid init.

- start_gateway: log paths, PS7 stderr handling via cmd, background stdout/stderr redirect; runtime assistant_runtime_log_dir export.

- Ops: clear_all_chat_sessions with PG-only --postgresql and env-gated wipe; clear_postgres_chat_sessions.ps1.

- Tests: SA repos, pg compat, persist fallback, smoke env isolation; CI and docs touch-ups.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 09:33:50 +08:00

96 lines
3.6 KiB
Python

"""SA migration: ui_session_owner upsert + backfill."""
from __future__ import annotations
import pytest
from svc.persistence.db.engine import clear_assistant_engine_cache
from svc.persistence.sqlite_store import SqliteStore
@pytest.fixture
def fresh_sqlite_store(monkeypatch: pytest.MonkeyPatch, tmp_path):
monkeypatch.delenv("AIA_ASSISTANT_DB_BACKEND", raising=False)
monkeypatch.setenv("OPS_WORKSPACE_ROOT", str(tmp_path))
dbfile = tmp_path / "sa_ui_owner.sqlite"
monkeypatch.setenv("AIA_ASSISTANT_DB_PATH", str(dbfile))
clear_assistant_engine_cache()
s = SqliteStore(str(dbfile))
try:
yield s
finally:
clear_assistant_engine_cache()
def test_sa_create_session_for_user_and_get_owner(fresh_sqlite_store: SqliteStore) -> None:
s = fresh_sqlite_store
t = s.create_tenant("UIO")
u = s.create_user(tenant_id=t["id"], display_name="U", role="member")
sess = s.create_session_for_user(title="T", tenant_id=t["id"], user_id=u["id"])
row = s.get_ui_session_owner(session_id=sess.id)
assert row is not None
assert row["tenant_id"] == t["id"]
assert row["user_id"] == u["id"]
s.ensure_ui_session_owner(session_id=sess.id, tenant_id=t["id"], user_id=u["id"])
row2 = s.get_ui_session_owner(session_id=sess.id)
assert row2 == row
def test_sa_upsert_replace_changes_owner(fresh_sqlite_store: SqliteStore) -> None:
s = fresh_sqlite_store
t = s.create_tenant("UIO2")
u1 = s.create_user(tenant_id=t["id"], display_name="A", role="member")
u2 = s.create_user(tenant_id=t["id"], display_name="B", role="member")
sess = s.create_session_for_user(title="X", tenant_id=t["id"], user_id=u1["id"])
s._ui_session_owner_repo().upsert_replace(
session_id=sess.id,
tenant_id=t["id"],
user_id=u2["id"],
created_at="2099-01-01T00:00:00+00:00",
)
row = s.get_ui_session_owner(session_id=sess.id)
assert row is not None
assert row["user_id"] == u2["id"]
def test_sa_backfill_orphan_sessions(fresh_sqlite_store: SqliteStore) -> None:
s = fresh_sqlite_store
t = s.create_tenant("BF")
u = s.create_user(tenant_id=t["id"], display_name="BFU", role="member")
orphan = s.create_session("orphan title")
assert s.get_ui_session_owner(session_id=orphan.id) is None
n = s.backfill_orphan_chat_sessions_for_user(tenant_id=t["id"], user_id=u["id"])
assert n >= 1
row = s.get_ui_session_owner(session_id=orphan.id)
assert row is not None
assert row["user_id"] == u["id"]
def test_sa_backfill_ui_session_owner_from_channel_v2(fresh_sqlite_store: SqliteStore) -> None:
s = fresh_sqlite_store
t = s.create_tenant("CH")
u = s.create_user(tenant_id=t["id"], display_name="CHU", role="member")
sess = s.create_session("ch sess")
ts = "2025-01-01T00:00:00+00:00"
with s._connect() as conn:
conn.execute(
"""
INSERT INTO channel_identity_v2
(tenant_id, channel, account_id, external_user_id, user_id, created_at)
VALUES (?, 'wecom', 'acc1', 'extu', ?, ?)
""",
(t["id"], u["id"], ts),
)
conn.execute(
"""
INSERT INTO channel_session_v2
(tenant_id, channel, account_id, external_chat_id, external_user_id, session_id, created_at)
VALUES (?, 'wecom', 'acc1', 'chat1', 'extu', ?, ?)
""",
(t["id"], sess.id, ts),
)
n = s.backfill_ui_session_owner_from_channel_v2()
assert n >= 1
row = s.get_ui_session_owner(session_id=sess.id)
assert row is not None
assert row["user_id"] == u["id"]