- Add pytest test suite across all modules (config, database, file_processor, ai_processor, api, scheduler, monitoring, ftp_server) - 145 tests covering normal paths, error handling, edge cases - Mock external dependencies (AI endpoint, requests, pyftpdlib) - 95% code coverage with fail-under=90 threshold in CI - Update CI workflow to run pytest with coverage enforcement - Add pyproject.toml with pytest/coverage configuration
99 lines
4.0 KiB
Python
99 lines
4.0 KiB
Python
"""Tests for scheduler module."""
|
|
|
|
import os
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
from factsdb.config import Config, DatabaseConfig, AIEndpointConfig, FTPServerConfig, SchedulerConfig
|
|
from factsdb.scheduler import FactExtractionJob, FactExtractionScheduler, get_scheduler
|
|
|
|
|
|
class MockConfig:
|
|
"""Lightweight mock Config that avoids env var requirements."""
|
|
|
|
def __init__(self, db_path=":memory:"):
|
|
self.database = DatabaseConfig(path=db_path)
|
|
self.ai_endpoint = AIEndpointConfig(url="http://localhost", auth_token="tok")
|
|
self.ftp_server = FTPServerConfig(host="0.0.0.0", port=2121, username="u", password="p")
|
|
self.scheduler = SchedulerConfig(interval_minutes=10)
|
|
|
|
|
|
@pytest.fixture
|
|
def scheduler_config(tmp_path):
|
|
return MockConfig(db_path=str(tmp_path / "sched.db"))
|
|
|
|
|
|
class TestFactExtractionJob:
|
|
def test_execute_missing_directory(self, scheduler_config):
|
|
from factsdb.database import DatabaseManager
|
|
from factsdb.file_processor import FileProcessor
|
|
from factsdb.ai_processor import AIProcessor
|
|
dm = DatabaseManager(scheduler_config.database)
|
|
fp = FileProcessor()
|
|
ap = AIProcessor(scheduler_config.ai_endpoint)
|
|
job = FactExtractionJob(scheduler_config, dm, fp, ap)
|
|
with pytest.raises(Exception, match="Directory does not exist"):
|
|
job.execute("/nonexistent/dir", "t", "", "gpt-oss")
|
|
|
|
|
|
class TestFactExtractionScheduler:
|
|
def test_init(self, scheduler_config):
|
|
with patch("factsdb.scheduler.AIProcessor"):
|
|
with patch("factsdb.scheduler.DatabaseManager"):
|
|
sched = FactExtractionScheduler(scheduler_config)
|
|
assert sched.jobs == {}
|
|
assert sched.is_running is False
|
|
|
|
def test_add_job(self, scheduler_config):
|
|
with patch("factsdb.scheduler.AIProcessor"):
|
|
with patch("factsdb.scheduler.DatabaseManager"):
|
|
sched = FactExtractionScheduler(scheduler_config)
|
|
sched.add_job("/tmp", "t1", "", "gpt-oss", 5)
|
|
jobs = sched.get_jobs()
|
|
assert len(jobs) == 1
|
|
assert jobs[0]["directory_path"] == "/tmp"
|
|
assert jobs[0]["interval_minutes"] == 5
|
|
|
|
def test_remove_job(self, scheduler_config):
|
|
with patch("factsdb.scheduler.AIProcessor"):
|
|
with patch("factsdb.scheduler.DatabaseManager"):
|
|
sched = FactExtractionScheduler(scheduler_config)
|
|
sched.add_job("/tmp", "t1")
|
|
sched.remove_job("job_1")
|
|
assert len(sched.get_jobs()) == 0
|
|
|
|
def test_is_job_running(self, scheduler_config):
|
|
with patch("factsdb.scheduler.AIProcessor"):
|
|
with patch("factsdb.scheduler.DatabaseManager"):
|
|
sched = FactExtractionScheduler(scheduler_config)
|
|
sched.add_job("/tmp", "t1")
|
|
assert sched.is_job_running("job_1") is True
|
|
assert sched.is_job_running("job_99") is False
|
|
|
|
def test_start_stop(self, scheduler_config):
|
|
with patch("factsdb.scheduler.AIProcessor"):
|
|
with patch("factsdb.scheduler.DatabaseManager"):
|
|
sched = FactExtractionScheduler(scheduler_config)
|
|
sched.start()
|
|
assert sched.is_running is True
|
|
sched.stop()
|
|
assert sched.is_running is False
|
|
|
|
def test_get_jobs_empty(self, scheduler_config):
|
|
with patch("factsdb.scheduler.AIProcessor"):
|
|
with patch("factsdb.scheduler.DatabaseManager"):
|
|
sched = FactExtractionScheduler(scheduler_config)
|
|
assert sched.get_jobs() == []
|
|
|
|
|
|
class TestGetScheduler:
|
|
def test_singleton(self, scheduler_config):
|
|
import factsdb.scheduler as sched_mod
|
|
sched_mod._scheduler = None
|
|
with patch("factsdb.scheduler.AIProcessor"):
|
|
with patch("factsdb.scheduler.DatabaseManager"):
|
|
s1 = get_scheduler(scheduler_config)
|
|
s2 = get_scheduler(scheduler_config)
|
|
assert s1 is s2
|
|
sched_mod._scheduler = None
|