test_operations.py 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. from __future__ import annotations
  2. from datetime import datetime, timedelta, timezone
  3. class Repository:
  4. def __init__(self):
  5. self.recovered = []
  6. def running_job_count(self):
  7. return 2
  8. def list_stale_jobs(self, before):
  9. return [
  10. {"uid": "job-1", "status": "extracting", "updated_at": before - timedelta(seconds=1)},
  11. {"uid": "job-2", "status": "cancelled", "updated_at": before - timedelta(seconds=2)},
  12. ]
  13. def recover_job(self, uid, status, safe_error):
  14. self.recovered.append((uid, status, safe_error))
  15. def aggregate_metrics(self):
  16. return {
  17. "job_states": {"failed": 2, "published": 8},
  18. "duration_seconds_p95": 12.5,
  19. "failure_stage": {"ocr": 1},
  20. "candidate_acceptance_ratio": 0.8,
  21. "review_time_seconds_p95": 55,
  22. "ontology_coverage_ratio": 0.75,
  23. "projection_lag_seconds": 4,
  24. "database_password": "must-not-leak",
  25. }
  26. def dead_letter_count(self):
  27. return 1
  28. def test_limits_metrics_and_stale_job_recovery_are_bounded_and_secret_free():
  29. from app.core.data_research.operations import DataResearchOperations
  30. repository = Repository()
  31. operations = DataResearchOperations(repository, max_concurrent_jobs=2)
  32. assert operations.can_start_job() is False
  33. recovered = operations.recover_stale_jobs(
  34. now=datetime(2026, 7, 22, tzinfo=timezone.utc), stale_after_seconds=300
  35. )
  36. assert recovered == ["job-1"]
  37. assert repository.recovered == [("job-1", "queued", "recovered stale extracting job")]
  38. health = operations.health()
  39. assert health["dead_letter_count"] == 1
  40. assert health["metrics"]["projection_lag_seconds"] == 4
  41. assert "must-not-leak" not in repr(health)
  42. assert "password" not in repr(health).lower()
  43. def test_invalid_resource_limits_are_rejected():
  44. import pytest
  45. from app.core.data_research.operations import DataResearchOperations
  46. with pytest.raises(ValueError, match="max_concurrent_jobs"):
  47. DataResearchOperations(Repository(), max_concurrent_jobs=0)