Files
hermes-agent/tests/hermes_cli/test_merge_profile_tree.py
Brooklyn Nicholson f102b16c1f fix(profiles): reuse the canonical _path_key for cross-profile folder merge
Review on #132008: os.path.normcase/normpath derive case folding and
separator handling from the host, so a POSIX backend serving Windows
paths never folds C:\Users\me\Project with C:/Users/me/project, and
accented folders regress on the #65014 class. tui_gateway.project_tree
already defines the shape-derived identity the per-profile trees use;
reuse it so the merge agrees by construction with its inputs. The
casing test no longer monkeypatches normcase — it pins the real
normalization on POSIX, plus forward-slash and NFC/NFD fold tests
(red on the normcase key, green with _path_key).
2026-10-05 16:55:53 -05:00

122 lines
4.7 KiB
Python

"""_merge_profile_tree folds projects across profiles by normalized path."""
def _make_project(path=None, pid="p_abc", is_auto=False, label="Proj"):
"""Minimal project dict matching the shape _merge_profile_tree expects."""
return {
"id": pid,
"path": path,
"label": label,
"isAuto": is_auto,
"repos": [],
"previewSessions": [],
"sessionCount": 0,
"totalTokens": 0,
"totalCostUsd": 0,
"lastActive": 0,
}
class TestMergeProfileTree:
"""Unit tests for the merge-key normalization in _merge_profile_tree."""
def test_same_path_different_casing_produces_one_entry(self):
"""Windows case-insensitive paths must fold into one group on any host.
The canonical folder key is shape-derived (``_path_key``): a drive-letter
path case-folds even when the backend runs on POSIX, so this needs no
monkeypatching — it exercises the real normalization.
"""
from hermes_cli.web_routers.profiles import _merge_profile_tree
merged = {}
proj_a = _make_project(path=r"C:\Users\me\Project", pid="p_a")
proj_b = _make_project(path=r"C:\Users\me\project", pid="p_b")
_merge_profile_tree(merged, [proj_a], "alpha", preview_limit=5)
_merge_profile_tree(merged, [proj_b], "beta", preview_limit=5)
assert len(merged) == 1
# First writer wins the identity slot.
assert list(merged.values())[0]["id"] == "p_a"
def test_windows_forward_slash_spelling_folds(self):
"""``C:/Users/me/Project`` and ``C:\\Users\\me\\Project`` are one folder."""
from hermes_cli.web_routers.profiles import _merge_profile_tree
merged = {}
proj_a = _make_project(path=r"C:\Users\me\Project", pid="p_a")
proj_b = _make_project(path="C:/Users/me/Project", pid="p_b")
_merge_profile_tree(merged, [proj_a], "alpha", preview_limit=5)
_merge_profile_tree(merged, [proj_b], "beta", preview_limit=5)
assert len(merged) == 1
def test_accented_folder_nfd_nfc_spellings_fold(self):
"""The same accented folder may arrive NFC (typed) or NFD (macOS pickers) —
one group (#65014 class)."""
from hermes_cli.web_routers.profiles import _merge_profile_tree
merged = {}
proj_a = _make_project(path="/home/me/café", pid="p_a")
proj_b = _make_project(path="/home/me/café", pid="p_b")
_merge_profile_tree(merged, [proj_a], "alpha", preview_limit=5)
_merge_profile_tree(merged, [proj_b], "beta", preview_limit=5)
assert len(merged) == 1
def test_trailing_slash_does_not_duplicate(self):
"""A trailing separator doesn't create a second folder identity."""
from hermes_cli.web_routers.profiles import _merge_profile_tree
merged = {}
proj_a = _make_project(path="/home/me/proj", pid="p_a")
proj_b = _make_project(path="/home/me/proj/", pid="p_b")
_merge_profile_tree(merged, [proj_a], "alpha", preview_limit=5)
_merge_profile_tree(merged, [proj_b], "beta", preview_limit=5)
assert len(merged) == 1
def test_different_paths_stay_separate(self):
"""Genuinely different folders must remain distinct groups."""
from hermes_cli.web_routers.profiles import _merge_profile_tree
merged = {}
proj_a = _make_project(path="/home/me/alpha", pid="p_a")
proj_b = _make_project(path="/home/me/beta", pid="p_b")
_merge_profile_tree(merged, [proj_a], "alpha", preview_limit=5)
_merge_profile_tree(merged, [proj_b], "beta", preview_limit=5)
assert len(merged) == 2
def test_id_fallback_when_path_is_none(self):
"""Projects without a path key on the project id instead."""
from hermes_cli.web_routers.profiles import _merge_profile_tree
merged = {}
proj_a = _make_project(path=None, pid="p_same")
proj_b = _make_project(path=None, pid="p_same")
_merge_profile_tree(merged, [proj_a], "alpha", preview_limit=5)
_merge_profile_tree(merged, [proj_b], "beta", preview_limit=5)
assert len(merged) == 1
def test_declared_project_wins_over_auto(self):
"""When a declared project meets an auto entry, declared wins identity."""
from hermes_cli.web_routers.profiles import _merge_profile_tree
merged = {}
auto = _make_project(path="/home/me/proj", pid="p_auto", is_auto=True, label="auto")
declared = _make_project(path="/home/me/proj", pid="p_decl", is_auto=False, label="My Proj")
_merge_profile_tree(merged, [auto], "alpha", preview_limit=5)
_merge_profile_tree(merged, [declared], "beta", preview_limit=5)
assert len(merged) == 1
assert list(merged.values())[0]["label"] == "My Proj"