#!/usr/bin/env python3 """ test_variables_engine.py — Unit and integration tests for: 1. Schema validation (int, float, bool, string, ranges, choices) 2. Dual-synced atomic persistence between local and /srv/box/ 3. Append-only audit history logging to variables-history.jsonl 4. Atomic rollback to previous revisions 5. box-ctl.py allowlisted RPC actions for variables """ 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" VARS_PY = BIN_DIR / "variables.py" BOX_CTL = BIN_DIR / "box-ctl.py" sys.path.insert(0, str(BIN_DIR)) import variables class TestVariablesEngine(unittest.TestCase): """Test schema enforcement and in-memory variable operations.""" def setUp(self): self.tmp_dir = tempfile.mkdtemp() self.var_file = Path(self.tmp_dir) / "variables.json" self.hist_file = Path(self.tmp_dir) / "variables-history.jsonl" self.engine = variables.Variables(path=str(self.var_file), history_path=str(self.hist_file)) def tearDown(self): shutil.rmtree(self.tmp_dir, ignore_errors=True) def test_default_values_load(self): val = self.engine.get("loop_health_threshold") self.assertEqual(val, 0.5) val_sec = self.engine.get("loop_health_interval_s") self.assertEqual(val_sec, 900) def test_schema_validation_out_of_range(self): # loop_health_threshold range: 0.0 .. 1.0 with self.assertRaises(ValueError): self.engine.set("loop_health_threshold", 1.5) with self.assertRaises(ValueError): self.engine.set("loop_health_threshold", -0.1) def test_schema_validation_type_coercion(self): self.engine.set("loop_health_interval_s", "1200") self.assertEqual(self.engine.get("loop_health_interval_s"), 1200) def test_history_logging_and_rollback(self): initial = self.engine.get("max_nudge_count") self.engine.set("max_nudge_count", 5, by="operator_test") self.assertEqual(self.engine.get("max_nudge_count"), 5) # Check history recorded hist = self.engine.history(name="max_nudge_count", limit=5) self.assertGreaterEqual(len(hist), 1) self.assertEqual(hist[0]["action"], "set") self.assertEqual(hist[0]["new_value"], 5) # Rollback to initial rb_val = self.engine.rollback("max_nudge_count", by="operator_test") self.assertEqual(rb_val, initial) self.assertEqual(self.engine.get("max_nudge_count"), initial) def test_reset_variable(self): self.engine.set("sweeper_interval_s", 120) self.assertEqual(self.engine.get("sweeper_interval_s"), 120) res_val = self.engine.reset("sweeper_interval_s") self.assertEqual(res_val, 60) self.assertEqual(self.engine.get("sweeper_interval_s"), 60) class TestVariablesCliAndRpc(unittest.TestCase): """Test CLI commands and box-ctl RPC actions for variables.""" def test_box_ctl_vars_list(self): cmd = [sys.executable, str(BOX_CTL), "vars-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("variables", data) self.assertIn("loop_health_threshold", data["variables"]) def test_box_ctl_vars_history(self): cmd = [sys.executable, str(BOX_CTL), "vars-history", "loop_health_threshold", "5"] 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.assertIsInstance(data.get("history"), list) if __name__ == "__main__": unittest.main()