Skip to content

Commit 146e1b5

Browse files
authored
tests: add unit tests for telemetry module (#49)
also, use the pypi published `opentelemetry-instrumentation-eodag` package now that it is decoupled from `stac-fastapi-eodag`. Updates #13 --------- Signed-off-by: Aubin Lambaré <aubin.lambare@cs-soprasteria.com>
1 parent ace2899 commit 146e1b5

2 files changed

Lines changed: 157 additions & 32 deletions

File tree

pyproject.toml

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ name = "stac_fastapi.eodag"
33
version = "0.1.0"
44
description = "A stac-fastapi backend using EODAG"
55
readme = "README.md"
6-
authors = [
7-
{name = "CS GROUP - France (CSSI)", email = "eodag@csgroup.space"},
8-
]
9-
license = {file = "LICENSE"}
6+
authors = [{ name = "CS GROUP - France (CSSI)", email = "eodag@csgroup.space" }]
7+
license = { file = "LICENSE" }
108
requires-python = ">= 3.9"
119
dependencies = [
1210
"attr",
@@ -24,7 +22,7 @@ dependencies = [
2422
"brotli-asgi",
2523
"starlette",
2624
"typing_extensions",
27-
"shapely"
25+
"shapely",
2826
]
2927

3028
[project.urls]
@@ -33,15 +31,13 @@ Repository = "https://github.com/CS-SI/stac-fastapi-eodag.git"
3331
"Bug Tracker" = "https://github.com/CS-SI/stac-fastapi-eodag/issues/"
3432

3533
[project.optional-dependencies]
36-
server = [
37-
"uvicorn[standard]",
38-
]
34+
server = ["uvicorn[standard]"]
3935
telemetry = [
4036
"opentelemetry-api",
4137
"opentelemetry-sdk",
4238
"opentelemetry-exporter-otlp-proto-http",
43-
"opentelemetry-instrumentation-eodag @ git+https://github.com/CS-SI/opentelemetry-instrumentation-eodag",
44-
"opentelemetry-instrumentation-fastapi"
39+
"opentelemetry-instrumentation-eodag>=0.2.0",
40+
"opentelemetry-instrumentation-fastapi",
4541
]
4642
dev = [
4743
"stac_fastapi.eodag[server,telemetry]",
@@ -60,7 +56,7 @@ dev = [
6056
"stdlib-list",
6157
"tox",
6258
"tox-uv",
63-
"types-shapely"
59+
"types-shapely",
6460
]
6561

6662
[tool.mypy]
@@ -69,24 +65,15 @@ explicit_package_bases = true
6965
exclude = ["tests", ".venv"]
7066

7167
[[tool.mypy.overrides]]
72-
module = [
73-
"pygeofilter",
74-
"pygeofilter.*",
75-
"stac_fastapi",
76-
"stac_fastapi.*",
77-
]
68+
module = ["pygeofilter", "pygeofilter.*", "stac_fastapi", "stac_fastapi.*"]
7869
ignore_missing_imports = true
7970

8071
[tool.pytest.ini_options]
8172
addopts = "--disable-socket --allow-unix-socket --allow-hosts=localhost"
8273
asyncio_mode = "auto"
8374
asyncio_default_fixture_loop_scope = "session"
84-
testpaths = [
85-
"tests",
86-
]
87-
filterwarnings = [
88-
"ignore:Unused async fixture loop scope:pytest.PytestWarning"
89-
]
75+
testpaths = ["tests"]
76+
filterwarnings = ["ignore:Unused async fixture loop scope:pytest.PytestWarning"]
9077

9178
[tool.ruff]
9279
target-version = "py39" # minimum supported version
@@ -99,20 +86,26 @@ quote-style = "double"
9986
[tool.ruff.lint]
10087
# https://docs.astral.sh/ruff/rules/
10188
select = [
102-
"B", # flake8-bugbear
103-
"E", # pycodestyle errors
104-
"W", # pycodestyle warnings
105-
"F", # Pyflakes
106-
"C90", # mccabe (complexity)
107-
"I", # isort
108-
"T20", # flake8-print
109-
"D1", # pydocstyle - docstring presence only
89+
"B", # flake8-bugbear
90+
"E", # pycodestyle errors
91+
"W", # pycodestyle warnings
92+
"F", # Pyflakes
93+
"C90", # mccabe (complexity)
94+
"I", # isort
95+
"T20", # flake8-print
96+
"D1", # pydocstyle - docstring presence only
11097
]
11198

11299
[tool.ruff.lint.isort]
113100
known-first-party = ["stac_fastapi.eodag"]
114101
known-third-party = ["rasterio", "stac-pydantic", "fastapi", "stac_fastapi"]
115-
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
102+
section-order = [
103+
"future",
104+
"standard-library",
105+
"third-party",
106+
"first-party",
107+
"local-folder",
108+
]
116109

117110
[tool.ruff.lint.mccabe]
118111
max-complexity = 18

tests/test_telemetry.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2025, CS GROUP - France, https://www.cs-soprasteria.com
3+
#
4+
# This file is part of stac-fastapi-eodag project
5+
# https://www.github.com/CS-SI/stac-fastapi-eodag
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
"""Unit tests for telemetry module.
19+
20+
These tests verify that the telemetry functions can be
21+
initialized without errors, and that tracer and meter providers
22+
are correctly set up using in-memory exporters.
23+
"""
24+
25+
import pytest
26+
from fastapi import FastAPI
27+
from opentelemetry import metrics, trace
28+
from opentelemetry.sdk.metrics.export import InMemoryMetricReader
29+
from opentelemetry.sdk.resources import Resource
30+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
31+
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
32+
33+
from stac_fastapi.eodag.telemetry import (
34+
create_meter_provider,
35+
create_tracer_provider,
36+
instrument_eodag,
37+
instrument_fastapi,
38+
)
39+
40+
41+
@pytest.fixture
42+
def resource() -> Resource:
43+
"""Fixture returning a test Resource.
44+
45+
:return: resource with test service name
46+
"""
47+
return Resource.create({"service.name": "test-service"})
48+
49+
50+
def test_create_tracer_provider(resource: Resource) -> None:
51+
"""Test that ``create_tracer_provider`` sets up a tracer provider and records spans.
52+
53+
:param resource: fixture providing a test Resource
54+
"""
55+
# Patch exporter to in-memory
56+
exporter = InMemorySpanExporter()
57+
tracer_provider = create_tracer_provider(resource)
58+
tracer_provider.add_span_processor(SimpleSpanProcessor(exporter))
59+
60+
tracer = trace.get_tracer("test")
61+
with tracer.start_as_current_span("test-span"):
62+
pass
63+
64+
spans = exporter.get_finished_spans()
65+
assert len(spans) == 1
66+
assert spans[0].name == "test-span"
67+
68+
69+
def test_create_meter_provider(resource: Resource, monkeypatch: pytest.MonkeyPatch) -> None:
70+
"""Test that ``create_meter_provider`` sets up a meter provider and records metrics.
71+
72+
:param resource: fixture providing a test Resource
73+
:param monkeypatch: pytest fixture to patch dependencies
74+
"""
75+
reader = InMemoryMetricReader()
76+
77+
# Monkeypatch PeriodicExportingMetricReader to return our InMemoryMetricReader
78+
monkeypatch.setattr(
79+
"stac_fastapi.eodag.telemetry.PeriodicExportingMetricReader",
80+
lambda exporter=None: reader,
81+
)
82+
83+
# Call the function (will use patched reader)
84+
meter_provider = create_meter_provider(resource)
85+
86+
# Ensure it’s registered
87+
metrics.set_meter_provider(meter_provider)
88+
89+
meter = metrics.get_meter("test")
90+
counter = meter.create_counter("test_counter")
91+
counter.add(1)
92+
93+
reader.collect()
94+
collected = reader.get_metrics_data()
95+
96+
assert collected is not None
97+
assert any(record.resource.attributes["service.name"] == "test-service" for record in collected.resource_metrics)
98+
99+
100+
def test_instrument_fastapi_runs() -> None:
101+
"""Test that ``instrument_fastapi`` runs without raising errors.
102+
103+
:param resource: fixture providing a test Resource
104+
"""
105+
app = FastAPI()
106+
instrument_fastapi(app)
107+
108+
109+
def test_instrument_eodag_runs(monkeypatch: pytest.MonkeyPatch) -> None:
110+
"""Test that ``instrument_eodag`` runs without raising errors.
111+
112+
The real :class:`EODAGInstrumentor` is monkeypatched to avoid
113+
external dependencies.
114+
115+
:param monkeypatch: pytest fixture for monkeypatching
116+
"""
117+
118+
class DummyEODAG:
119+
pass
120+
121+
class DummyInstrumentor:
122+
def __init__(self, api):
123+
self.api = api
124+
125+
def instrument(self, tracer_provider, meter_provider):
126+
return "ok"
127+
128+
# Patch the EODAGInstrumentor used inside your module
129+
monkeypatch.setattr("stac_fastapi.eodag.telemetry.EODAGInstrumentor", DummyInstrumentor)
130+
131+
eodag = DummyEODAG()
132+
instrument_eodag(eodag)

0 commit comments

Comments
 (0)