import pytest from pytest_mock import MockerFixture from src import config from src.attachment_resolver import resolve_attachments_for_job from src.errors import PermanentError, TransientError _SUCCESS_BODY = {"upcs": ["00602458823264", "00602458823271"], "isrcs": ["USRC17607839", "GBUM71507104"]} def _mock_resp(mocker: MockerFixture, status_code: int, json_body: object = None, text: str = "") -> object: resp = mocker.MagicMock() resp.status_code = status_code resp.text = text if json_body is not None: resp.json.return_value = json_body else: resp.json.side_effect = ValueError("no JSON") return resp def test_happy_path_returns_upcs_and_isrcs(mocker: MockerFixture) -> None: mock_get = mocker.patch.object(config.ows_client, "get") mock_get.return_value = _mock_resp(mocker, 200, _SUCCESS_BODY) result = resolve_attachments_for_job(job_id=42) assert result == _SUCCESS_BODY mock_get.assert_called_once_with("ows-project-manager", path="/transfer/job/42/attachments") def test_4xx_raises_permanent_error(mocker: MockerFixture) -> None: mock_get = mocker.patch.object(config.ows_client, "get") mock_get.return_value = _mock_resp(mocker, 404, text="not found") with pytest.raises(PermanentError, match="404"): resolve_attachments_for_job(job_id=42) def test_5xx_raises_transient_error(mocker: MockerFixture) -> None: mock_get = mocker.patch.object(config.ows_client, "get") mock_get.return_value = _mock_resp(mocker, 503, text="unavailable") with pytest.raises(TransientError, match="503"): resolve_attachments_for_job(job_id=42) def test_non_json_raises_transient_error(mocker: MockerFixture) -> None: mock_get = mocker.patch.object(config.ows_client, "get") mock_get.return_value = _mock_resp(mocker, 200, text="not json") with pytest.raises(TransientError, match="non-JSON"): resolve_attachments_for_job(job_id=42)