Delete Flutter-based multi-agent-mux-ui folder and root Melos/Flutter config files
This commit is contained in:
+2
-2
@@ -14,8 +14,8 @@ def mam_sandbox(tmp_path, monkeypatch):
|
||||
"""
|
||||
# 1. Copy the skill scripts to sandboxed tmp_path/skills and tmp_path/.agents/skills
|
||||
src_skills = "/home/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.agents/skills"
|
||||
shutil.copytree(src_skills, tmp_path / "skills", ignore=shutil.ignore_patterns('multi-agent-mux-ui'))
|
||||
shutil.copytree(src_skills, tmp_path / ".agents" / "skills", ignore=shutil.ignore_patterns('multi-agent-mux-ui'))
|
||||
shutil.copytree(src_skills, tmp_path / "skills")
|
||||
shutil.copytree(src_skills, tmp_path / ".agents" / "skills")
|
||||
|
||||
# 2. Create sandboxed directory structure
|
||||
mam_dir = tmp_path / ".mam"
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
import sqlite3
|
||||
import pytest
|
||||
import shutil
|
||||
import yaml
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Helper to run mutation on agent-sessions.yaml using atomic_dump_yaml in bash
|
||||
def run_mutation(mam_sandbox, mutation_str, env=None):
|
||||
lib_path = mam_sandbox / ".agents" / "skills" / "lib.sh"
|
||||
yaml_path = mam_sandbox / ".mam" / "agent-sessions.yaml"
|
||||
cmd_str = f"source {lib_path} && atomic_dump_yaml {yaml_path}"
|
||||
run_env = dict(os.environ)
|
||||
if env:
|
||||
run_env.update(env)
|
||||
res = subprocess.run(["bash", "-c", cmd_str], input=mutation_str, capture_output=True, text=True, env=run_env)
|
||||
return res
|
||||
|
||||
def test_stop_session_unvalidated_agent(mam_sandbox, mock_herdr, mock_agents):
|
||||
"""
|
||||
Test case 1: Unvalidated agent argument in stop_session.sh
|
||||
Verify that calling stop_session.sh with an invalid agent name returns code 2
|
||||
and exits with a clear error message to stderr.
|
||||
"""
|
||||
tmp_path = mam_sandbox
|
||||
stop_script = tmp_path / "skills" / "multi-agent-mux-stop" / "scripts" / "stop_session.sh"
|
||||
|
||||
# 1. Register a session first
|
||||
session_name = "invalid-agent-session-creator-claude"
|
||||
mutation = """
|
||||
d['herdr_sessions'] = [{
|
||||
'name': 'invalid-agent-session-creator-claude',
|
||||
'status': 'running',
|
||||
'role': 'Creator',
|
||||
'pane': {
|
||||
'cwd': 'WS_PLACEHOLDER',
|
||||
'pid': 8888,
|
||||
'cmd': 'claude',
|
||||
'cmd_full': 'claude'
|
||||
}
|
||||
}]
|
||||
""".replace("WS_PLACEHOLDER", str(tmp_path))
|
||||
res_mut = run_mutation(tmp_path, mutation)
|
||||
assert res_mut.returncode == 0
|
||||
|
||||
# 2. Try stopping with --agent invalid_agent and --purge-conversation
|
||||
cmd_stop = [
|
||||
"bash", str(stop_script),
|
||||
"--session", session_name,
|
||||
"--agent", "invalid_agent",
|
||||
"--purge-conversation",
|
||||
"--yes"
|
||||
]
|
||||
res_stop = subprocess.run(cmd_stop, capture_output=True, text=True, cwd=str(tmp_path))
|
||||
|
||||
# It returns code 2
|
||||
assert res_stop.returncode == 2
|
||||
|
||||
# And prints invalid agent type error to stderr
|
||||
assert "ERROR: invalid agent type 'invalid_agent'" in res_stop.stderr
|
||||
|
||||
# The registry entry is NOT removed
|
||||
yaml_path = tmp_path / ".mam" / "agent-sessions.yaml"
|
||||
with open(yaml_path, 'r') as f:
|
||||
reg = yaml.safe_load(f)
|
||||
assert len(reg.get("herdr_sessions", [])) == 1
|
||||
|
||||
|
||||
def test_special_character_workspace_slug_interpretation(mam_sandbox):
|
||||
"""
|
||||
Test case 2: Special character workspace slug interpretation
|
||||
Verify that workspace paths with only special characters result in derived session names
|
||||
that do NOT start with dashes, preventing option flags injection.
|
||||
"""
|
||||
tmp_path = mam_sandbox
|
||||
lib_path = tmp_path / ".agents" / "skills" / "lib.sh"
|
||||
|
||||
# Workspace consisting of purely special characters
|
||||
special_ws = tmp_path / "@#$*" / "@#$*"
|
||||
special_ws.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
cmd = f"source {lib_path} && derive_session_name '{special_ws}' 'claude'"
|
||||
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, cwd=str(tmp_path))
|
||||
assert res.returncode == 0
|
||||
derived_name = res.stdout.strip()
|
||||
|
||||
# Confirm that the derived name does NOT start with a dash and is default "ws"
|
||||
assert not derived_name.startswith("-")
|
||||
assert derived_name == "ws-creator-claude"
|
||||
|
||||
|
||||
def test_monitor_reconcile_race_on_purge(mam_sandbox, mock_herdr):
|
||||
"""
|
||||
Test case 3: Concurrency/Race condition during session purge
|
||||
Verify that if a session is being purged, the presence of the purging lock file
|
||||
prevents concurrent monitor checks from auto-registering it back.
|
||||
"""
|
||||
tmp_path = mam_sandbox
|
||||
reconcile_script = tmp_path / "skills" / "multi-agent-mux-monitor" / "scripts" / "reconcile.sh"
|
||||
|
||||
# 1. Register a running session in herdr and YAML
|
||||
session_name = "purged-race-creator-claude"
|
||||
with open(mock_herdr, 'r') as f:
|
||||
state = json.load(f)
|
||||
state["agents"][session_name] = {
|
||||
"status": "running",
|
||||
"agent": "claude",
|
||||
"cwd": str(tmp_path),
|
||||
"pid": 9999,
|
||||
"pane_id": "w1:p1",
|
||||
"command": "claude"
|
||||
}
|
||||
with open(mock_herdr, 'w') as f:
|
||||
json.dump(state, f, indent=2)
|
||||
|
||||
mutation = f"""
|
||||
d['herdr_sessions'] = [{{
|
||||
'name': '{session_name}',
|
||||
'status': 'running',
|
||||
'role': 'Creator',
|
||||
'pane': {{
|
||||
'cwd': 'WS_PLACEHOLDER',
|
||||
'pid': 9999,
|
||||
'cmd': 'claude',
|
||||
'cmd_full': 'claude'
|
||||
}}
|
||||
}}]
|
||||
""".replace("WS_PLACEHOLDER", str(tmp_path))
|
||||
res_mut = run_mutation(tmp_path, mutation)
|
||||
assert res_mut.returncode == 0
|
||||
|
||||
# 2. Simulate a purge: stop_session.sh deletes the registry row, but herdr session takes a moment to die.
|
||||
# We remove it from the YAML registry, and create the purging lock file.
|
||||
mutation_purge = f"""
|
||||
d['herdr_sessions'] = [s for s in d.get('herdr_sessions', []) if s.get('name') != '{session_name}']
|
||||
"""
|
||||
res_purge_mut = run_mutation(tmp_path, mutation_purge)
|
||||
assert res_purge_mut.returncode == 0
|
||||
|
||||
# Create the purging lock file
|
||||
purging_file = tmp_path / ".mam" / f"purging-{session_name}"
|
||||
purging_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
purging_file.touch()
|
||||
|
||||
# Confirm YAML registry has no sessions
|
||||
yaml_path = tmp_path / ".mam" / "agent-sessions.yaml"
|
||||
with open(yaml_path, 'r') as f:
|
||||
reg = yaml.safe_load(f)
|
||||
assert len(reg.get("herdr_sessions", [])) == 0
|
||||
|
||||
# 3. Run reconcile.sh once. It should NOT auto-register it back because the purging lock file exists!
|
||||
cmd_reconcile = ["bash", str(reconcile_script), "--once", "--emit-diff"]
|
||||
res_rec = subprocess.run(cmd_reconcile, capture_output=True, text=True, cwd=str(tmp_path))
|
||||
assert res_rec.returncode == 0
|
||||
|
||||
# Verify that the session has NOT been auto-registered back in YAML
|
||||
with open(yaml_path, 'r') as f:
|
||||
reg_after = yaml.safe_load(f)
|
||||
sessions_after = reg_after.get("herdr_sessions", [])
|
||||
assert len(sessions_after) == 0
|
||||
|
||||
|
||||
def test_mqtt_hmac_and_seq_validation(mam_sandbox):
|
||||
"""
|
||||
Test case 4: MQTT HMAC and sequence number validation
|
||||
Verify that mqtt_common correctly verifies HMAC signatures and drops messages with
|
||||
invalid signatures or out-of-order sequence numbers.
|
||||
"""
|
||||
# Import mqtt_common from sandboxed folder
|
||||
sys.path.insert(0, str(mam_sandbox / ".agents" / "skills" / "multi-agent-mux-delegate-job" / "scripts"))
|
||||
import mqtt_common
|
||||
|
||||
# 1. verify_hmac behaviour with no auth_token (PoC mode)
|
||||
payload_poc = {"data": {"val": 123}}
|
||||
assert mqtt_common.verify_hmac(payload_poc, None) is True
|
||||
|
||||
# 2. verify_hmac with auth_token and valid HMAC
|
||||
auth_token = "mysecrettoken"
|
||||
payload = {
|
||||
"job_id": "job123",
|
||||
"event": "started",
|
||||
"seq": 1,
|
||||
"data": {
|
||||
"val": 123
|
||||
}
|
||||
}
|
||||
|
||||
# Compute valid signature
|
||||
msg = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
|
||||
import hmac
|
||||
import hashlib
|
||||
sig = hmac.new(auth_token.encode(), msg, hashlib.sha256).hexdigest()
|
||||
|
||||
payload_signed = dict(payload)
|
||||
payload_signed["data"] = dict(payload["data"])
|
||||
payload_signed["data"]["hmac_sig"] = sig
|
||||
|
||||
assert mqtt_common.verify_hmac(payload_signed, auth_token) is True
|
||||
|
||||
# 3. verify_hmac with invalid signature
|
||||
payload_signed["data"]["hmac_sig"] = "invalidsignature"
|
||||
assert mqtt_common.verify_hmac(payload_signed, auth_token) is False
|
||||
Reference in New Issue
Block a user