"""Tests for messaging logic module.""" import uuid from typing import Any from unittest.mock import MagicMock, patch import pytest from flexmock import flexmock from owsresponse import response from pytest import FixtureRequest from transcoding import config from transcoding.connectors import ows_assets from transcoding.logic import messaging from transcoding.models import ( sqs_message, transcoding_job as transcoding_job_model, transcoding_order as transcoding_order_model, ) @pytest.fixture def transcoding_order_fixture() -> dict[str, Any]: """Return transcoding order body.""" return { "transcoding_order_id": 1, "sns_topic": None, "input_bucket": "ows-transcoding-raw", "input_key": "c85cc331-c08f-46fd-906d-793ef224eb1f.wav", "preset_id": None, } @pytest.fixture def transcoding_job_fixture() -> dict[str, Any]: """Return transcoding job body.""" return { "transcoding_job_id": 1, "transcoding_order_id": 1, "status": "requested", "description": None, "attempt": 3, "output_bucket": "ows-transcoding-raw", "output_key": "4e6e4670-03f4-4a34-8d92-123123dc.wav", "container": "m4a", "channels": 4, "codec": "PCM", "sample_rate": 44100, "bit_rate": 1411200, "bit_depth": 16, } @pytest.fixture def pass_thru_transcoding_job_fixture( transcoding_job_fixture: dict[str, Any], ) -> dict[str, Any]: """Return pass-thru transcoding job body (no codec).""" return {**transcoding_job_fixture, "codec": None} @pytest.fixture def expected_send_sqs_message_args() -> dict[str, Any]: """Return expected args for send_sqs_message.""" return { "transcoding_job_id": 1, "input_bucket": "ows-transcoding-raw", "input_key": "c85cc331-c08f-46fd-906d-793ef224eb1f.wav", "output_bucket": "ows-transcoding-raw", "output_key": "4e6e4670-03f4-4a34-8d92-123123dc.wav", "container": "m4a", "channels": 4, "codec": "PCM", "sample_rate": 44100, "bit_rate": 1411200, "bit_depth": 16, } @pytest.fixture def expected_pass_thru_send_sqs_message_args() -> dict[str, Any]: """Return expected args for send_sqs_message in pass-thru case.""" return { "transcoding_job_id": 1, "input_bucket": "ows-transcoding-raw", "input_key": "c85cc331-c08f-46fd-906d-793ef224eb1f.wav", "output_bucket": "ows-transcoding-raw", "output_key": "4e6e4670-03f4-4a34-8d92-123123dc.wav", "pass_thru": True, } @pytest.fixture def resend_transcoding_job_mocks( transcoding_job_fixture: dict[str, Any], transcoding_order_fixture: dict[str, Any] ) -> dict[str, Any]: """Return mocks for resend_transcoding_job_sqs_message.""" return { "get_transcoding_job": response.Response(transcoding_job_fixture), "get_transcoding_order": response.Response(transcoding_order_fixture), "send_sqs_message": response.Response(), } def test_get_message_group_id_success() -> None: """Test message group id is built from vendor_id and subaccount_id.""" input_key = "file.wav" ( flexmock(ows_assets) .should_receive("get_asset_owner") .with_args(input_key) .and_return({"vendor_id": 100, "subaccount_id": 200}) .once() ) result = messaging.get_message_group_id(input_key) assert result == "100-200" @patch("transcoding.logic.messaging.g", spec=["log"]) def test_get_message_group_id_owner_not_determinable_logs_warning( mock_g: MagicMock, ) -> None: """Fallback group id is returned and warning log is used for non-determinable owner.""" input_key = "missing.wav" ( flexmock(ows_assets) .should_receive("get_asset_owner") .with_args(input_key) .and_raise(ows_assets.AssetOwnerNotDeterminableException("not found")) .once() ) result = messaging.get_message_group_id(input_key) assert uuid.UUID(result).version == 4 mock_g.log.warning.assert_called_once_with( "Unable to get asset owner for input_key=%s", input_key, exc_info=True, ) mock_g.log.exception.assert_not_called() @patch("transcoding.logic.messaging.g", spec=["log"]) def test_get_message_group_id_owner_lookup_exception_logs_exception( mock_g: MagicMock, ) -> None: """Fallback group id is returned and exception log is used for lookup failures.""" input_key = "file.wav" ( flexmock(ows_assets) .should_receive("get_asset_owner") .with_args(input_key) .and_raise(ows_assets.AssetOwnerLookupException("server error")) .once() ) result = messaging.get_message_group_id(input_key) assert uuid.UUID(result).version == 4 mock_g.log.exception.assert_called_once_with( "Unable to get asset owner for input_key=%s", input_key, ) mock_g.log.warning.assert_not_called() @pytest.mark.parametrize( "job_fixture,expected_fixture", [ pytest.param( "transcoding_job_fixture", "expected_send_sqs_message_args", id="with_codec", ), pytest.param( "pass_thru_transcoding_job_fixture", "expected_pass_thru_send_sqs_message_args", id="pass_thru", ), ], ) def test_send_transcoding_job_message_success( request: FixtureRequest, transcoding_order_fixture: dict[str, Any], job_fixture: str, expected_fixture: str, ) -> None: """Test for send_transcoding_job_message success.""" transcoding_job = request.getfixturevalue(job_fixture) expected_args = request.getfixturevalue(expected_fixture) message_group_id = "20-30" ( flexmock(messaging) .should_receive("get_message_group_id") .with_args(transcoding_order_fixture["input_key"]) .and_return(message_group_id) .once() ) ( flexmock(sqs_message) .should_receive("send_sqs_message") .with_args( queue_url=config.SQS_TRANSCODING_URL, message=expected_args, message_group_id=message_group_id, ) .and_return(response.Response("ok")) .once() ) result = messaging.send_transcoding_job_message( transcoding_order=transcoding_order_fixture, transcoding_job=transcoding_job, ) assert result def test_send_transcoding_job_message_failure( transcoding_order_fixture: dict[str, Any], transcoding_job_fixture: dict[str, Any], expected_send_sqs_message_args: dict[str, Any], ) -> None: """Test for send_transcoding_job_message failure.""" message_group_id = "20-30" ( flexmock(messaging) .should_receive("get_message_group_id") .with_args(transcoding_order_fixture["input_key"]) .and_return(message_group_id) .once() ) ( flexmock(sqs_message) .should_receive("send_sqs_message") .with_args( queue_url=config.SQS_TRANSCODING_URL, message=expected_send_sqs_message_args, message_group_id=message_group_id, ) .and_return(response.create_fatal_response("fatel_error")) .once() ) result = messaging.send_transcoding_job_message( transcoding_order=transcoding_order_fixture, transcoding_job=transcoding_job_fixture, ) assert not result assert result.status == 500 def test_resend_transcoding_job_sqs_message_success( transcoding_job_fixture: dict[str, Any], transcoding_order_fixture: dict[str, Any], resend_transcoding_job_mocks: dict[str, Any], ) -> None: """Test for success of resend_transcoding_job_sqs_message.""" transcoding_job_id = transcoding_job_fixture["transcoding_job_id"] transcoding_order_id = transcoding_order_fixture["transcoding_order_id"] ( flexmock(transcoding_job_model) .should_receive("get_transcoding_job_by_id") .with_args(transcoding_job_id) .and_return(resend_transcoding_job_mocks["get_transcoding_job"]) .once() ) ( flexmock(transcoding_order_model) .should_receive("get_transcoding_order") .with_args(transcoding_order_id) .and_return(resend_transcoding_job_mocks["get_transcoding_order"]) .once() ) ( flexmock(messaging) .should_receive("send_transcoding_job_message") .with_args(transcoding_order_fixture, transcoding_job_fixture) .and_return(resend_transcoding_job_mocks["send_sqs_message"]) .once() ) result = messaging.resend_transcoding_job_sqs_message(transcoding_job_id) assert result @pytest.mark.parametrize( "failing_mock", ["get_transcoding_job", "get_transcoding_order", "send_sqs_message"] ) def test_resend_transcoding_job_sqs_message_subcall_failure( transcoding_job_fixture: dict[str, Any], transcoding_order_fixture: dict[str, Any], resend_transcoding_job_mocks: dict[str, Any], failing_mock: str, ) -> None: """Test for subcall failure of resend_transcoding_job_sqs_message.""" error_message = "fatal_error_%s" % failing_mock error_response = response.create_fatal_response(error_message) resend_transcoding_job_mocks[failing_mock] = error_response transcoding_job_id = transcoding_job_fixture["transcoding_job_id"] transcoding_order_id = transcoding_order_fixture["transcoding_order_id"] ( flexmock(transcoding_job_model) .should_receive("get_transcoding_job_by_id") .with_args(transcoding_job_id) .and_return(resend_transcoding_job_mocks["get_transcoding_job"]) ) ( flexmock(transcoding_order_model) .should_receive("get_transcoding_order") .with_args(transcoding_order_id) .and_return(resend_transcoding_job_mocks["get_transcoding_order"]) ) ( flexmock(messaging) .should_receive("send_transcoding_job_message") .with_args(transcoding_order_fixture, transcoding_job_fixture) .and_return(resend_transcoding_job_mocks["send_sqs_message"]) ) result = messaging.resend_transcoding_job_sqs_message(transcoding_job_id) assert not result assert result.errors["message"] == error_message