"""The SNMP device form, the row it becomes, and what a page says about the newest poll (milestone 491).""" from datetime import datetime, timedelta, timezone from types import SimpleNamespace as NS import pytest from werkzeug.datastructures import MultiDict from plugins.snmp.readings import Reading from plugins.snmp.service import (FormError, RateKeeper, credentials, device_state, form_credentials, form_values, parse_form, readings_for) from steward.core.crypto import decrypt_secret, init_crypto, is_encrypted def setup_function(): init_crypto("unit-test-secret-key") def _form(**fields): presets = fields.pop("presets", ["system"]) md = MultiDict({"name": "core-switch", "address": "192.168.1.2", "port": "161", "version": "2c", **fields}) for p in presets: md.add("presets", p) return md def test_a_device_is_stored_with_its_community_encrypted(): values = parse_form(_form(community="s3cret", interfaces="1-2", presets=["system", "interfaces"])) assert values["name"] == "core-switch" and values["port"] == 161 and values["version"] == "2c" assert values["presets"] == ["system", "interfaces"] and values["interfaces"] == "1-2" assert is_encrypted(values["community"]) and decrypt_secret(values["community"]) == "s3cret" assert values["v3_user"] == "" and values["host_id"] is None def test_a_blank_community_is_public_on_a_new_device_and_kept_on_an_old_one(): assert decrypt_secret(parse_form(_form())["community"]) == "public" existing = NS(community="enc:kept", v3_auth_key="", v3_priv_key="") assert parse_form(_form(), existing)["community"] == "enc:kept" @pytest.mark.parametrize("fields, says", [ ({"name": ""}, "name"), ({"address": ""}, "address"), ({"port": "99999"}, "port"), ({"version": "4"}, "version"), ({"presets": []}, "at least one"), ({"presets": ["interfaces"]}, "interface numbers"), ({"custom": "1.3.6 Bad-Name"}, "Line 1"), ({"version": "3"}, "user name"), ({"version": "3", "v3_user": "mon", "v3_auth": "none", "v3_priv": "aes128"}, "Privacy needs authentication"), ({"version": "3", "v3_user": "mon", "v3_auth": "sha", "v3_priv": "none"}, "needs its key"), ({"version": "3", "v3_user": "mon", "v3_auth": "sha", "v3_auth_key": "short", "v3_priv": "none"}, "at least 8"), ]) def test_a_form_that_cannot_be_saved_says_what_to_fix(fields, says): with pytest.raises(FormError, match=says): parse_form(_form(**fields)) def test_a_v3_device_keeps_its_user_and_encrypted_keys_and_no_community(): values = parse_form(_form(version="3", v3_user="mon", v3_auth="sha256", v3_auth_key="authpass1", v3_priv="aes128", v3_priv_key="privpass1")) assert values["community"] == "" and values["v3_user"] == "mon" assert (values["v3_auth"], values["v3_priv"]) == ("sha256", "aes128") assert decrypt_secret(values["v3_auth_key"]) == "authpass1" and decrypt_secret(values["v3_priv_key"]) == "privpass1" row = NS(name="x", **{k: values[k] for k in ("version", "community", "v3_user", "v3_auth", "v3_auth_key", "v3_priv", "v3_priv_key")}) creds = credentials(row) assert (creds.user, creds.auth_key, creds.priv_key) == ("mon", "authpass1", "privpass1") def test_the_edit_form_never_carries_a_secret_back(): device = NS(name="ups", address="10.0.0.9", port=161, version="2c", v3_user="", v3_auth="none", v3_priv="none", presets=["ups"], interfaces="", custom="", host_id=None, community="enc:x", v3_auth_key="", v3_priv_key="") v = form_values(device) assert v["has_community"] and not v["has_auth_key"] assert "enc:x" not in str(v) def test_test_uses_what_is_typed_and_falls_back_to_the_stored_secret(): from steward.core.crypto import encrypt_secret existing = NS(community=encrypt_secret("stored"), v3_auth_key="", v3_priv_key="") address, port, creds = form_credentials(_form(port="1161"), existing) assert (address, port, creds.community) == ("192.168.1.2", 1161, "stored") assert form_credentials(_form(community="typed"), existing)[2].community == "typed" assert form_credentials(_form(), None)[2].community == "public" def test_a_row_that_no_longer_parses_is_asked_for_nothing(): row = NS(presets=["system"], interfaces="eth0", custom="", version="2c") assert readings_for(row) == [] def _row(**kw): return NS(**{"last_polled_at": None, "last_error": None, **kw}) def test_the_newest_poll_decides_what_the_page_says(): now = datetime(2026, 10, 8, 15, 0, tzinfo=timezone.utc) recent = now - timedelta(seconds=30) assert device_state(_row(), 60, now).reach == "pending" assert device_state(_row(last_polled_at=recent), 60, now).reach == "up" down = device_state(_row(last_polled_at=recent, last_error="No reply from x"), 60, now) assert (down.reach, down.says) == ("down", "No reply from x") assert device_state(_row(last_polled_at=now - timedelta(minutes=5)), 60, now).reach == "stale" def test_a_counter_becomes_a_rate_and_a_reset_is_skipped(): keeper = RateKeeper() rx = Reading("1.3", "if1_in", "Interface 1 in", "octets") t0 = datetime(2026, 10, 8, 15, 0, tzinfo=timezone.utc) assert keeper.rate("d1", rx, 1_000, t0) is None # nothing to compare yet assert keeper.rate("d1", rx, 7_000, t0 + timedelta(seconds=60)) == 800.0 # 100 B/s as bit/s assert keeper.rate("d1", rx, 50, t0 + timedelta(seconds=120)) is None # rebooted or wrapped assert keeper.rate("d1", rx, 650, t0 + timedelta(seconds=180)) == 80.0 keeper.forget("d1") assert keeper.rate("d1", rx, 9_999, t0 + timedelta(seconds=240)) is None