import subprocess import hashlib import json import sys import pytest from lib_py.agents.sanitize import sanitize_herdr_agent_name def test_sanitize_sibling_workspace_non_collision(): """ Verifies that sibling workspaces with identical prefix and suffix do NOT collapse into the same 32-character sanitized herdr agent name. """ workspaces = [ "canary-projects-educative-export-tools-creator-claude", "canary-projects-getting-started-a2a-creator-claude", "canary-projects-multi-agent-mux-creator-claude", "canary-projects-pu-riverpod-cookbook-creator-claude", ] sanitized = [sanitize_herdr_agent_name(w) for w in workspaces] # 1. All lengths <= 32 for s in sanitized: assert len(s) <= 32, f"Length exceeds 32: {s}" assert len(s) == 32, f"Expected 32 chars for long names: {s}" # 2. All names are unique (0 collisions) assert len(set(sanitized)) == len(workspaces), f"Collisions detected: {sanitized}" def test_sanitize_bash_python_parity(mam_sandbox): """ Verifies that bash _sanitize_herdr_agent_name and Python sanitize_herdr_agent_name produce byte-for-byte identical output across various edge cases. """ test_cases = [ "canary-projects-multi-agent-mux-creator-claude", "canary-projects-educative-export-tools-creator-claude", "canary-projects-getting-started-a2a-creator-claude", "canary-projects-pu-riverpod-cookbook-creator-claude", "short-name", "UPPER_CASE_123.dots", "123_starts_digit", "_underscore_leading", "", "exact-32-chars-long-name-1234567", "exact-33-chars-long-name-12345678", ] lib_path = mam_sandbox / ".agents" / "skills" / "lib.sh" for case in test_cases: py_res = sanitize_herdr_agent_name(case) cmd = f""" source {lib_path} _sanitize_herdr_agent_name "{case}" """ bash_res = subprocess.check_output(["bash", "-c", cmd]).decode().strip() assert py_res == bash_res, f"Mismatch for '{case}': Python={py_res} vs Bash={bash_res}" def test_mock_herdr_error_formatting_and_abort(mock_herdr, mock_agents): """ Verifies that mock herdr error output matches real Herdr 0.8.0 format and triggers lib.sh early abort regex. """ import re abort_regex = re.compile(r"^usage:|unknown option|unknown flag|missing required|invalid_agent_name|^error:", re.IGNORECASE) mock_bin = str(mock_agents / "herdr") # 1. Unknown flag res_unknown = subprocess.run([mock_bin, "agent", "start", "testagent", "--kind", "claude", "--pane", "w1:p1", "--env", "FOO=BAR"], capture_output=True, text=True) assert res_unknown.returncode != 0 assert abort_regex.search(res_unknown.stderr) is not None, f"Stderr did not match abort regex: {res_unknown.stderr}" assert "unknown option: --env" in res_unknown.stderr # 2. Missing required --pane res_missing = subprocess.run([mock_bin, "agent", "start", "testagent", "--kind", "claude"], capture_output=True, text=True) assert res_missing.returncode != 0 assert abort_regex.search(res_missing.stderr) is not None, f"Stderr did not match abort regex: {res_missing.stderr}" assert "missing required --pane" in res_missing.stderr # 3. Invalid agent name res_badname = subprocess.run([mock_bin, "agent", "start", "BAD_NAME", "--kind", "claude", "--pane", "w1:p1"], capture_output=True, text=True) assert res_badname.returncode != 0 assert abort_regex.search(res_badname.stderr) is not None, f"Stderr did not match abort regex: {res_badname.stderr}" assert "invalid_agent_name" in res_badname.stderr