"""Tests for CRDT flow dampening.""" import numpy as np from src.crdt.flow_dampening import ( CRDTFlowSystem, merge_flow_states, peer_penalty, simulate_drain_attack, update_peer_flow, ) class TestPeerFlowTracking: def test_new_peer_no_penalty(self): system = CRDTFlowSystem(default_total_value_ref=1000.0) assert peer_penalty(system, "alice") == 1.0 def test_update_records_flow(self): system = CRDTFlowSystem(default_total_value_ref=1000.0) system = update_peer_flow(system, "alice", 50.0, 0.0) assert "alice" in system.peers assert system.peers["alice"].tracker.current_flow == 50.0 def test_multiple_peers_independent(self): system = CRDTFlowSystem(default_total_value_ref=1000.0) system = update_peer_flow(system, "alice", 50.0, 0.0) system = update_peer_flow(system, "bob", 200.0, 0.0) assert system.peers["alice"].tracker.current_flow == 50.0 assert system.peers["bob"].tracker.current_flow == 200.0 def test_high_flow_triggers_penalty(self): system = CRDTFlowSystem( default_total_value_ref=1000.0, default_threshold=0.1, ) system = update_peer_flow(system, "alice", 200.0, 0.0) penalty = peer_penalty(system, "alice") assert penalty < 1.0 def test_pure_functional_no_mutation(self): system = CRDTFlowSystem(default_total_value_ref=1000.0) updated = update_peer_flow(system, "alice", 50.0, 0.0) assert "alice" not in system.peers assert "alice" in updated.peers class TestFlowMerge: def test_merge_commutativity(self): a = CRDTFlowSystem(default_total_value_ref=1000.0) a = update_peer_flow(a, "alice", 100.0, 0.0) b = CRDTFlowSystem(default_total_value_ref=1000.0) b = update_peer_flow(b, "alice", 50.0, 1.0) ab = merge_flow_states(a, b) ba = merge_flow_states(b, a) assert ab.peers["alice"].tracker.current_flow == ba.peers["alice"].tracker.current_flow assert ab.peers["alice"].tracker.last_update_time == ba.peers["alice"].tracker.last_update_time def test_merge_idempotency(self): a = CRDTFlowSystem(default_total_value_ref=1000.0) a = update_peer_flow(a, "alice", 100.0, 0.0) aa = merge_flow_states(a, a) assert aa.peers["alice"].tracker.current_flow == a.peers["alice"].tracker.current_flow def test_merge_takes_max_flow(self): a = CRDTFlowSystem(default_total_value_ref=1000.0) a = update_peer_flow(a, "alice", 100.0, 0.0) b = CRDTFlowSystem(default_total_value_ref=1000.0) b = update_peer_flow(b, "alice", 200.0, 0.0) merged = merge_flow_states(a, b) assert merged.peers["alice"].tracker.current_flow == 200.0 def test_merge_disjoint_peers(self): a = CRDTFlowSystem(default_total_value_ref=1000.0) a = update_peer_flow(a, "alice", 100.0, 0.0) b = CRDTFlowSystem(default_total_value_ref=1000.0) b = update_peer_flow(b, "bob", 50.0, 0.0) merged = merge_flow_states(a, b) assert "alice" in merged.peers assert "bob" in merged.peers class TestDrainSimulation: def test_simulation_returns_arrays(self): system = CRDTFlowSystem( default_total_value_ref=10000.0, default_threshold=0.1, ) result = simulate_drain_attack( system, "attacker", amounts=[100.0] * 10, times=[float(i) for i in range(10)], ) assert len(result["times"]) == 10 assert len(result["flows"]) == 10 assert len(result["penalties"]) == 10 def test_penalty_increases_with_drain(self): system = CRDTFlowSystem( default_total_value_ref=1000.0, default_threshold=0.1, ) result = simulate_drain_attack( system, "attacker", amounts=[200.0] * 5, times=[0.0, 0.1, 0.2, 0.3, 0.4], ) # Penalties should decrease (get worse) as flow accumulates assert result["penalties"][-1] <= result["penalties"][0]