spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
1import math23import pytest45from internetpressure.config import ConfigError, build_config, load_file_config6from internetpressure.engine import scoring as S789def test_robust_z_basic_and_clipping():10 assert S.robust_z(30, 20, 2) == 5.011 assert S.robust_z(200, 20, 2) == 8.0 # clipped high12 assert S.robust_z(0, 20, 2) == -3.0 # clipped low13 assert S.robust_z(None, 20, 2) is None14 assert S.robust_z(20, None, 2) is None151617def test_robust_z_mad_floor_prevents_explosion():18 # ultra-tight baseline (MAD 0.01 ms on a 20 ms median): floor = max(1.0, 0.05*20=1.0) → z uses 1.019 assert S.robust_z(25, 20, 0.01) == 5.020 # relative floor on large medians21 assert S.robust_z(1100, 1000, 1) == pytest.approx(2.0) # floor 50 ms222324def test_mad_from_iqr():25 assert S.mad_from_iqr(10, 20) == pytest.approx(7.413)26 assert S.mad_from_iqr(None, 20) is None272829def test_z_to_stress_ramp():30 assert S.z_to_stress(None) == 031 assert S.z_to_stress(0.5) == 032 assert S.z_to_stress(1.0) == 033 assert S.z_to_stress(3.5) == pytest.approx(0.5)34 assert S.z_to_stress(6) == 135 assert S.z_to_stress(8) == 1363738def test_rate_stress():39 assert S.rate_stress(0.0, 0.0) == 040 assert S.rate_stress(0.25, 0.0) == 1.041 assert S.rate_stress(0.125, 0.0) == pytest.approx(0.5)42 # a noisy target with a 10 % baseline failure rate needs 30 % excess for full stress43 assert S.rate_stress(0.25, 0.10) == pytest.approx(0.5)44 assert S.rate_stress(0.05, 0.10) == 0454647def test_ratio_stress():48 assert S.ratio_stress(100, 100) == 049 assert S.ratio_stress(150, 100) == 050 assert S.ratio_stress(375, 100) == pytest.approx(0.5)51 assert S.ratio_stress(1000, 100) == 152 assert S.ratio_stress(10, 0) == 0535455def test_saturate_bounds_and_shape():56 assert S.saturate(0) == 057 assert S.saturate(1) == pytest.approx(100)58 mid = S.saturate(0.5)59 assert 55 < mid < 75 # concave: half stress is well above 5060 assert S.saturate(0.1) < S.saturate(0.2) < S.saturate(0.5)61 assert S.saturate(0.5, k=0) == 50626364def test_coverage_factor():65 assert S.coverage_factor(0, 24) == 066 assert S.coverage_factor(2, 24) == 0 # below 20 %67 assert S.coverage_factor(24, 24) == 168 assert 0 < S.coverage_factor(12, 24) < 169 assert S.coverage_factor(5, 0) == 1707172def test_weighted_stress_and_breadth():73 pairs = [74 S.PairStress("a", 1.0, 2.0),75 S.PairStress("b", 0.0, 1.0),76 S.PairStress("c", 0.5, 1.0),77 S.PairStress("d", 0.9, 0.0), # zero weight ignored78 ]79 stress, tw, n = S.weighted_stress(pairs)80 assert n == 3 and tw == 4.081 assert stress == pytest.approx((2.0 + 0 + 0.5) / 4.0)82 assert S.breadth(pairs, threshold=0.4) == pytest.approx(3.0 / 4.0)83 assert S.weighted_stress([]) == (0.0, 0.0, 0)848586def test_component_score_renormalises_missing_signals():87 w = {"a": 0.5, "b": 0.3, "c": 0.2}88 assert S.component_score({}, w) == 089 full = S.component_score({"a": 1, "b": 1, "c": 1}, w)90 assert full == pytest.approx(100)91 # only "a" observed at full stress → still 100 (renormalised), not 5092 assert S.component_score({"a": 1}, w) == pytest.approx(100)93 assert S.component_score({"a": 0, "b": 0, "c": 0}, w) == 0949596def test_global_score_weights_from_config():97 cfg = load_file_config()98 comps = {c: 50.0 for c in cfg.weights}99 score, contrib = S.global_score(comps, cfg.weights)100 assert score == pytest.approx(50.0)101 assert sum(contrib.values()) == pytest.approx(50.0)102 # missing routing → renormalised over the rest103 comps["routing"] = None104 score2, contrib2 = S.global_score(comps, cfg.weights)105 assert score2 == pytest.approx(50.0)106 assert "routing" not in contrib2107108109def test_global_score_example_from_spec():110 weights = {"routing": .25, "latency": .20, "dns": .15, "availability": .15, "http_tls": .10, "path": .10,111 "corroboration": .05}112 comps = {"routing": 57, "latency": 44, "dns": 19, "availability": 37, "http_tls": 28, "path": 51, "corroboration": 0}113 score, _ = S.global_score(comps, weights)114 assert score == pytest.approx(57 * .25 + 44 * .2 + 19 * .15 + 37 * .15 + 28 * .1 + 51 * .1)115116117def test_confidence_monotone():118 low = S.confidence_score(probes=1, probe_regions=1, signals_agreeing=1, samples=2, min_samples=24)119 high = S.confidence_score(probes=8, probe_regions=4, signals_agreeing=3, samples=100, min_samples=24,120 duration_s=1200, bgp_corroborated=True, external_corroborated=True)121 assert 0 < low < high <= 1.0122123124def test_velocity_and_acceleration():125 pts = [(t * 60.0, 20 + t * 0.5) for t in range(0, 30)] # +0.5/min = +30/h126 v, a = S.velocity(pts)127 assert v == pytest.approx(30.0, rel=1e-3)128 assert a == pytest.approx(0.0, abs=1e-6)129 pts2 = [(t * 60.0, 20 + 0.02 * t * t) for t in range(0, 30)] # accelerating130 v2, a2 = S.velocity(pts2)131 assert v2 > 0 and a2 > 0132 assert S.velocity(pts[:3]) == (None, None)133134135def test_volatility_and_trend():136 assert S.volatility([1, 1, 1]) == 0137 assert S.volatility([1, 2]) is None138 assert S.volatility([10, 20, 30]) == pytest.approx(10.0)139 assert S.trend_word(0.4) == "stable"140 assert S.trend_word(5) == "rising"141 assert S.trend_word(-5) == "falling"142 assert S.trend_word(None) == "stable"143144145def test_config_validation_rejects_bad_weights():146 cfg = load_file_config()147 raw = dict(cfg.raw)148 raw["pressure_weights"] = {**cfg.weights, "routing": 0.5}149 with pytest.raises(ConfigError):150 build_config(raw)151 raw["pressure_weights"] = {k: v for k, v in cfg.weights.items() if k != "path"}152 with pytest.raises(ConfigError):153 build_config(raw)154155156def test_levels_from_config():157 cfg = load_file_config()158 assert cfg.level_for(5)[0] == "calm"159 assert cfg.level_for(10)[0] == "calm"160 assert cfg.level_for(10.1)[0] == "normal"161 assert cfg.level_for(42.7)[0] == "stressed"162 assert cfg.level_for(99)[0] == "extreme"163 assert cfg.level_for(None)[0] == "unknown"164 assert not math.isnan(cfg.importance_weight(5))165 assert cfg.importance_weight(5) > cfg.importance_weight(1)166