"""Unit tests for the DynamoDB job store client.""" from unittest.mock import MagicMock import pytest from botocore.exceptions import ClientError from pytest_mock import MockerFixture from ai_eval_runner.clients import jobs def _mock_table(mocker: MockerFixture) -> MagicMock: table = mocker.MagicMock() resource = mocker.MagicMock() resource.Table.return_value = table mocker.patch("ai_eval_runner.clients.jobs.boto3.resource", return_value=resource) return table def test_create_job_writes_pending_item(mocker: MockerFixture) -> None: table = _mock_table(mocker) jobs.create_job("job-1", mcp_name="test-mcp", pipeline_id="123") table.put_item.assert_called_once() item = table.put_item.call_args.kwargs["Item"] assert item["job_id"] == "job-1" assert item["status"] == "pending" assert item["mcp_name"] == "test-mcp" assert item["pipeline_id"] == "123" assert "created_at" in item and "updated_at" in item and "expires_at" in item def test_update_job_sets_status_and_result(mocker: MockerFixture) -> None: table = _mock_table(mocker) jobs.update_job("job-1", status="succeeded", result_json='{"mcp": "test-mcp"}') table.update_item.assert_called_once() kwargs = table.update_item.call_args.kwargs assert kwargs["Key"] == {"job_id": "job-1"} assert kwargs["ExpressionAttributeValues"][":status"] == "succeeded" assert kwargs["ExpressionAttributeValues"][":result_json"] == '{"mcp": "test-mcp"}' assert "result_json = :result_json" in kwargs["UpdateExpression"] def test_update_job_sets_error(mocker: MockerFixture) -> None: table = _mock_table(mocker) jobs.update_job("job-1", status="failed", error="boom") kwargs = table.update_item.call_args.kwargs assert kwargs["ExpressionAttributeValues"][":error"] == "boom" assert kwargs["ExpressionAttributeNames"]["#error"] == "error" def test_get_job_returns_none_when_item_missing(mocker: MockerFixture) -> None: table = _mock_table(mocker) table.get_item.return_value = {} assert jobs.get_job("missing") is None def test_get_job_returns_record_when_item_exists(mocker: MockerFixture) -> None: table = _mock_table(mocker) table.get_item.return_value = { "Item": { "job_id": "job-1", "status": "succeeded", "result_json": '{"mcp": "test-mcp"}', } } result = jobs.get_job("job-1") assert result == jobs.JobRecord( status="succeeded", result_json='{"mcp": "test-mcp"}', error=None ) def test_ensure_table_exists_is_noop_without_local_endpoint( mocker: MockerFixture, ) -> None: mocker.patch("ai_eval_runner.clients.jobs.config.DYNAMODB_ENDPOINT_URL", None) resource_ctor = mocker.patch("ai_eval_runner.clients.jobs.boto3.resource") jobs.ensure_table_exists() resource_ctor.assert_not_called() def test_ensure_table_exists_creates_table_against_local_endpoint( mocker: MockerFixture, ) -> None: mocker.patch( "ai_eval_runner.clients.jobs.config.DYNAMODB_ENDPOINT_URL", "http://localhost:8000", ) resource = mocker.MagicMock() mocker.patch("ai_eval_runner.clients.jobs.boto3.resource", return_value=resource) jobs.ensure_table_exists() resource.create_table.assert_called_once() resource.meta.client.update_time_to_live.assert_called_once() def test_ensure_table_exists_tolerates_table_already_existing( mocker: MockerFixture, ) -> None: mocker.patch( "ai_eval_runner.clients.jobs.config.DYNAMODB_ENDPOINT_URL", "http://localhost:8000", ) resource = mocker.MagicMock() resource.create_table.side_effect = ClientError( {"Error": {"Code": "ResourceInUseException", "Message": "exists"}}, "CreateTable", ) mocker.patch("ai_eval_runner.clients.jobs.boto3.resource", return_value=resource) jobs.ensure_table_exists() resource.meta.client.update_time_to_live.assert_called_once() def test_ensure_table_exists_reraises_other_client_errors( mocker: MockerFixture, ) -> None: mocker.patch( "ai_eval_runner.clients.jobs.config.DYNAMODB_ENDPOINT_URL", "http://localhost:8000", ) resource = mocker.MagicMock() resource.create_table.side_effect = ClientError( {"Error": {"Code": "ThrottlingException", "Message": "slow down"}}, "CreateTable", ) mocker.patch("ai_eval_runner.clients.jobs.boto3.resource", return_value=resource) with pytest.raises(ClientError): jobs.ensure_table_exists()