2026-06-28 17:14:17 +09:00
|
|
|
"""Tests for example files in examples/grafana/."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
from pathlib import Path
|
2026-07-03 03:54:26 +09:00
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from collections.abc import Iterator
|
2026-06-28 17:14:17 +09:00
|
|
|
|
|
|
|
|
_EXAMPLES_DIR = Path(__file__).parent.parent / "examples" / "grafana"
|
|
|
|
|
_DASHBOARDS_DIR = _EXAMPLES_DIR / "dashboards"
|
|
|
|
|
|
|
|
|
|
|
2026-07-03 03:54:26 +09:00
|
|
|
def _dashboard_json_files() -> list[Path]:
|
|
|
|
|
"""Return bundled Grafana dashboard JSON files in deterministic order."""
|
|
|
|
|
return sorted(_DASHBOARDS_DIR.glob("*.json"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(params=_dashboard_json_files(), ids=lambda path: path.name)
|
|
|
|
|
def dashboard_path(request: pytest.FixtureRequest) -> Iterator[Path]:
|
|
|
|
|
"""Yield one bundled Grafana dashboard JSON file per test case."""
|
|
|
|
|
return request.param
|
|
|
|
|
|
|
|
|
|
|
2026-06-28 17:14:17 +09:00
|
|
|
class TestGrafanaExamples:
|
|
|
|
|
"""Validate structure and content of bundled Grafana example files."""
|
|
|
|
|
|
2026-07-03 03:54:26 +09:00
|
|
|
def test_dashboard_json_files_are_present(self) -> None:
|
|
|
|
|
"""At least one dashboard JSON file is bundled."""
|
|
|
|
|
assert _dashboard_json_files(), "No dashboard JSON files found"
|
2026-06-28 17:14:17 +09:00
|
|
|
|
2026-07-03 03:54:26 +09:00
|
|
|
def test_dashboard_json_file_is_valid_json(self, dashboard_path: Path) -> None:
|
|
|
|
|
"""Each dashboard JSON file parses to a JSON object."""
|
|
|
|
|
content = dashboard_path.read_text(encoding="utf-8")
|
|
|
|
|
obj = json.loads(content)
|
|
|
|
|
assert isinstance(obj, dict), (
|
|
|
|
|
f"{dashboard_path.name} root must be a JSON object"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("private_pattern", ["password", "api_key", "apikey"])
|
|
|
|
|
def test_dashboard_json_has_no_private_placeholders(
|
|
|
|
|
self,
|
|
|
|
|
dashboard_path: Path,
|
|
|
|
|
private_pattern: str,
|
|
|
|
|
) -> None:
|
2026-06-28 17:14:17 +09:00
|
|
|
"""Dashboard JSON files contain no obvious credential placeholders."""
|
2026-07-03 03:54:26 +09:00
|
|
|
content = dashboard_path.read_text(encoding="utf-8").lower()
|
|
|
|
|
assert private_pattern not in content, (
|
|
|
|
|
f"{dashboard_path.name} contains {private_pattern!r}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_dashboard_json_uses_grafana_views(self, dashboard_path: Path) -> None:
|
|
|
|
|
"""Each dashboard JSON file queries grafana_* views."""
|
|
|
|
|
content = dashboard_path.read_text(encoding="utf-8")
|
|
|
|
|
assert "grafana_" in content, (
|
|
|
|
|
f"{dashboard_path.name} must contain queries against grafana_* views"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("field", ["uid", "title"])
|
|
|
|
|
def test_dashboard_json_has_required_field(
|
|
|
|
|
self,
|
|
|
|
|
dashboard_path: Path,
|
|
|
|
|
field: str,
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Each dashboard JSON file has a non-empty uid and title field."""
|
|
|
|
|
obj = json.loads(dashboard_path.read_text(encoding="utf-8"))
|
|
|
|
|
assert obj.get(field), f"{dashboard_path.name} must have a {field}"
|
2026-06-28 17:14:17 +09:00
|
|
|
|
|
|
|
|
def test_expected_dashboards_present(self) -> None:
|
|
|
|
|
"""The three expected dashboard files are present."""
|
2026-07-03 03:54:26 +09:00
|
|
|
names = {p.name for p in _dashboard_json_files()}
|
2026-06-28 17:14:17 +09:00
|
|
|
assert "mt5cli-overview.json" in names
|
|
|
|
|
assert "mt5cli-trades.json" in names
|
|
|
|
|
assert "mt5cli-market.json" in names
|
|
|
|
|
|
2026-07-03 03:54:26 +09:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"relative_path",
|
|
|
|
|
[
|
|
|
|
|
"README.md",
|
|
|
|
|
"compose.yml",
|
|
|
|
|
"provisioning/datasources/mt5cli-sqlite.yml",
|
|
|
|
|
"provisioning/dashboards/mt5cli.yml",
|
|
|
|
|
],
|
|
|
|
|
ids=["readme", "compose", "datasource-provisioning", "dashboard-provisioning"],
|
|
|
|
|
)
|
|
|
|
|
def test_expected_file_exists(self, relative_path: str) -> None:
|
|
|
|
|
"""Bundled Grafana example support files are present."""
|
|
|
|
|
assert (_EXAMPLES_DIR / relative_path).is_file()
|