From e9bfda14a6397965f85d9ae363da9be1e51cd8cc Mon Sep 17 00:00:00 2001 From: Diogo Ribeiro Date: Tue, 14 Apr 2026 21:49:28 +0100 Subject: [PATCH 1/5] test: add skeleton tests/test_cli_main.py --- tests/test_cli_main.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/test_cli_main.py diff --git a/tests/test_cli_main.py b/tests/test_cli_main.py new file mode 100644 index 0000000..63d9ddd --- /dev/null +++ b/tests/test_cli_main.py @@ -0,0 +1,9 @@ +import importlib + + +def test_import_cli_main(): + mod = importlib.import_module("llm_observability_analytics.cli.main") + assert mod is not None + + +# TODO: add tests for --help, --dry-run and config loading using tmp_path From c1b8e046915c7785f0d4bd9f4edd4e700041d58e Mon Sep 17 00:00:00 2001 From: Diogo Ribeiro Date: Tue, 14 Apr 2026 21:49:59 +0100 Subject: [PATCH 2/5] test: add skeleton tests/test_contracts_entities.py --- tests/test_contracts_entities.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/test_contracts_entities.py diff --git a/tests/test_contracts_entities.py b/tests/test_contracts_entities.py new file mode 100644 index 0000000..d9f6bba --- /dev/null +++ b/tests/test_contracts_entities.py @@ -0,0 +1,9 @@ +import importlib + + +def test_import_contracts_entities(): + mod = importlib.import_module("llm_observability_analytics.contracts.entities") + assert mod is not None + + +# TODO: add roundtrip dataclass tests using sample data From 5e3320cfb937864b78109ceb3cbeb29cc86e523f Mon Sep 17 00:00:00 2001 From: Diogo Ribeiro Date: Tue, 14 Apr 2026 21:50:39 +0100 Subject: [PATCH 3/5] test: add skeleton tests/test_events_interfaces.py --- tests/test_events_interfaces.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/test_events_interfaces.py diff --git a/tests/test_events_interfaces.py b/tests/test_events_interfaces.py new file mode 100644 index 0000000..ce1e1e4 --- /dev/null +++ b/tests/test_events_interfaces.py @@ -0,0 +1,9 @@ +import importlib + + +def test_import_events_interfaces(): + mod = importlib.import_module("llm_observability_analytics.events.interfaces") + assert mod is not None + + +# TODO: add tests that implement a dummy loader to validate interface behavior From 8f539a49ac537d3ff4e6ce1c66cc44c410b68f54 Mon Sep 17 00:00:00 2001 From: Diogo Ribeiro Date: Tue, 14 Apr 2026 21:51:07 +0100 Subject: [PATCH 4/5] test: add skeleton tests/test_events_loader.py --- tests/test_events_loader.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/test_events_loader.py diff --git a/tests/test_events_loader.py b/tests/test_events_loader.py new file mode 100644 index 0000000..3c25dd0 --- /dev/null +++ b/tests/test_events_loader.py @@ -0,0 +1,9 @@ +import importlib + + +def test_import_events_loader(): + mod = importlib.import_module("llm_observability_analytics.events.loader") + assert mod is not None + + +# TODO: add file-based loader tests using tmp_path From 1a2e972737be82317b45fe85d30c068d0f77dad1 Mon Sep 17 00:00:00 2001 From: Diogo Ribeiro Date: Tue, 14 Apr 2026 22:49:56 +0100 Subject: [PATCH 5/5] test: expand events.loader tests (valid/invalid cases) --- tests/test_events_loader.py | 109 ++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 5 deletions(-) diff --git a/tests/test_events_loader.py b/tests/test_events_loader.py index 3c25dd0..cbd889a 100644 --- a/tests/test_events_loader.py +++ b/tests/test_events_loader.py @@ -1,9 +1,108 @@ -import importlib +from __future__ import annotations +from datetime import datetime, timezone +from pathlib import Path -def test_import_events_loader(): - mod = importlib.import_module("llm_observability_analytics.events.loader") - assert mod is not None +import pytest +from llm_observability_analytics.contracts.models import ( + LLMInteractionEvent, + ModelExecutionContext, + TokenUsageRecord, + LatencyRecord, + SourceGroundingReference, + RetrievalTraceEvent, +) +from llm_observability_analytics.events.loader import ( + load_interaction_events, + load_retrieval_trace_events, +) + + +def _now_iso() -> str: + return datetime.now(tz=timezone.utc).isoformat() + + +def _make_interaction(query_id: str, trace_id: str) -> LLMInteractionEvent: + mc = ModelExecutionContext( + query_id=query_id, + trace_id=trace_id, + model_version="m-v", + dataset_version=None, + provider="prov", + model_name="name", + ) + tu = TokenUsageRecord( + query_id=query_id, + trace_id=trace_id, + model_version="m-v", + dataset_version=None, + input_tokens=1, + output_tokens=2, + recorded_at=datetime.now(timezone.utc), + ) + lat = LatencyRecord( + query_id=query_id, + trace_id=trace_id, + request_timestamp=datetime.now(timezone.utc), + response_timestamp=datetime.now(timezone.utc), + latency_ms=123, + ) + ref = SourceGroundingReference( + query_id=query_id, trace_id=trace_id, document_id="doc1", chunk_id="chk1" + ) + return LLMInteractionEvent( + query_id=query_id, + trace_id=trace_id, + request_timestamp=datetime.now(timezone.utc), + response_timestamp=datetime.now(timezone.utc), + prompt_text="p", + response_text="r", + model_context=mc, + token_usage=tu, + latency=lat, + retrieval_references=[ref], + ) + + +def _make_retrieval(query_id: str, trace_id: str) -> RetrievalTraceEvent: + ref = SourceGroundingReference( + query_id=query_id, trace_id=trace_id, document_id="doc1", chunk_id="chk1" + ) + return RetrievalTraceEvent( + query_id=query_id, + trace_id=trace_id, + retrieval_timestamp=datetime.now(timezone.utc), + query_text="hello", + retrieval_system="r", + top_k=1, + references=[ref], + ) + + +def test_load_interaction_events_success(tmp_path: Path) -> None: + p = tmp_path / "interactions.jsonl" + evt = _make_interaction("q1", "t1") + p.write_text(evt.to_json() + "\n", encoding="utf-8") + + out = load_interaction_events(p, max_events=10) + assert len(out) == 1 + assert out[0].query_id == "q1" + + +def test_load_retrieval_trace_events_success(tmp_path: Path) -> None: + p = tmp_path / "retrieval.jsonl" + evt = _make_retrieval("q2", "t2") + p.write_text(evt.to_json() + "\n", encoding="utf-8") + + out = load_retrieval_trace_events(p, max_events=10) + assert len(out) == 1 + assert out[0].query_id == "q2" + + +def test_load_invalid_json_raises(tmp_path: Path) -> None: + p = tmp_path / "broken.jsonl" + p.write_text("{not-json}\n", encoding="utf-8") + with pytest.raises(ValueError): + load_interaction_events(p, max_events=10) -# TODO: add file-based loader tests using tmp_path