Files
hermes-webui/tests/test_issue6964_title_dedup.py
nesquena-hermesandwebtecnica fac3470668 docs(changelog): stamp #6964 title-dedup + add manual-rename-during-retry test
Codex SHOULD-FIX: add a committed test proving the de-dup retry variant never
clobbers a user-source manual rename (retry goes through the same provenance
rank guard). Additive test-only; no behavior change to the contributor's fix.

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-08-13 00:39:07 +00:00

136 lines
5.1 KiB
Python

"""Regression tests for state.db title de-dup on collision (#6964).
Follow-up to #6892 (sync auto-generated session titles to state.db).
When two WebUI sessions generate the SAME auto-title, hermes-agent's
state.db uniqueness rule makes the second ``set_auto_title_if_empty``
raise ValueError, which ``sync_session_title`` used to swallow at debug
level -- leaving the second row blank in ``hermes sessions list``.
These tests prove the collision path now de-duplicates (suffix variant)
instead of leaving the row untitled, and that unique titles are unchanged.
"""
import pytest
def _make_db(tmp_path):
hermes_state = pytest.importorskip("hermes_state")
SessionDB = hermes_state.SessionDB
return SessionDB(db_path=tmp_path / "state.db")
@pytest.mark.requires_agent_modules
def test_sync_session_title_dedups_second_session_on_collision(tmp_path, monkeypatch):
"""Two sessions with the same auto-title: the 2nd gets a de-duplicated
variant instead of being left blank in state.db."""
from api import state_sync
db = _make_db(tmp_path)
try:
# Route sync_session_title at the real temp DB (a fresh handle each call,
# mirroring production, but pointed at our tmp state.db).
monkeypatch.setattr(
state_sync,
"_get_state_db",
lambda profile=None: _make_db(tmp_path),
)
state_sync.sync_session_title("sess-1", "Same Title", profile="default")
state_sync.sync_session_title("sess-2", "Same Title", profile="default")
t1 = db.get_session_title("sess-1")
t2 = db.get_session_title("sess-2")
assert t1 == "Same Title"
# De-duplicated with a lineage suffix -- never left blank.
assert t2 == "Same Title #2"
assert t1 != t2
finally:
db.close()
@pytest.mark.requires_agent_modules
def test_sync_session_title_dedup_handles_numbered_base(tmp_path, monkeypatch):
"""A colliding title that already carries a '#N' suffix keeps incrementing
deterministically (e.g. 'Same Title #2' -> 'Same Title #3')."""
from api import state_sync
db = _make_db(tmp_path)
try:
monkeypatch.setattr(
state_sync,
"_get_state_db",
lambda profile=None: _make_db(tmp_path),
)
state_sync.sync_session_title("sess-1", "Same Title #2", profile="default")
state_sync.sync_session_title("sess-2", "Same Title #2", profile="default")
t1 = db.get_session_title("sess-1")
t2 = db.get_session_title("sess-2")
assert t1 == "Same Title #2"
assert t2 == "Same Title #3"
assert t1 != t2
finally:
db.close()
@pytest.mark.requires_agent_modules
def test_sync_session_title_unique_titles_unchanged(tmp_path, monkeypatch):
"""Non-colliding titles keep their exact value (no suffix added)."""
from api import state_sync
db = _make_db(tmp_path)
try:
monkeypatch.setattr(
state_sync,
"_get_state_db",
lambda profile=None: _make_db(tmp_path),
)
state_sync.sync_session_title("sess-a", "Alpha Title", profile="default")
state_sync.sync_session_title("sess-b", "Beta Title", profile="default")
assert db.get_session_title("sess-a") == "Alpha Title"
assert db.get_session_title("sess-b") == "Beta Title"
finally:
db.close()
@pytest.mark.requires_agent_modules
def test_sync_session_title_dedup_retry_never_clobbers_manual_rename(tmp_path, monkeypatch):
"""The de-dup retry still respects title provenance: if the second session
was manually renamed (``user`` source) before the colliding auto-title sync
runs, the retry with the ``#2`` variant must NOT overwrite the user's name.
Codex #6966 SHOULD-FIX: covers the manual-rename-during-retry interleaving
(the existing tests cover manual-rename-before-sync only indirectly)."""
from api import state_sync
db = _make_db(tmp_path)
try:
monkeypatch.setattr(
state_sync,
"_get_state_db",
lambda profile=None: _make_db(tmp_path),
)
# sess-1 takes "Same Title" via the normal auto-title sync.
state_sync.sync_session_title("sess-1", "Same Title", profile="default")
# sess-2 is manually renamed by the user (higher-ranked `user` source)
# BEFORE the colliding auto-title sync fires for it.
db.ensure_session(session_id="sess-2", source="webui")
db.set_session_title("sess-2", "My Own Name")
# Now the colliding auto-title sync runs for sess-2. It collides on
# "Same Title", derives "Same Title #2", and retries -- but the retry
# goes through the same provenance rank guard, so the user's manual
# name must survive.
state_sync.sync_session_title("sess-2", "Same Title", profile="default")
assert db.get_session_title("sess-1") == "Same Title"
# The manual (user-source) rename is preserved -- never clobbered by
# either the initial auto-title or the de-dup retry variant.
assert db.get_session_title("sess-2") == "My Own Name"
finally:
db.close()