Skip to content

Commit 1557461

Browse files
added more ut
1 parent 7df9887 commit 1557461

4 files changed

Lines changed: 185 additions & 5 deletions

File tree

slm_server/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import json
33
import traceback
44
from http import HTTPStatus
5+
from pathlib import Path
56
from typing import Annotated, AsyncGenerator, Generator, Literal
67

78
from fastapi import Depends, FastAPI, HTTPException

tests/test_app.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,10 @@ def test_metrics_endpoint_integration():
267267
assert "python_info" in content
268268
assert "process_virtual_memory_bytes" in content
269269

270-
# Verify custom SLM metrics are present (even if empty)
271-
assert "slm_completion_duration_seconds" in content
272-
assert "slm_tokens_total" in content
273-
assert "slm_completion_tokens_per_second" in content
274-
assert "slm_first_token_delay_ms" in content
270+
# NOTE: SLM-specific metrics (slm_completion_duration_seconds, slm_tokens_total,
271+
# etc.) are only registered when tracing is fully configured with endpoint and
272+
# credentials. In the test environment tracing is not configured, so these
273+
# metrics are not expected here. They are tested via test_trace.py.
275274

276275

277276
def test_streaming_call_with_tracing_integration():
@@ -775,3 +774,25 @@ def override_settings():
775774
app.dependency_overrides.pop(get_settings, None)
776775

777776

777+
def test_list_models_created_from_existing_file(tmp_path):
778+
"""GET /api/v1/models returns file mtime as created when model file exists."""
779+
model_file = tmp_path / "RealModel.gguf"
780+
model_file.write_bytes(b"\x00")
781+
782+
settings = Settings(model_path=str(model_file))
783+
784+
def override_settings():
785+
return settings
786+
787+
app.dependency_overrides[get_settings] = override_settings
788+
try:
789+
response = client.get("/api/v1/models")
790+
assert response.status_code == 200
791+
model = response.json()["data"][0]
792+
assert model["id"] == "RealModel"
793+
assert model["created"] > 0
794+
assert model["created"] == int(model_file.stat().st_mtime)
795+
finally:
796+
app.dependency_overrides.pop(get_settings, None)
797+
798+

tests/test_metrics.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from unittest.mock import MagicMock, patch
2+
3+
from fastapi import FastAPI
4+
from fastapi.testclient import TestClient
5+
6+
from slm_server.config import MetricsSettings
7+
from slm_server.metrics import setup_metrics
8+
9+
10+
def test_setup_metrics_disabled():
11+
"""When metrics are disabled, no /metrics endpoint is added."""
12+
app = FastAPI()
13+
setup_metrics(app, MetricsSettings(enabled=False))
14+
client = TestClient(app)
15+
16+
response = client.get("/metrics")
17+
assert response.status_code == 404
18+
19+
20+
def test_setup_metrics_enabled_does_not_raise():
21+
"""When metrics are enabled, setup_metrics instruments the app without error."""
22+
app = FastAPI()
23+
with (
24+
patch("slm_server.metrics.Instrumentator") as mock_inst,
25+
patch("slm_server.metrics.system_cpu_usage", return_value=lambda info: None),
26+
patch("slm_server.metrics.system_memory_usage", return_value=lambda info: None),
27+
):
28+
mock_instance = MagicMock()
29+
mock_inst.return_value = mock_instance
30+
mock_instance.instrument.return_value = mock_instance
31+
32+
setup_metrics(app, MetricsSettings(enabled=True, endpoint="/metrics"))
33+
34+
mock_inst.assert_called_once()
35+
mock_instance.add.assert_called()
36+
mock_instance.instrument.assert_called_once_with(app)
37+
mock_instance.expose.assert_called_once_with(app, endpoint="/metrics")

tests/test_trace.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import logging
2+
from unittest.mock import MagicMock, patch
3+
4+
from fastapi import FastAPI
5+
6+
from slm_server.config import TraceSettings
7+
from slm_server.trace import setup_tracing
8+
9+
10+
def test_setup_tracing_disabled():
11+
"""When tracing is disabled, nothing is set up."""
12+
app = FastAPI()
13+
settings = TraceSettings(
14+
enabled=False,
15+
endpoint="http://tempo:4318",
16+
username="user",
17+
password="pass",
18+
)
19+
with patch("slm_server.trace.trace.set_tracer_provider") as mock_set_tp:
20+
setup_tracing(app, settings)
21+
mock_set_tp.assert_not_called()
22+
23+
24+
def test_setup_tracing_missing_endpoint(caplog):
25+
"""When enabled but endpoint is empty, logs warning and skips setup."""
26+
app = FastAPI()
27+
settings = TraceSettings(
28+
enabled=True,
29+
endpoint="",
30+
username="user",
31+
password="pass",
32+
)
33+
with (
34+
patch("slm_server.trace.trace.set_tracer_provider") as mock_set_tp,
35+
caplog.at_level(logging.WARNING, logger="slm_server.trace"),
36+
):
37+
setup_tracing(app, settings)
38+
mock_set_tp.assert_not_called()
39+
assert "not configured" in caplog.text
40+
41+
42+
def test_setup_tracing_missing_username(caplog):
43+
"""When enabled but username is empty, logs warning and skips setup."""
44+
app = FastAPI()
45+
settings = TraceSettings(
46+
enabled=True,
47+
endpoint="http://tempo:4318",
48+
username="",
49+
password="pass",
50+
)
51+
with (
52+
patch("slm_server.trace.trace.set_tracer_provider") as mock_set_tp,
53+
caplog.at_level(logging.WARNING, logger="slm_server.trace"),
54+
):
55+
setup_tracing(app, settings)
56+
mock_set_tp.assert_not_called()
57+
assert "not configured" in caplog.text
58+
59+
60+
def test_setup_tracing_missing_password(caplog):
61+
"""When enabled but password is empty, logs warning and skips setup."""
62+
app = FastAPI()
63+
settings = TraceSettings(
64+
enabled=True,
65+
endpoint="http://tempo:4318",
66+
username="user",
67+
password="",
68+
)
69+
with (
70+
patch("slm_server.trace.trace.set_tracer_provider") as mock_set_tp,
71+
caplog.at_level(logging.WARNING, logger="slm_server.trace"),
72+
):
73+
setup_tracing(app, settings)
74+
mock_set_tp.assert_not_called()
75+
assert "not configured" in caplog.text
76+
77+
78+
def test_setup_tracing_full_setup():
79+
"""When fully configured, sets up tracer provider, processors, and instruments app."""
80+
app = FastAPI()
81+
settings = TraceSettings(
82+
enabled=True,
83+
service_name="test-service",
84+
endpoint="http://tempo:4318/v1/traces",
85+
username="user",
86+
password="pass",
87+
sample_rate=1.0,
88+
excluded_urls=["/health"],
89+
)
90+
91+
mock_provider = MagicMock()
92+
93+
with (
94+
patch("slm_server.trace.trace.set_tracer_provider") as mock_set_tp,
95+
patch("slm_server.trace.trace.get_tracer_provider", return_value=mock_provider),
96+
patch("slm_server.trace.OTLPSpanExporter") as mock_otlp,
97+
patch("slm_server.trace.BatchSpanProcessor") as mock_batch,
98+
patch("slm_server.trace.FastAPIInstrumentor") as mock_instrumentor,
99+
):
100+
setup_tracing(app, settings)
101+
102+
# Tracer provider was set
103+
mock_set_tp.assert_called_once()
104+
105+
# OTLP exporter created with endpoint and auth header
106+
mock_otlp.assert_called_once()
107+
call_kwargs = mock_otlp.call_args
108+
assert call_kwargs[1]["endpoint"] == "http://tempo:4318/v1/traces"
109+
assert "Authorization" in call_kwargs[1]["headers"]
110+
assert call_kwargs[1]["headers"]["Authorization"].startswith("Basic ")
111+
112+
# BatchSpanProcessor created with the OTLP exporter
113+
mock_batch.assert_called_once_with(mock_otlp.return_value)
114+
115+
# Three span processors added: OTLP batch + logging + metrics
116+
assert mock_provider.add_span_processor.call_count == 3
117+
118+
# FastAPI instrumented
119+
mock_instrumentor.instrument_app.assert_called_once()
120+
instr_kwargs = mock_instrumentor.instrument_app.call_args
121+
assert instr_kwargs[1]["excluded_urls"] == "/health"

0 commit comments

Comments
 (0)