753e16b84c
Helper py analogo a mark_claude_role: resuelve el sessionId de un Claude recien arrancado por su PID (sondeando sessions/<pid>.json) y escribe SOLO la clave parent_orchestrator en su goal.json, preservando el resto. Lo consume spawn_fleet_agent --parent para que el watcher de fleetview rutee los avisos del ejecutor a su orquestador padre. Tests: escribe+preserva, goal inexistente, parent vacio (ValueError), timeout sin crash. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
122 lines
3.9 KiB
Python
122 lines
3.9 KiB
Python
"""Tests para mark_claude_parent."""
|
|
|
|
import json
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from mark_claude_parent import mark_claude_parent
|
|
|
|
|
|
def _write_session(sessions_dir, pid, session_id):
|
|
"""Escribe un sessions/<pid>.json con el sessionId dado."""
|
|
os.makedirs(sessions_dir, exist_ok=True)
|
|
path = os.path.join(sessions_dir, f"{pid}.json")
|
|
with open(path, "w", encoding="utf-8") as fh:
|
|
json.dump({"sessionId": session_id, "cwd": "/tmp/whatever"}, fh)
|
|
return path
|
|
|
|
|
|
def _write_goal(goals_dir, session_id, goal):
|
|
"""Escribe un goal/<sessionId>.json con el dict dado."""
|
|
os.makedirs(goals_dir, exist_ok=True)
|
|
path = os.path.join(goals_dir, f"{session_id}.json")
|
|
with open(path, "w", encoding="utf-8") as fh:
|
|
json.dump(goal, fh)
|
|
return path
|
|
|
|
|
|
def _read_goal(goals_dir, session_id):
|
|
path = os.path.join(goals_dir, f"{session_id}.json")
|
|
with open(path, "r", encoding="utf-8") as fh:
|
|
return json.load(fh)
|
|
|
|
|
|
def test_sessions_presente_resuelve_y_escribe_parent_preservando_otros_campos(tmp_path):
|
|
sessions_dir = str(tmp_path / "sessions")
|
|
goals_dir = str(tmp_path / "goals")
|
|
pid = 4242
|
|
sid = "executor-abc-123"
|
|
parent = "orchestrator-xyz-789"
|
|
|
|
_write_session(sessions_dir, pid, sid)
|
|
# Goal preexistente con campos que NO deben perderse (incluido role).
|
|
_write_goal(
|
|
goals_dir,
|
|
sid,
|
|
{
|
|
"goal": "implementar fase 3",
|
|
"phase": "trabajando",
|
|
"role": "executor",
|
|
"dod_contract": {"capa1": "mecanica"},
|
|
},
|
|
)
|
|
|
|
res = mark_claude_parent(pid, parent, wait_s=2.0,
|
|
sessions_dir=sessions_dir, goals_dir=goals_dir)
|
|
|
|
assert res["ok"] is True
|
|
assert res["pid"] == pid
|
|
assert res["session_id"] == sid
|
|
assert res["parent_orchestrator"] == parent
|
|
assert res["path"] == os.path.join(goals_dir, f"{sid}.json")
|
|
|
|
goal = _read_goal(goals_dir, sid)
|
|
assert goal["parent_orchestrator"] == parent
|
|
# Todos los demas campos del goal se preservan intactos.
|
|
assert goal["goal"] == "implementar fase 3"
|
|
assert goal["phase"] == "trabajando"
|
|
assert goal["role"] == "executor"
|
|
assert goal["dod_contract"] == {"capa1": "mecanica"}
|
|
|
|
|
|
def test_parent_vacio_lanza_value_error_sin_escribir(tmp_path):
|
|
sessions_dir = str(tmp_path / "sessions")
|
|
goals_dir = str(tmp_path / "goals")
|
|
pid = 4242
|
|
sid = "executor-abc-123"
|
|
_write_session(sessions_dir, pid, sid)
|
|
|
|
with pytest.raises(ValueError):
|
|
mark_claude_parent(pid, " ", wait_s=2.0,
|
|
sessions_dir=sessions_dir, goals_dir=goals_dir)
|
|
|
|
# No escribio nada: el goals_dir ni siquiera deberia existir.
|
|
assert not os.path.exists(goals_dir)
|
|
|
|
|
|
def test_sessions_ausente_devuelve_timeout_sin_crash(tmp_path):
|
|
sessions_dir = str(tmp_path / "sessions")
|
|
goals_dir = str(tmp_path / "goals")
|
|
pid = 9999 # sin sessions/<pid>.json escrito
|
|
|
|
res = mark_claude_parent(pid, "orchestrator-xyz-789", wait_s=0.5,
|
|
sessions_dir=sessions_dir, goals_dir=goals_dir)
|
|
|
|
assert res["ok"] is False
|
|
assert res["pid"] == pid
|
|
assert "timeout" in res["error"]
|
|
assert f"{pid}.json" in res["error"]
|
|
# No se escribio ningun goal.
|
|
assert not os.path.exists(goals_dir)
|
|
|
|
|
|
def test_goal_inexistente_se_crea_con_solo_parent(tmp_path):
|
|
sessions_dir = str(tmp_path / "sessions")
|
|
goals_dir = str(tmp_path / "goals")
|
|
pid = 7
|
|
sid = "fresh-executor-uuid"
|
|
parent = "orchestrator-xyz-789"
|
|
_write_session(sessions_dir, pid, sid)
|
|
# No existe goal previo para esta sesion.
|
|
|
|
res = mark_claude_parent(pid, parent, wait_s=2.0,
|
|
sessions_dir=sessions_dir, goals_dir=goals_dir)
|
|
|
|
assert res["ok"] is True
|
|
assert res["session_id"] == sid
|
|
assert res["parent_orchestrator"] == parent
|
|
|
|
goal = _read_goal(goals_dir, sid)
|
|
assert goal == {"parent_orchestrator": parent}
|