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 | from src.database.models import DATA_MODELS | 29 | from src.database.models import DATA_MODELS | ||
| 30 | from src.utils.logger import alert, get_logger | 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 | def _day_dir(date_key: datetime.date) -> Path: | 39 | def _day_dir(date_key: datetime.date) -> Path: | ||
| 34 | day_dir = get_settings().backups_dir / date_key.isoformat() | 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 | out_path = day_dir / f"{table}_{timestamp}.sql.gz" | 75 | out_path = day_dir / f"{table}_{timestamp}.sql.gz" | ||
| 70 | result = subprocess.run( | 76 | result = subprocess.run( | ||
| 71 | [ | 77 | [ | ||
| 72 | − "pg_dump", | 78 | + PG_DUMP, | ||
| 73 | "--dbname", | 79 | "--dbname", | ||
| 74 | _libpq_url(settings.database_url), | 80 | _libpq_url(settings.database_url), | ||
| 75 | "--table", | 81 | "--table", | ||
modified
tests/conftest.py
+21 −0
@@ -15,8 +15,10 @@ valeurs dès le premier appel. | |||||
| 15 | 15 | ||||
| 16 | from __future__ import annotations | 16 | from __future__ import annotations | ||
| 17 | 17 | ||||
| 18 | +import dataclasses | ||||
| 18 | import os | 19 | import os | ||
| 19 | import tempfile | 20 | import tempfile | ||
| 21 | +from pathlib import Path | ||||
| 20 | 22 | ||||
| 21 | _TMP_DIR = tempfile.mkdtemp(prefix="apika-tests-") | 23 | _TMP_DIR = tempfile.mkdtemp(prefix="apika-tests-") | ||
| 22 | os.environ["APP_ENV"] = "test" | 24 | os.environ["APP_ENV"] = "test" | ||
@@ -49,3 +51,22 @@ def no_backup(monkeypatch: pytest.MonkeyPatch): | |||||
| 49 | monkeypatch.setattr( | 51 | monkeypatch.setattr( | ||
| 50 | "src.collectors.base_collector.backup_service", lambda *args, **kwargs: None | 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 | ||||