"""Tests for metrics builder functions.""" from meshmon.html import ( _build_traffic_table_rows, build_companion_metrics, build_node_details, build_radio_config, build_repeater_metrics, ) class TestBuildRepeaterMetrics: """Tests for build_repeater_metrics function.""" def test_returns_dict(self, sample_repeater_metrics): """Returns a dictionary.""" # build_repeater_metrics takes a row dict (firmware field names) result = build_repeater_metrics(sample_repeater_metrics) assert isinstance(result, dict) def test_returns_dict_structure(self, sample_repeater_metrics): """Returns dict with expected keys.""" result = build_repeater_metrics(sample_repeater_metrics) # Should have critical_metrics, secondary_metrics, traffic_metrics assert "critical_metrics" in result assert "secondary_metrics" in result assert "traffic_metrics" in result def test_critical_metrics_is_list(self, sample_repeater_metrics): """Critical metrics is a list.""" result = build_repeater_metrics(sample_repeater_metrics) assert isinstance(result["critical_metrics"], list) def test_handles_none(self): """Handles None row.""" result = build_repeater_metrics(None) assert isinstance(result, dict) assert result["critical_metrics"] == [] def test_handles_empty_dict(self): """Handles empty dict.""" result = build_repeater_metrics({}) assert isinstance(result, dict) class TestBuildCompanionMetrics: """Tests for build_companion_metrics function.""" def test_returns_dict(self, sample_companion_metrics): """Returns a dictionary.""" result = build_companion_metrics(sample_companion_metrics) assert isinstance(result, dict) def test_returns_dict_structure(self, sample_companion_metrics): """Returns dict with expected keys.""" result = build_companion_metrics(sample_companion_metrics) assert "critical_metrics" in result assert "secondary_metrics" in result assert "traffic_metrics" in result def test_handles_none(self): """Handles None row.""" result = build_companion_metrics(None) assert isinstance(result, dict) assert result["critical_metrics"] == [] def test_handles_empty_dict(self): """Handles empty dict.""" result = build_companion_metrics({}) assert isinstance(result, dict) class TestBuildNodeDetails: """Tests for build_node_details function.""" def test_returns_list(self, configured_env): """Returns a list of detail items.""" result = build_node_details("repeater") assert isinstance(result, list) def test_items_have_label_value(self, configured_env): """Each item has label and value.""" result = build_node_details("repeater") for item in result: assert isinstance(item, dict) assert "label" in item assert "value" in item def test_includes_hardware_info(self, configured_env, monkeypatch): """Includes hardware model info.""" monkeypatch.setenv("REPEATER_HARDWARE", "Test LoRa Device") import meshmon.env meshmon.env._config = None result = build_node_details("repeater") # Should have hardware in one of the items hardware = next(item for item in result if item.get("label") == "Hardware") assert hardware["value"] == "Test LoRa Device" def test_different_roles(self, configured_env): """Different roles return details.""" repeater_details = build_node_details("repeater") companion_details = build_node_details("companion") assert isinstance(repeater_details, list) assert isinstance(companion_details, list) class TestBuildRadioConfig: """Tests for build_radio_config function.""" def test_returns_list(self, configured_env): """Returns a list of config items.""" result = build_radio_config() assert isinstance(result, list) def test_items_have_label_value(self, configured_env): """Each item has label and value.""" result = build_radio_config() for item in result: assert isinstance(item, dict) assert "label" in item assert "value" in item def test_includes_frequency_when_set(self, configured_env, monkeypatch): """Includes frequency when configured.""" monkeypatch.setenv("RADIO_FREQUENCY", "869.618 MHz") import meshmon.env meshmon.env._config = None result = build_radio_config() freq = next(item for item in result if item.get("label") == "Frequency") assert freq["value"] == "869.618 MHz" def test_handles_missing_config(self, configured_env): """Returns list even with default config.""" result = build_radio_config() assert isinstance(result, list) class TestBuildTrafficTableRows: """Tests for _build_traffic_table_rows function.""" def test_returns_list(self): """Returns a list of rows.""" # Input is list of traffic metric dicts traffic_metrics = [ {"label": "RX", "value": "100", "raw_value": 100, "unit": "/min"}, {"label": "TX", "value": "50", "raw_value": 50, "unit": "/min"}, ] rows = _build_traffic_table_rows(traffic_metrics) assert isinstance(rows, list) def test_rows_have_structure(self): """Each row has expected structure.""" traffic_metrics = [ {"label": "RX", "value": "100", "raw_value": 100, "unit": "/min"}, {"label": "TX", "value": "50", "raw_value": 50, "unit": "/min"}, ] rows = _build_traffic_table_rows(traffic_metrics) for row in rows: assert isinstance(row, dict) assert "label" in row assert "rx" in row assert "tx" in row assert "rx_raw" in row assert "tx_raw" in row assert "unit" in row def test_handles_empty_list(self): """Handles empty traffic metrics list.""" rows = _build_traffic_table_rows([]) assert isinstance(rows, list) assert len(rows) == 0 def test_combines_rx_tx_pairs(self): """Combines RX and TX into single row.""" traffic_metrics = [ {"label": "Flood RX", "value": "100", "raw_value": 100, "unit": "/min"}, {"label": "Flood TX", "value": "50", "raw_value": 50, "unit": "/min"}, ] rows = _build_traffic_table_rows(traffic_metrics) # Should have one "Flood" row with both rx and tx assert len(rows) == 1 assert rows[0]["label"] == "Flood" assert rows[0]["rx"] == "100" assert rows[0]["tx"] == "50"