from __future__ import annotations import pytest from yuxi.channels.adapters.nostr.metrics import NostrMetrics, MetricsSnapshot class TestNostrMetrics: @pytest.fixture def metrics(self): return NostrMetrics() def test_increment_counter(self, metrics): metrics.increment("test.counter", 1) assert metrics.get_counter("test.counter") == 1 def test_increment_multiple(self, metrics): metrics.increment("test.counter", 5) metrics.increment("test.counter", 3) assert metrics.get_counter("test.counter") == 8 def test_increment_default_value(self, metrics): metrics.increment("default_val") assert metrics.get_counter("default_val") == 1 def test_set_gauge(self, metrics): metrics.set_gauge("test.gauge", 42.0) assert metrics.get_gauge("test.gauge") == 42.0 def test_adjust_gauge_positive(self, metrics): metrics.set_gauge("test.gauge", 10.0) metrics.adjust_gauge("test.gauge", 5.0) assert metrics.get_gauge("test.gauge") == 15.0 def test_adjust_gauge_negative(self, metrics): metrics.set_gauge("test.gauge", 10.0) metrics.adjust_gauge("test.gauge", -3.0) assert metrics.get_gauge("test.gauge") == 7.0 def test_snapshot_contains_counters(self, metrics): metrics.increment("event.count", 10) metrics.set_gauge("memory.size", 1024.0) snapshot = metrics.snapshot() assert isinstance(snapshot, MetricsSnapshot) assert snapshot.counters["event.count"] == 10 assert snapshot.gauges["memory.size"] == 1024.0 assert snapshot.timestamp > 0 def test_reset_clears_all(self, metrics): metrics.increment("counter1", 100) metrics.set_gauge("gauge1", 50.0) metrics.reset() assert metrics.get_counter("counter1") == 0 assert metrics.get_gauge("gauge1") == 0.0 def test_record_event_received(self, metrics): metrics.record_event_received(1) metrics.record_event_received(4) assert metrics.get_counter("event.received.total") == 2 assert metrics.get_counter("event.received.kind_1") == 1 assert metrics.get_counter("event.received.kind_4") == 1 def test_record_event_rejected(self, metrics): metrics.record_event_rejected("duplicate") metrics.record_event_rejected("rate_limit") assert metrics.get_counter("event.rejected.total") == 2 assert metrics.get_counter("event.rejected.duplicate") == 1 def test_record_event_accepted(self, metrics): metrics.record_event_accepted() metrics.record_event_accepted() assert metrics.get_counter("event.accepted.total") == 2 def test_record_relay_connect(self, metrics): metrics.record_relay_connect("wss://relay.example.com") assert metrics.get_counter("relay.connect.total") == 1 def test_record_relay_disconnect(self, metrics): metrics.record_relay_disconnect("wss://relay.example.com") assert metrics.get_counter("relay.disconnect.total") == 1 def test_record_relay_send_success(self, metrics): metrics.record_relay_send_success("wss://relay.example.com") assert metrics.get_counter("relay.send.success.total") == 1 def test_record_relay_send_failure(self, metrics): metrics.record_relay_send_failure("wss://relay.example.com") assert metrics.get_counter("relay.send.failure.total") == 1 def test_record_circuit_breaker_state(self, metrics): metrics.record_circuit_breaker_state("wss://relay.example.com", "closed") assert metrics.get_gauge("circuit_breaker.wss_relay_example_com.state") == 0 metrics.record_circuit_breaker_state("wss://relay.example.com", "open") assert metrics.get_gauge("circuit_breaker.wss_relay_example_com.state") == 2 def test_record_rate_limit_hit_sender(self, metrics): metrics.record_rate_limit_hit(sender_pubkey="abc123", global_hit=False) assert metrics.get_counter("rate_limit.sender.hit") == 1 def test_record_rate_limit_hit_global(self, metrics): metrics.record_rate_limit_hit(global_hit=True) assert metrics.get_counter("rate_limit.global.hit") == 1 def test_record_decrypt_success_nip04(self, metrics): metrics.record_decrypt_success("nip04") assert metrics.get_counter("decrypt.nip04.success") == 1 def test_record_decrypt_success_nip17(self, metrics): metrics.record_decrypt_success("nip17") assert metrics.get_counter("decrypt.nip17.success") == 1 def test_record_decrypt_failure(self, metrics): metrics.record_decrypt_failure("nip04") assert metrics.get_counter("decrypt.nip04.failure") == 1 def test_record_seen_tracker_size(self, metrics): metrics.record_seen_tracker_size(500) assert metrics.get_gauge("memory.seen_tracker_size") == 500 def test_record_inflight_size(self, metrics): metrics.record_inflight_size(10) assert metrics.get_gauge("memory.inflight_size") == 10 def test_safe_label_replaces_special_chars(self): result = NostrMetrics._safe_label("wss://relay.example.com/path/") assert "://" not in result assert "." not in result assert "/" not in result def test_snapshot_is_independent(self, metrics): metrics.increment("counter_x", 5) snap = metrics.snapshot() metrics.increment("counter_x", 10) assert snap.counters["counter_x"] == 5 assert metrics.get_counter("counter_x") == 15 def test_get_nonexistent_counter(self, metrics): assert metrics.get_counter("nonexistent") == 0 def test_get_nonexistent_gauge(self, metrics): assert metrics.get_gauge("nonexistent") == 0.0