@@ -464,6 +464,82 @@ def test_func(x):
464464 test_func .clear_cache ()
465465
466466
467+ @pytest .mark .memory
468+ def test_prometheus_collector_collect_mocked ():
469+ """Test CachierCollector.collect() loop using mocked metric family types.
470+
471+ Covers lines 81-99 without requiring prometheus_client to be installed.
472+
473+ """
474+ from unittest .mock import MagicMock , patch
475+
476+ from cachier .exporters .prometheus import CachierCollector
477+
478+ @cachier (backend = "memory" , enable_metrics = True )
479+ def test_func (x ):
480+ return x * 2
481+
482+ test_func .clear_cache ()
483+ test_func (5 )
484+ test_func (5 )
485+
486+ exporter = PrometheusExporter (port = 0 , use_prometheus_client = False )
487+ exporter .register_function (test_func )
488+
489+ with (
490+ patch ("cachier.exporters.prometheus.CounterMetricFamily" , lambda * a , ** kw : MagicMock ()),
491+ patch ("cachier.exporters.prometheus.GaugeMetricFamily" , lambda * a , ** kw : MagicMock ()),
492+ ):
493+ collector = CachierCollector (exporter )
494+ results = list (collector .collect ())
495+ # 5 counter families + 3 gauge families
496+ assert len (results ) == 8
497+
498+ test_func .clear_cache ()
499+
500+
501+ @pytest .mark .memory
502+ def test_prometheus_start_prometheus_server_mocked ():
503+ """Test _start_prometheus_server and its MetricsHandler without prometheus_client.
504+
505+ Covers lines 285-329 (start() prom branch, MetricsHandler.do_GET, log_message).
506+
507+ """
508+ import sys
509+ import urllib .request
510+ from http .client import HTTPConnection
511+ from unittest .mock import MagicMock , patch
512+
513+ mock_exposition = MagicMock ()
514+ mock_exposition .generate_latest .return_value = b"# mocked metrics"
515+ mock_exposition .CONTENT_TYPE_LATEST = "text/plain"
516+
517+ prom_mock = MagicMock ()
518+ prom_mock .exposition = mock_exposition
519+
520+ exporter = PrometheusExporter (port = 0 , use_prometheus_client = False )
521+ # Manually inject prometheus state to trigger _start_prometheus_server path
522+ exporter ._prom_client = prom_mock
523+ exporter ._registry = MagicMock ()
524+
525+ with patch .dict (sys .modules , {"prometheus_client" : prom_mock , "prometheus_client.exposition" : mock_exposition }):
526+ exporter .start ()
527+ actual_port = exporter ._server .server_address [1 ]
528+ assert exporter ._server is not None
529+ try :
530+ response = urllib .request .urlopen (f"http://127.0.0.1:{ actual_port } /metrics" , timeout = 5 )
531+ assert b"# mocked metrics" in response .read ()
532+
533+ conn = HTTPConnection ("127.0.0.1" , actual_port )
534+ conn .request ("GET" , "/notfound" )
535+ resp = conn .getresponse ()
536+ assert resp .status == 404
537+ conn .close ()
538+ finally :
539+ exporter .stop ()
540+ assert exporter ._server is None
541+
542+
467543@pytest .mark .memory
468544def test_prometheus_collector_collect_skips_none_metrics ():
469545 """Test CachierCollector.collect() skips functions where metrics is None."""
0 commit comments