"""Unit tests for eval job orchestration.""" import asyncio import uuid from pytest_mock import MockerFixture from ai_eval_runner.api.schemas.eval import ( EvalRequest, EvalResponse, JobStatus, McpConfig, PipelineContext, ) from ai_eval_runner.clients.jobs import JobRecord from ai_eval_runner.services import jobs def _request() -> EvalRequest: return EvalRequest( mcp=McpConfig( name="test-mcp", endpoint="http://localhost:8080/mcp", auth_token="test-jwt", ), pipeline=PipelineContext(pipeline_name="test-pipeline", pipeline_id="123"), prompts={}, ) def test_start_job_creates_job_record_and_schedules_execution( mocker: MockerFixture, ) -> None: create_job = mocker.patch("ai_eval_runner.services.jobs.jobs_client.create_job") create_task = mocker.patch("ai_eval_runner.services.jobs.asyncio.create_task") job_id = asyncio.run(jobs.start_job(_request())) assert uuid.UUID(job_id) create_job.assert_called_once_with(job_id, mcp_name="test-mcp", pipeline_id="123") create_task.assert_called_once() def test_start_job_uses_placeholder_mcp_name_when_no_mcp_configured( mocker: MockerFixture, ) -> None: create_job = mocker.patch("ai_eval_runner.services.jobs.jobs_client.create_job") mocker.patch("ai_eval_runner.services.jobs.asyncio.create_task") request = EvalRequest( pipeline=PipelineContext(pipeline_name="test-pipeline", pipeline_id="123"), prompts={}, ) job_id = asyncio.run(jobs.start_job(request)) create_job.assert_called_once_with(job_id, mcp_name="none", pipeline_id="123") def test_execute_marks_job_succeeded_with_result(mocker: MockerFixture) -> None: response = EvalResponse(mcp="test-mcp", model="test-model", prompts={}) mocker.patch("ai_eval_runner.services.jobs.run_eval", return_value=response) update_job = mocker.patch("ai_eval_runner.services.jobs.jobs_client.update_job") jobs._execute("job-1", _request()) update_job.assert_any_call("job-1", status=JobStatus.RUNNING) update_job.assert_any_call( "job-1", status=JobStatus.SUCCEEDED, result_json=response.model_dump_json() ) def test_execute_marks_job_failed_on_exception(mocker: MockerFixture) -> None: mocker.patch( "ai_eval_runner.services.jobs.run_eval", side_effect=RuntimeError("bedrock down"), ) update_job = mocker.patch("ai_eval_runner.services.jobs.jobs_client.update_job") jobs._execute("job-1", _request()) update_job.assert_any_call("job-1", status=JobStatus.RUNNING) update_job.assert_any_call("job-1", status=JobStatus.FAILED, error="bedrock down") def test_get_job_returns_none_when_missing(mocker: MockerFixture) -> None: mocker.patch("ai_eval_runner.services.jobs.jobs_client.get_job", return_value=None) assert jobs.get_job("missing") is None def test_get_job_parses_stored_result(mocker: MockerFixture) -> None: response = EvalResponse(mcp="test-mcp", model="test-model", prompts={}) mocker.patch( "ai_eval_runner.services.jobs.jobs_client.get_job", return_value=JobRecord( status="succeeded", result_json=response.model_dump_json(), error=None ), ) result = jobs.get_job("job-1") assert result is not None assert result.job_id == "job-1" assert result.status == JobStatus.SUCCEEDED assert result.result == response assert result.error is None