fix(reliability): hop governance belongs to rfplan, not boot reconciler (I2)
This commit is contained in:
+41
-2
@@ -3,6 +3,8 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'payload', 'use
|
||||
import server
|
||||
import mk8_guard
|
||||
|
||||
HOP_KEY = 'pineapd.wlan1mon.hop'
|
||||
|
||||
class GuardTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.calls = []
|
||||
@@ -30,19 +32,33 @@ class GuardTest(unittest.TestCase):
|
||||
return (0, '', '')
|
||||
mk8_guard.device_run = fake_run
|
||||
server.device_run = fake_run
|
||||
mk8_guard._GR_CACHE['data'] = None
|
||||
|
||||
def tearDown(self):
|
||||
server._iface_up = self.old_iface_up
|
||||
server.device_run = self.old_server_device_run
|
||||
mk8_guard.device_run = self.old_guard_device_run
|
||||
mk8_guard._GR_CACHE['data'] = None
|
||||
|
||||
def wanted_count(self):
|
||||
# Safe set minus wlan1mon.hop (rfplan owns hop), plus WANTED_EXTRA.
|
||||
return (len(server.PINEAPD_SAFE_UCI) - 1 +
|
||||
len(mk8_guard.WANTED_EXTRA))
|
||||
|
||||
def test_applies_all_wanted_when_missing(self):
|
||||
self.pool = 0
|
||||
result = mk8_guard.reconcile(clear_pool=False)
|
||||
sets = [c[2] for c in self.calls if c[:2] == ['uci', 'set']]
|
||||
self.assertEqual(len(sets),
|
||||
len(server.PINEAPD_SAFE_UCI) + len(mk8_guard.WANTED_EXTRA))
|
||||
self.assertEqual(len(sets), self.wanted_count())
|
||||
self.assertTrue(result['changed'])
|
||||
self.assertNotIn(HOP_KEY, {c[2].partition('=')[0]
|
||||
for c in self.calls
|
||||
if c[:2] == ['uci', 'set']})
|
||||
|
||||
def test_wanted_excludes_hop(self):
|
||||
wanted = mk8_guard._wanted()
|
||||
self.assertNotIn(HOP_KEY, wanted)
|
||||
self.assertEqual(len(wanted), self.wanted_count())
|
||||
|
||||
def test_clears_large_pool_only(self):
|
||||
self.pool = 25
|
||||
@@ -55,5 +71,28 @@ class GuardTest(unittest.TestCase):
|
||||
result = mk8_guard.reconcile(clear_pool=True)
|
||||
self.assertFalse(result['pool_cleared'])
|
||||
|
||||
def test_report_ignores_hop_and_caches(self):
|
||||
for key, value in mk8_guard._wanted().items():
|
||||
self.uci[key] = value
|
||||
self.uci[HOP_KEY] = '1' # attack-role baseline; must stay ignored
|
||||
report = mk8_guard.guard_report()
|
||||
self.assertTrue(report['in_sync'], report)
|
||||
gets = [c[2] for c in self.calls if c[:2] == ['uci', 'get']]
|
||||
self.assertNotIn(HOP_KEY, gets)
|
||||
n_after_first = len(self.calls)
|
||||
self.assertIs(mk8_guard.guard_report(), report)
|
||||
self.assertEqual(len(self.calls), n_after_first,
|
||||
'guard_report must serve from cache within TTL')
|
||||
mk8_guard._GR_CACHE['t'] -= mk8_guard.GR_TTL_SECONDS * 2
|
||||
mk8_guard.guard_report()
|
||||
self.assertGreater(len(self.calls), n_after_first)
|
||||
# reconcile mutates live state; it must invalidate the cached report.
|
||||
mk8_guard.guard_report()
|
||||
n_cached = len(self.calls)
|
||||
mk8_guard.reconcile(clear_pool=False)
|
||||
self.assertIsNone(mk8_guard._GR_CACHE['data'])
|
||||
mk8_guard.guard_report()
|
||||
self.assertGreater(len(self.calls), n_cached)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -32,6 +32,8 @@ class ReliabilityApiTest(unittest.TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
server.device_run = self.old_device_run
|
||||
import mk8_guard
|
||||
mk8_guard._GR_CACHE['data'] = None
|
||||
|
||||
def test_h_health_exposes_reliability_feed(self):
|
||||
status, h = server.h_health(None)
|
||||
|
||||
Reference in New Issue
Block a user