fix: pg_dump en chemin absolu (cron 03:30) + isolation des logs sous pytest
- backup.py : PG_DUMP = shutil.which ou /opt/homebrew/bin/pg_dump — le cron de 03:30 (PATH minimal /usr/bin:/bin) échouait en [Errno 2] depuis le 17/08, backups ET purge 90 j jamais exécutés. Testé : 8/8 dumps + purge OK sous env cron simulé. - tests/conftest.py : fixture session isolated_logs_dir — redirige logs_dir (alerts.log/apika.log) vers le tmp de test ; pytest écrivait de fausses alertes [louka] 2026-08-03 dans le vrai logs/alerts.log de prod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 changed files +28 −1
modified
src/utils/backup.py
+7 −1
@@ -29,6 +29,12 @@ from src.database.db import session_scope | ||
| 29 | 29 | from src.database.models import DATA_MODELS |
| 30 | 30 | from src.utils.logger import alert, get_logger |
| 31 | 31 | |
| 32 | +# Chemin absolu de pg_dump : le cron de 03:30 tourne avec un PATH minimal | |
| 33 | +# (/usr/bin:/bin) qui ne contient pas /opt/homebrew/bin — un appel nu à | |
| 34 | +# "pg_dump" y échoue en [Errno 2] FileNotFoundError. On résout via le PATH | |
| 35 | +# courant quand c'est possible, sinon on retombe sur l'installation Homebrew. | |
| 36 | +PG_DUMP = shutil.which("pg_dump") or "/opt/homebrew/bin/pg_dump" | |
| 37 | + | |
| 32 | 38 | |
| 33 | 39 | def _day_dir(date_key: datetime.date) -> Path: |
| 34 | 40 | day_dir = get_settings().backups_dir / date_key.isoformat() |
@@ -69,7 +75,7 @@ def backup_service(service: str, date_key: datetime.date | None = None) -> Path: | ||
| 69 | 75 | out_path = day_dir / f"{table}_{timestamp}.sql.gz" |
| 70 | 76 | result = subprocess.run( |
| 71 | 77 | [ |
| 72 | − "pg_dump", | |
| 78 | + PG_DUMP, | |
| 73 | 79 | "--dbname", |
| 74 | 80 | _libpq_url(settings.database_url), |
| 75 | 81 | "--table", |
modified
tests/conftest.py
+21 −0
@@ -15,8 +15,10 @@ valeurs dès le premier appel. | ||
| 15 | 15 | |
| 16 | 16 | from __future__ import annotations |
| 17 | 17 | |
| 18 | +import dataclasses | |
| 18 | 19 | import os |
| 19 | 20 | import tempfile |
| 21 | +from pathlib import Path | |
| 20 | 22 | |
| 21 | 23 | _TMP_DIR = tempfile.mkdtemp(prefix="apika-tests-") |
| 22 | 24 | os.environ["APP_ENV"] = "test" |
@@ -49,3 +51,22 @@ def no_backup(monkeypatch: pytest.MonkeyPatch): | ||
| 49 | 51 | monkeypatch.setattr( |
| 50 | 52 | "src.collectors.base_collector.backup_service", lambda *args, **kwargs: None |
| 51 | 53 | ) |
| 54 | + | |
| 55 | + | |
| 56 | +@pytest.fixture(scope="session", autouse=True) | |
| 57 | +def isolated_logs_dir(): | |
| 58 | + """Redirige les logs (alerts.log, apika.log) vers le tmp de test. | |
| 59 | + | |
| 60 | + ``logs_dir`` est codé en dur sur ``BASE_DIR/logs`` dans ``get_settings()`` : | |
| 61 | + sans cette fixture, les collectes échouées simulées par les tests | |
| 62 | + (ex. ``test_run_failure_after_retries``) écriraient de fausses alertes | |
| 63 | + dans le vrai ``logs/alerts.log`` de production du node. | |
| 64 | + """ | |
| 65 | + test_logs = Path(_TMP_DIR) / "logs" | |
| 66 | + test_logs.mkdir(parents=True, exist_ok=True) | |
| 67 | + test_settings = dataclasses.replace(get_settings(), logs_dir=test_logs) | |
| 68 | + mp = pytest.MonkeyPatch() | |
| 69 | + # alert() et get_logger() résolvent get_settings via src.utils.logger. | |
| 70 | + mp.setattr("src.utils.logger.get_settings", lambda: test_settings) | |
| 71 | + yield test_logs | |
| 72 | + mp.undo() | |
| 52 | 73 | |