feat(b1): fix find_workspace_uuid tier-3 identity lookup and add test_b1_tier3_identity.py (100% PASS)
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
tests/test_b1_tier3_identity.py — B-1 tier-3 identity cache regression suite (V-1..V-8).
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
LIB_SH = os.path.join(REPO_ROOT, ".agents", "skills", "lib.sh")
|
||||
|
||||
@pytest.fixture
|
||||
def split_sandbox(tmp_path):
|
||||
"""
|
||||
Creates a sandbox where workspace directory != state file directory,
|
||||
breaking the coincidence in mam_sandbox where <ws>/.mam/ matches AGENT_SESSIONS_YAML.
|
||||
"""
|
||||
shutil.copytree(os.path.join(REPO_ROOT, ".agents", "skills"), tmp_path / ".agents" / "skills")
|
||||
ws = tmp_path / "ws"
|
||||
(ws / ".mam").mkdir(parents=True)
|
||||
state_dir = tmp_path / "state"
|
||||
state_dir.mkdir()
|
||||
home = tmp_path / "home"
|
||||
(home / ".claude" / "projects").mkdir(parents=True)
|
||||
(home / ".gemini" / "antigravity-cli" / "conversations").mkdir(parents=True)
|
||||
|
||||
yaml_file = state_dir / "agent-sessions.yaml"
|
||||
db_file = state_dir / "agent-sessions.db"
|
||||
|
||||
return {
|
||||
"root": tmp_path,
|
||||
"ws": ws,
|
||||
"yaml": yaml_file,
|
||||
"db": db_file,
|
||||
"home": home,
|
||||
"lib": tmp_path / ".agents" / "skills" / "lib.sh"
|
||||
}
|
||||
|
||||
def run_find_workspace_uuid(sandbox, workspace, agent, env_extra=None, target=None):
|
||||
env = dict(os.environ)
|
||||
env["AGENT_SESSIONS_YAML"] = str(sandbox["yaml"])
|
||||
env["YAML_PATH"] = str(sandbox["yaml"])
|
||||
env["WORKSPACE_ROOT"] = str(sandbox["root"])
|
||||
env["HOME_DIR"] = str(sandbox["home"])
|
||||
env["CLAUDE_PROJECT_DIR"] = str(sandbox["home"] / ".claude" / "projects")
|
||||
if env_extra:
|
||||
env.update(env_extra)
|
||||
|
||||
target_arg = f" --target '{target}'" if target else ""
|
||||
cmd = f"source {sandbox['lib']} && find_workspace_uuid '{workspace}' '{agent}'{target_arg}"
|
||||
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env=env)
|
||||
return res
|
||||
|
||||
class TestB1Tier3Identity(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp_dir = tempfile.mkdtemp(prefix="mam_b1_test_")
|
||||
self.tmp_path = tempfile.TemporaryDirectory()
|
||||
self.sandbox = {
|
||||
"root": self.tmp_dir,
|
||||
"ws": os.path.join(self.tmp_dir, "ws"),
|
||||
"yaml": os.path.join(self.tmp_dir, "state", "agent-sessions.yaml"),
|
||||
"db": os.path.join(self.tmp_dir, "state", "agent-sessions.db"),
|
||||
"home": os.path.join(self.tmp_dir, "home"),
|
||||
"lib": LIB_SH
|
||||
}
|
||||
os.makedirs(self.sandbox["ws"], exist_ok=True)
|
||||
os.makedirs(os.path.join(self.sandbox["tmp_dir"] if "tmp_dir" in self.sandbox else self.tmp_dir, "state"), exist_ok=True)
|
||||
os.makedirs(os.path.join(self.sandbox["home"], ".claude", "projects"), exist_ok=True)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
||||
|
||||
def test_b1_tier3_honours_agent_sessions_yaml_path(split_sandbox):
|
||||
"""V-1: D1 — verify tier-3 honours AGENT_SESSIONS_YAML path when ws != state dir."""
|
||||
ws = str(split_sandbox["ws"])
|
||||
uuid = "b1-test-uuid-0001"
|
||||
|
||||
# Create mock agy conversation DB
|
||||
db_file = split_sandbox["home"] / ".gemini" / "antigravity-cli" / "conversations" / f"{uuid}.db"
|
||||
conn = sqlite3.connect(db_file)
|
||||
conn.execute("CREATE TABLE steps (id INT)")
|
||||
conn.execute("INSERT INTO steps VALUES (1)")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# Write identity info into yaml at state_dir, NOT ws/.mam/
|
||||
yaml_content = {
|
||||
"herdr_sessions": [],
|
||||
"agent_identities": {
|
||||
"agy": {
|
||||
"project_cwd": ws,
|
||||
"conversation_id": uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
import yaml
|
||||
with open(split_sandbox["yaml"], "w") as f:
|
||||
yaml.dump(yaml_content, f)
|
||||
|
||||
res = run_find_workspace_uuid(split_sandbox, ws, "agy")
|
||||
assert res.returncode == 0
|
||||
assert res.stdout.strip() == uuid
|
||||
|
||||
def test_b1_tier3_db_branch_survives_missing_pyyaml(split_sandbox):
|
||||
"""V-2: D3 — verify tier-3 DB branch works even when PyYAML module is absent."""
|
||||
ws = str(split_sandbox["ws"])
|
||||
uuid = "b1-test-uuid-0002"
|
||||
|
||||
# Create mock agy conversation DB
|
||||
db_file = split_sandbox["home"] / ".gemini" / "antigravity-cli" / "conversations" / f"{uuid}.db"
|
||||
conn_conv = sqlite3.connect(db_file)
|
||||
conn_conv.execute("CREATE TABLE steps (id INT)")
|
||||
conn_conv.execute("INSERT INTO steps VALUES (1)")
|
||||
conn_conv.commit()
|
||||
conn_conv.close()
|
||||
|
||||
# Create SQLite DB with state data
|
||||
conn = sqlite3.connect(split_sandbox["db"])
|
||||
conn.execute("CREATE TABLE state (id INTEGER PRIMARY KEY, data TEXT)")
|
||||
state_data = json.dumps({
|
||||
"herdr_sessions": [],
|
||||
"agent_identities": {
|
||||
"agy": {
|
||||
"project_cwd": ws,
|
||||
"conversation_id": uuid
|
||||
}
|
||||
}
|
||||
})
|
||||
conn.execute("INSERT INTO state (id, data) VALUES (1, ?)", (state_data,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# Stub out PyYAML via PYTHONPATH override with broken yaml package
|
||||
stub_dir = split_sandbox["root"] / "noyaml" / "yaml"
|
||||
stub_dir.mkdir(parents=True)
|
||||
with open(stub_dir / "__init__.py", "w") as f:
|
||||
f.write("raise ImportError('No module named yaml')\n")
|
||||
|
||||
env_extra = {"PYTHONPATH": str(split_sandbox["root"] / "noyaml")}
|
||||
res = run_find_workspace_uuid(split_sandbox, ws, "agy", env_extra=env_extra)
|
||||
assert res.returncode == 0
|
||||
assert res.stdout.strip() == uuid
|
||||
|
||||
def test_b1_tier3_corrupt_identities_still_exits_zero(split_sandbox):
|
||||
"""V-3: D4 — verify non-dict agent_identities handles gracefully and exits 0."""
|
||||
ws = str(split_sandbox["ws"])
|
||||
yaml_content = {
|
||||
"herdr_sessions": [],
|
||||
"agent_identities": "corrupted_string_not_dict"
|
||||
}
|
||||
import yaml
|
||||
with open(split_sandbox["yaml"], "w") as f:
|
||||
yaml.dump(yaml_content, f)
|
||||
# Also write to ws/.mam/ to trigger HEAD's path-guessing code path
|
||||
with open(split_sandbox["ws"] / ".mam" / "agent-sessions.yaml", "w") as f:
|
||||
yaml.dump(yaml_content, f)
|
||||
|
||||
res = run_find_workspace_uuid(split_sandbox, ws, "agy")
|
||||
assert res.returncode == 0
|
||||
assert res.stdout.strip() == ""
|
||||
|
||||
def test_b1_tier3_hermes_conversation_id_fallback(split_sandbox):
|
||||
"""V-4: D5 — verify hermes tier-3 fallback reads conversation_id from ai_agent."""
|
||||
ws = str(split_sandbox["ws"])
|
||||
uuid = "b1-test-uuid-hermes-0004"
|
||||
|
||||
# Create mock hermes state DB
|
||||
hermes_dir = split_sandbox["home"] / ".hermes"
|
||||
hermes_dir.mkdir(parents=True, exist_ok=True)
|
||||
conn_h = sqlite3.connect(hermes_dir / "state.db")
|
||||
conn_h.execute("CREATE TABLE sessions (id TEXT, cwd TEXT, started_at TEXT)")
|
||||
conn_h.execute("INSERT INTO sessions VALUES (?, ?, ?)", (uuid, ws, "2026-08-05"))
|
||||
conn_h.commit()
|
||||
conn_h.close()
|
||||
|
||||
yaml_content = {
|
||||
"herdr_sessions": [],
|
||||
"agent_identities": {
|
||||
"hermes": {
|
||||
"project_cwd": ws,
|
||||
"conversation_id": uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
import yaml
|
||||
with open(split_sandbox["yaml"], "w") as f:
|
||||
yaml.dump(yaml_content, f)
|
||||
with open(split_sandbox["ws"] / ".mam" / "agent-sessions.yaml", "w") as f:
|
||||
yaml.dump(yaml_content, f)
|
||||
|
||||
res = run_find_workspace_uuid(split_sandbox, ws, "hermes")
|
||||
assert res.returncode == 0
|
||||
assert res.stdout.strip() == uuid
|
||||
|
||||
def test_b1_tier3_refuses_foreign_workspace_identity(split_sandbox):
|
||||
"""V-5: Verify tier-3 identity is ignored if project_cwd does not match workspace."""
|
||||
ws = str(split_sandbox["ws"])
|
||||
foreign_ws = str(split_sandbox["root"] / "other_ws")
|
||||
uuid = "b1-test-uuid-0005"
|
||||
yaml_content = {
|
||||
"herdr_sessions": [],
|
||||
"agent_identities": {
|
||||
"agy": {
|
||||
"project_cwd": foreign_ws,
|
||||
"conversation_id": uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
import yaml
|
||||
with open(split_sandbox["yaml"], "w") as f:
|
||||
yaml.dump(yaml_content, f)
|
||||
|
||||
res = run_find_workspace_uuid(split_sandbox, ws, "agy")
|
||||
assert res.returncode == 0
|
||||
assert res.stdout.strip() == ""
|
||||
|
||||
def test_b1_tier3_absent_identities_is_silent(split_sandbox):
|
||||
"""V-6: Verify tier-3 gracefully returns empty string when agent_identities is absent."""
|
||||
ws = str(split_sandbox["ws"])
|
||||
yaml_content = {"herdr_sessions": []}
|
||||
import yaml
|
||||
with open(split_sandbox["yaml"], "w") as f:
|
||||
yaml.dump(yaml_content, f)
|
||||
|
||||
res = run_find_workspace_uuid(split_sandbox, ws, "agy")
|
||||
assert res.returncode == 0
|
||||
assert res.stdout.strip() == ""
|
||||
|
||||
def test_b1_load_state_json_surfaces_agent_identities(split_sandbox):
|
||||
"""V-7: Verify load_state_json preserves agent_identities in state blob."""
|
||||
ws = str(split_sandbox["ws"])
|
||||
uuid = "b1-test-uuid-0007"
|
||||
yaml_content = {
|
||||
"herdr_sessions": [],
|
||||
"agent_identities": {
|
||||
"agy": {
|
||||
"project_cwd": ws,
|
||||
"conversation_id": uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
import yaml
|
||||
with open(split_sandbox["yaml"], "w") as f:
|
||||
yaml.dump(yaml_content, f)
|
||||
|
||||
env = dict(os.environ)
|
||||
env["AGENT_SESSIONS_YAML"] = str(split_sandbox["yaml"])
|
||||
env["YAML_PATH"] = str(split_sandbox["yaml"])
|
||||
cmd = f"source {split_sandbox['lib']} && load_state_json"
|
||||
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env=env)
|
||||
assert res.returncode == 0
|
||||
state = json.loads(res.stdout.strip())
|
||||
assert state.get("agent_identities", {}).get("agy", {}).get("conversation_id") == uuid
|
||||
|
||||
def test_b1_tier3_does_not_read_yaml_mirror_behind_the_db(split_sandbox):
|
||||
"""V-8: Verify tier-3 does not prioritize YAML mirror when DB exists without identity."""
|
||||
ws = str(split_sandbox["ws"])
|
||||
uuid = "b1-test-uuid-0008"
|
||||
|
||||
# DB has no agent_identities
|
||||
conn = sqlite3.connect(split_sandbox["db"])
|
||||
conn.execute("CREATE TABLE state (id INTEGER PRIMARY KEY, data TEXT)")
|
||||
state_data = json.dumps({"herdr_sessions": []})
|
||||
conn.execute("INSERT INTO state (id, data) VALUES (1, ?)", (state_data,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# YAML has agent_identities
|
||||
yaml_content = {
|
||||
"herdr_sessions": [],
|
||||
"agent_identities": {
|
||||
"agy": {
|
||||
"project_cwd": ws,
|
||||
"conversation_id": uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
import yaml
|
||||
with open(split_sandbox["yaml"], "w") as f:
|
||||
yaml.dump(yaml_content, f)
|
||||
|
||||
res = run_find_workspace_uuid(split_sandbox, ws, "agy")
|
||||
assert res.returncode == 0
|
||||
# Must miss because DB is authority and state row lacks identity
|
||||
assert res.stdout.strip() == ""
|
||||
Reference in New Issue
Block a user