#!/usr/bin/env python3 """ test_modulate_strategy.py — Unit and integration tests for: 1. Hierarchical override scoping: (input_type, subtype, agent) -> (input_type, None, agent) -> (input_type, subtype, None) -> (input_type, None, None) -> Builtin table 2. Dynamic strategy evaluation (`modulate.py eval` / `box-ctl.py strat-get`) 3. Atomic persistence to strategy.json 4. Override resets and isolation across input types """ import unittest import subprocess import json import os import sys import tempfile import shutil from pathlib import Path REPO_ROOT = Path("/home/super/Projects/NetVM") BIN_DIR = REPO_ROOT / "bin" BOX_CTL = BIN_DIR / "box-ctl.py" sys.path.insert(0, str(BIN_DIR)) import modulate class TestModulationHierarchy(unittest.TestCase): """Test strategy lookup cascade and overrides.""" def setUp(self): self.tmp_dir = tempfile.mkdtemp() self.strat_file = str(Path(self.tmp_dir) / "strategy.json") self.orig_default = modulate.DEFAULT_STRATEGY_FILE self.orig_fallback = modulate.FALLBACK_STRATEGY_FILE modulate.DEFAULT_STRATEGY_FILE = self.strat_file modulate.FALLBACK_STRATEGY_FILE = self.strat_file def tearDown(self): modulate.DEFAULT_STRATEGY_FILE = self.orig_default modulate.FALLBACK_STRATEGY_FILE = self.orig_fallback shutil.rmtree(self.tmp_dir, ignore_errors=True) def test_builtin_fallback(self): # Default builtin policy for manual DMs pol = modulate.modulate(modulate.InputType.MANUAL, agent="646") self.assertIsNotNone(pol) self.assertTrue(pol.track) self.assertEqual(pol.timeout_s, 3600) def test_agent_specific_override(self): # Set override for agent 'pip' on 'manual' modulate.set_strategy_override( "manual", None, "pip", track=False, timeout_s=1800, nudges=0, escalate="none", ) # Pip gets the custom override (track=False -> modulate returns None) pip_pol = modulate.modulate(modulate.InputType.MANUAL, agent="pip") self.assertIsNone(pip_pol) # 646 gets the default builtin policy other_pol = modulate.modulate(modulate.InputType.MANUAL, agent="646") self.assertTrue(other_pol.track) self.assertEqual(other_pol.timeout_s, 3600) def test_subtype_hierarchical_resolution(self): # Set type-level default modulate.set_strategy_override("health", None, None, timeout_s=900) # Set subtype override modulate.set_strategy_override("health", "DEGRADED", None, timeout_s=3600) # Health without subtype gets type-level pol1 = modulate.modulate(modulate.InputType.HEALTH, subtype="OK") self.assertEqual(pol1.timeout_s, 900) # Health with 'DEGRADED' subtype gets subtype-specific pol2 = modulate.modulate(modulate.InputType.HEALTH, subtype="DEGRADED") self.assertEqual(pol2.timeout_s, 3600) def test_override_reset(self): modulate.set_strategy_override("wake", None, "opm", timeout_s=600) self.assertEqual(modulate.modulate(modulate.InputType.WAKE, agent="opm", actionable=True).timeout_s, 600) modulate.reset_strategy_override("wake", None, "opm") # Reverts to builtin default (7200s for wake) self.assertEqual(modulate.modulate(modulate.InputType.WAKE, agent="opm", actionable=True).timeout_s, 7200) class TestStrategyRpc(unittest.TestCase): """Test box-ctl.py allowlisted RPC actions for strategy.""" def test_box_ctl_strat_list(self): cmd = [sys.executable, str(BOX_CTL), "strat-list"] res = subprocess.run(cmd, capture_output=True, text=True) self.assertEqual(res.returncode, 0) data = json.loads(res.stdout) self.assertTrue(data.get("ok")) self.assertIn("strategies", data) if __name__ == "__main__": unittest.main()