Skip to content

Commit 8c501d6

Browse files
committed
Merge branch 'checkup-reports-tests' into 'main'
Add Postgres reporter generator and integration tests See merge request postgres-ai/postgres_ai!77
2 parents 027e77b + 71d8be4 commit 8c501d6

File tree

13 files changed

+1335
-2
lines changed

13 files changed

+1335
-2
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ pids
2828
*.seed
2929
*.pid.lock
3030

31+
# Python artifacts
32+
__pycache__/
33+
*.py[cod]
34+
35+
# Python virtual environments
36+
.venv/
37+
venv/
38+
3139
# Node artifacts
3240
node_modules/
3341
cli/node_modules/

.gitlab-ci.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
stages:
22
- test
33

4+
reporter:tests:
5+
stage: test
6+
image: python:3.11-bullseye
7+
variables:
8+
GIT_STRATEGY: fetch
9+
PIP_DISABLE_PIP_VERSION_CHECK: "1"
10+
PIP_NO_CACHE_DIR: "1"
11+
before_script:
12+
- python --version
13+
- pip install --upgrade pip
14+
- apt-get update
15+
- apt-get install -y --no-install-recommends postgresql postgresql-client && rm -rf /var/lib/apt/lists/*
16+
- pip install -r reporter/requirements-dev.txt
17+
script:
18+
- chown -R postgres:postgres "$CI_PROJECT_DIR"
19+
- su - postgres -c "cd \"$CI_PROJECT_DIR\" && python -m pytest --run-integration tests/reporter"
20+
rules:
21+
- if: '$CI_COMMIT_BRANCH'
22+
423
cli:smoke:test:
524
stage: test
625
image: alpine:3.20

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,54 @@ Get your access token at [PostgresAI](https://postgres.ai) for automated report
248248
- Query plan analysis and automated recommendations
249249
- Enhanced AI integration capabilities
250250

251+
## 🧪 Testing
252+
253+
Python-based report generation lives under `reporter/` and now ships with a pytest suite.
254+
255+
### Installation
256+
257+
Install dev dependencies (includes `pytest`, `pytest-postgresql`, `psycopg`, etc.):
258+
```bash
259+
python3 -m pip install -r reporter/requirements-dev.txt
260+
```
261+
262+
### Running Tests
263+
264+
#### Unit Tests Only (Fast, No External Services Required)
265+
266+
Run only unit tests with mocked Prometheus interactions:
267+
```bash
268+
pytest tests/reporter
269+
```
270+
271+
This automatically skips integration tests. Or run specific test files:
272+
```bash
273+
pytest tests/reporter/test_generators_unit.py -v
274+
pytest tests/reporter/test_formatters.py -v
275+
```
276+
277+
#### All Tests: Unit + Integration (Requires PostgreSQL)
278+
279+
Run the complete test suite (both unit and integration tests):
280+
```bash
281+
pytest tests/reporter --run-integration
282+
```
283+
284+
Integration tests create a temporary PostgreSQL instance automatically and require PostgreSQL binaries (`initdb`, `postgres`) on your PATH. No manual database setup or environment variables are required - the tests create and destroy their own temporary PostgreSQL instances.
285+
286+
**Summary:**
287+
- `pytest tests/reporter`**Unit tests only** (integration tests skipped)
288+
- `pytest tests/reporter --run-integration`**Both unit and integration tests**
289+
290+
### Test Coverage
291+
292+
Generate coverage report:
293+
```bash
294+
pytest tests/reporter -m unit --cov=reporter --cov-report=html
295+
```
296+
297+
View the coverage report by opening `htmlcov/index.html` in your browser.
298+
251299
## 🤝 Contributing
252300

253301
We welcome contributions from Postgres experts! Please check our [GitLab repository](https://gitlab.com/postgres-ai/postgres_ai) for:

pytest.ini

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[pytest]
2+
addopts = -ra --import-mode=importlib
3+
pythonpath = .
4+
testpaths = tests
5+
markers =
6+
unit: Marks fast unit tests that mock external services.
7+
integration: Marks tests that talk to real services like PostgreSQL.
8+
requires_postgres: Alias for tests needing a live Postgres instance.

reporter/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Reporter package exposing report generation utilities."""

reporter/requirements-dev.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-r requirements.txt
2+
pytest==9.0.1
3+
pytest-postgresql==7.0.2
4+
coverage==7.6.10
5+
pytest-cov==6.0.0

reporter/requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
requests>=2.31.0
2-
psycopg2-binary>=2.9.9
1+
requests==2.32.5
2+
psycopg2-binary==2.9.11

tests/__init__.py

Whitespace-only changes.

tests/reporter/__init__.py

Whitespace-only changes.

tests/reporter/conftest.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from typing import Callable
2+
3+
import pytest
4+
5+
6+
def pytest_addoption(parser: pytest.Parser) -> None:
7+
"""Add a flag for enabling integration tests that require services."""
8+
parser.addoption(
9+
"--run-integration",
10+
action="store_true",
11+
default=False,
12+
help="Run tests marked as integration/requires_postgres.",
13+
)
14+
15+
16+
def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
17+
"""Skip integration tests unless --run-integration is given."""
18+
if config.getoption("--run-integration"):
19+
return
20+
21+
skip_marker = pytest.mark.skip(reason="integration tests require --run-integration")
22+
for item in items:
23+
if "integration" in item.keywords or "requires_postgres" in item.keywords:
24+
item.add_marker(skip_marker)
25+
26+
27+
@pytest.fixture(name="prom_result")
28+
def fixture_prom_result() -> Callable[[list[dict] | None, str], dict]:
29+
"""Build a Prometheus-like payload for the happy-path tests."""
30+
31+
def _builder(rows: list[dict] | None = None, status: str = "success") -> dict:
32+
return {
33+
"status": status,
34+
"data": {
35+
"result": rows or [],
36+
},
37+
}
38+
39+
return _builder
40+
41+
42+
@pytest.fixture(name="series_sample")
43+
def fixture_series_sample() -> Callable[[str, dict | None, list[tuple[float | int, float | int | str]] | None], dict]:
44+
"""Create metric entries (metric metadata + values array) for query_range tests."""
45+
46+
def _builder(
47+
metric_name: str,
48+
labels: dict | None = None,
49+
values: list[tuple[float | int, float | int | str]] | None = None,
50+
) -> dict:
51+
labels = labels or {}
52+
values = values or []
53+
return {
54+
"metric": {"__name__": metric_name, **labels},
55+
"values": [[ts, str(val)] for ts, val in values],
56+
}
57+
58+
return _builder

0 commit comments

Comments
 (0)