Pre-publication review draft
日常訳NICHIJOYAKU日本語 ↗

OPEN SOURCE

software/tests/test_orchestrator.py

Read on this site and download the files you need.

Nichijoyaku was created using AI. A complete review by human biblical-language and biblical-studies specialists has not been established. Read alongside established translations and original-language resources.

← Back to the open-source guide

software/tests/test_orchestrator.py307 linesDownload this file ↓

This is the same source included in the download. Each line number is a link you can save or share.

File verification detailsSHA-256 29c9e915da6fb8ae641f59fbbd24573baee0c9f2ab5ba9498f63dfe10dc85e72
1"""Tests for the job orchestrator."""23from __future__ import annotations45import tempfile6from datetime import datetime7from pathlib import Path8from unittest.mock import MagicMock, patch910import pytest1112from src.orchestration.models import (13    ALL_BOOKS,14    NEW_TESTAMENT_BOOKS,15    OLD_TESTAMENT_BOOKS,16    JobStatus,17)18from src.orchestration.orchestrator import JobOrchestrator19from src.orchestration.state import JobStateStore202122@pytest.fixture23def temp_data_dir() -> Path:24    """Create a temporary data directory."""25    with tempfile.TemporaryDirectory() as tmpdir:26        data_dir = Path(tmpdir)27        # Create required subdirectories28        (data_dir / "sources" / "greek").mkdir(parents=True)29        (data_dir / "output").mkdir(parents=True)30        (data_dir / "progress").mkdir(parents=True)31        (data_dir / "memory").mkdir(parents=True)32        (data_dir / "metadata").mkdir(parents=True)33        (data_dir / "config").mkdir(parents=True)34        (data_dir / "jobs").mkdir(parents=True)3536        # Create minimal config37        import json38        with (data_dir / "config" / "style.json").open("w") as f:39            json.dump({40                "equivalence_approach": "optimal",41                "readability_level": "general_public",42            }, f)4344        yield data_dir454647@pytest.fixture48def orchestrator(temp_data_dir: Path) -> JobOrchestrator:49    """Create an orchestrator with temporary directory."""50    return JobOrchestrator(51        data_dir=temp_data_dir,52        max_parallel_books=1,53        max_concurrent_api_calls=2,54    )555657class TestJobOrchestratorInit:58    """Tests for JobOrchestrator initialization."""5960    def test_init(self, temp_data_dir: Path) -> None:61        """Test basic initialization."""62        orch = JobOrchestrator(data_dir=temp_data_dir)63        assert orch.data_dir == temp_data_dir64        assert orch.max_parallel_books == 4  # Default65        assert orch.max_concurrent_api_calls == 5  # Default6667    def test_custom_parallelism(self, temp_data_dir: Path) -> None:68        """Test initialization with custom parallelism."""69        orch = JobOrchestrator(70            data_dir=temp_data_dir,71            max_parallel_books=8,72            max_concurrent_api_calls=10,73        )74        assert orch.max_parallel_books == 875        assert orch.max_concurrent_api_calls == 10767778class TestCreateJob:79    """Tests for job creation."""8081    def test_create_job_all_books(self, orchestrator: JobOrchestrator) -> None:82        """Test creating a job for all books."""83        job_id = orchestrator.create_job()8485        job = orchestrator.state_store.get_bible_job(job_id)86        assert job is not None87        assert len(job.books) == 6688        assert job.status == JobStatus.PENDING8990    def test_create_job_specific_books(91        self, orchestrator: JobOrchestrator92    ) -> None:93        """Test creating a job for specific books."""94        job_id = orchestrator.create_job(books=["john", "matthew"])9596        job = orchestrator.state_store.get_bible_job(job_id)97        assert job is not None98        assert job.books == ["john", "matthew"]99100    def test_create_job_nt_only(self, orchestrator: JobOrchestrator) -> None:101        """Test creating a job for NT only."""102        job_id = orchestrator.create_job(nt_only=True)103104        job = orchestrator.state_store.get_bible_job(job_id)105        assert job is not None106        assert len(job.books) == 27107        assert job.books == list(NEW_TESTAMENT_BOOKS)108109    def test_create_job_ot_only(self, orchestrator: JobOrchestrator) -> None:110        """Test creating a job for OT only."""111        job_id = orchestrator.create_job(ot_only=True)112113        job = orchestrator.state_store.get_bible_job(job_id)114        assert job is not None115        assert len(job.books) == 39116        assert job.books == list(OLD_TESTAMENT_BOOKS)117118    def test_create_job_with_custom_id(119        self, orchestrator: JobOrchestrator120    ) -> None:121        """Test creating a job with a custom ID."""122        job_id = orchestrator.create_job(123            books=["john"],124            job_id="my-custom-job-id",125        )126        assert job_id == "my-custom-job-id"127128    def test_create_job_invalid_book(129        self, orchestrator: JobOrchestrator130    ) -> None:131        """Test that invalid book names raise an error."""132        with pytest.raises(ValueError, match="Invalid book names"):133            orchestrator.create_job(books=["invalid_book", "john"])134135    def test_create_job_config_snapshot(136        self, orchestrator: JobOrchestrator, temp_data_dir: Path137    ) -> None:138        """Test that job captures config snapshot."""139        job_id = orchestrator.create_job(books=["john"])140141        job = orchestrator.state_store.get_bible_job(job_id)142        assert job is not None143        assert "style" in job.config_snapshot or "model" in job.config_snapshot144145146class TestGetStatus:147    """Tests for job status retrieval."""148149    def test_get_status_existing_job(150        self, orchestrator: JobOrchestrator151    ) -> None:152        """Test getting status of an existing job."""153        job_id = orchestrator.create_job(books=["john", "matthew"])154155        status = orchestrator.get_status(job_id)156157        assert status["job_id"] == job_id158        assert status["status"] == "pending"159        assert status["total_books"] == 2160        assert len(status["books"]) == 2161162    def test_get_status_nonexistent_job(163        self, orchestrator: JobOrchestrator164    ) -> None:165        """Test getting status of non-existent job."""166        with pytest.raises(ValueError, match="Job not found"):167            orchestrator.get_status("nonexistent-id")168169170class TestRunJob:171    """Tests for running jobs."""172173    def test_run_nonexistent_job(self, orchestrator: JobOrchestrator) -> None:174        """Test running a non-existent job raises error."""175        with pytest.raises(ValueError, match="Job not found"):176            orchestrator.run("nonexistent-job")177178    def test_run_completed_job(self, orchestrator: JobOrchestrator) -> None:179        """Test running an already completed job raises error."""180        job_id = orchestrator.create_job(books=["john"])181        orchestrator.state_store.update_job_status(job_id, JobStatus.COMPLETED)182183        with pytest.raises(ValueError, match="already completed"):184            orchestrator.run(job_id)185186187class TestResumeJob:188    """Tests for job resumption."""189190    def test_resume_nonexistent_job(191        self, orchestrator: JobOrchestrator192    ) -> None:193        """Test resuming a non-existent job by ID."""194        with pytest.raises(ValueError, match="Job not found"):195            orchestrator.resume(job_id="nonexistent-id")196197    def test_resume_no_incomplete_jobs(198        self, orchestrator: JobOrchestrator199    ) -> None:200        """Test resuming when no incomplete jobs exist."""201        with pytest.raises(ValueError, match="No incomplete jobs"):202            orchestrator.resume()203204205class TestStateStoreIntegration:206    """Tests for state store integration."""207208    def test_state_persists_across_instances(209        self, temp_data_dir: Path210    ) -> None:211        """Test that state persists across orchestrator instances."""212        # Create job with first orchestrator213        orch1 = JobOrchestrator(data_dir=temp_data_dir)214        job_id = orch1.create_job(books=["john"])215216        # Load with second orchestrator217        orch2 = JobOrchestrator(data_dir=temp_data_dir)218        job = orch2.state_store.get_bible_job(job_id)219220        assert job is not None221        assert job.books == ["john"]222223224class TestOrphanedVerseRecovery:225    """Tests for orphaned verse recovery."""226227    def test_reset_orphaned_on_resume(228        self, orchestrator: JobOrchestrator229    ) -> None:230        """Test that orphaned verses are reset on resume."""231        # Create job and initialize verses232        job_id = orchestrator.create_job(books=["john"])233        orchestrator.state_store.initialize_verse_jobs(234            job_id, "john", [(1, 1), (1, 2)]235        )236237        # Claim verses (simulating in-progress state)238        orchestrator.state_store.atomic_claim_verse(239            job_id, "john", 1, 1, worker_pid=12345240        )241242        # Verify verses are in_progress243        pending_before = orchestrator.state_store.get_pending_verses(244            job_id, "john"245        )246        assert len(pending_before) == 1  # Only unclaimed verse247248        # Reset orphaned verses249        reset_count = orchestrator.state_store.reset_orphaned_verses(job_id)250        assert reset_count == 1251252        # Verify verses are pending again253        pending_after = orchestrator.state_store.get_pending_verses(254            job_id, "john"255        )256        assert len(pending_after) == 2257258259class TestConfigSnapshot:260    """Tests for configuration snapshotting."""261262    def test_snapshot_includes_model(263        self, orchestrator: JobOrchestrator264    ) -> None:265        """Test that config snapshot includes model info."""266        job_id = orchestrator.create_job(books=["john"])267268        job = orchestrator.state_store.get_bible_job(job_id)269        assert job is not None270        assert "model" in job.config_snapshot271272    def test_snapshot_includes_style(273        self, orchestrator: JobOrchestrator, temp_data_dir: Path274    ) -> None:275        """Test that config snapshot includes style if available."""276        job_id = orchestrator.create_job(books=["john"])277278        job = orchestrator.state_store.get_bible_job(job_id)279        assert job is not None280        assert "style" in job.config_snapshot281282283class TestBookJobCreation:284    """Tests for book job creation."""285286    def test_book_jobs_created_with_bible_job(287        self, orchestrator: JobOrchestrator288    ) -> None:289        """Test that book jobs are created when Bible job is created."""290        job_id = orchestrator.create_job(books=["john", "matthew", "luke"])291292        book_jobs = orchestrator.state_store.get_book_jobs(job_id)293        assert len(book_jobs) == 3294295        book_names = {bj.book for bj in book_jobs}296        assert book_names == {"john", "matthew", "luke"}297298    def test_all_book_jobs_pending(299        self, orchestrator: JobOrchestrator300    ) -> None:301        """Test that all book jobs start as pending."""302        job_id = orchestrator.create_job(books=["john", "matthew"])303304        book_jobs = orchestrator.state_store.get_book_jobs(job_id)305        for bj in book_jobs:306            assert bj.status == JobStatus.PENDING307