"""Approval logic tests.""" from datetime import datetime from typing import Any from unittest.mock import call, patch from zoneinfo import ZoneInfo import pytest from pytest_mock import MockerFixture from requests.exceptions import HTTPError from werkzeug.exceptions import BadRequest from video.constants import approval as approval_constants, release as release_constants from video.exceptions import ( InvalidRequest, ProductVideoNotFound, VideoValidationError, ) from video.logic import approval as approval_logic from video.models.ows import ( artist as ows_artist, product as ows_product, track as ows_track, ) from video.models.sql.classes import approval, product_video, release from video.utils import status_checker from tests.unit.ar_db_utils import mock_db_session track_id = 100034 def test_approval_get_success(mocker: MockerFixture) -> None: """Test get approvals.""" mocker.patch.object( approval, "get", return_value={"result": "cow"}, ) get_response = approval_logic.get(1) assert get_response == {"result": "cow"} # Start change tests def test_change_success(mocker: MockerFixture) -> None: """Test change approval_type release without validation.""" expected_result = {"release_id": 1} session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={"release_id": 1}, ) _get_product_not_for_distribution_mock = mocker.patch.object( approval_logic, "_get_product_not_for_distribution", autospec=True, ) check_release_status_before_action_mock = mocker.patch.object( status_checker, "check_release_status_before_action", autospec=True, return_value={"release_status": release_constants.TRANSFER_TO_CONTENT}, ) _update_upc_and_isrc_if_empty_mock = mocker.patch.object( approval_logic, "_update_upc_and_isrc_if_empty", autospec=True, ) _final_approval_mock = mocker.patch.object( approval_logic, "_final_approval", autospec=True, ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, return_value=expected_result, ) update_mock = mocker.patch.object( release, "update", autospec=True, ) change_response = approval_logic.change( 1, "bobby", {"approval_type": "release", "value": True}, ) get_mock.assert_called_with( 1, session=session, for_update=True, ) _get_product_not_for_distribution_mock.assert_not_called() check_release_status_before_action_mock.assert_called_with( 1, frozenset({release_constants.TRANSFER_TO_CONTENT}), session=session, ) _update_upc_and_isrc_if_empty_mock.assert_not_called() _final_approval_mock.assert_not_called() upsert_mock.assert_called_once_with( 1, "bobby", "release", True, session=session, ) update_mock.assert_not_called() assert change_response == expected_result @pytest.mark.parametrize( "string_value, expected_bool", [ ("true", True), ("True", True), ("false", False), ("False", False), ], ) def test_change_coerces_string_value( mocker: MockerFixture, string_value: Any, expected_bool: Any ) -> None: """Test that string 'true'/'false' values are coerced to bool before reaching the model.""" session = mock_db_session(mocker) mocker.patch.object( product_video, "get", autospec=True, return_value={"release_id": 1} ) mocker.patch.object( status_checker, "check_release_status_before_action", autospec=True ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, return_value={} ) approval_logic.change( 1, "bobby", {"approval_type": "release", "value": string_value} ) upsert_mock.assert_called_once_with( 1, "bobby", "release", expected_bool, session=session ) def test_change_raises_for_invalid_string_value() -> None: """Test that an unrecognised string value raises InvalidRequest.""" with pytest.raises(InvalidRequest): approval_logic.change(1, "bobby", {"approval_type": "release", "value": "yes"}) def test_change_final_approval_with_value_false_excludes_in_content( mocker: MockerFixture, ) -> None: """FINAL approval with value=False does not allow IN_CONTENT through validation.""" session = mock_db_session(mocker) mocker.patch.object( product_video, "get", autospec=True, return_value={"release_id": 1}, ) check_release_status_before_action_mock = mocker.patch.object( status_checker, "check_release_status_before_action", autospec=True, return_value={"release_status": release_constants.TRANSFER_TO_CONTENT}, ) mocker.patch.object(approval_logic, "_final_approval", autospec=True) mocker.patch.object(approval_logic, "_update_upc_and_isrc_if_empty", autospec=True) approval_logic.change( 1, "bobby", {"approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": False}, ) check_release_status_before_action_mock.assert_called_once_with( 1, frozenset({release_constants.TRANSFER_TO_CONTENT}), session=session, ) def test_change_final_approval_idempotent_when_already_in_content( mocker: MockerFixture, ) -> None: """Final approval on a release already in IN_CONTENT returns current state.""" session = mock_db_session(mocker) release_record = { "release_id": 1, "release_status": release_constants.IN_CONTENT, } mocker.patch.object( product_video, "get", autospec=True, return_value={"release_id": 1}, ) check_release_status_before_action_mock = mocker.patch.object( status_checker, "check_release_status_before_action", autospec=True, return_value=release_record, ) _final_approval_mock = mocker.patch.object( approval_logic, "_final_approval", autospec=True, ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, ) change_response = approval_logic.change( 1, "bobby", {"approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": True}, ) check_release_status_before_action_mock.assert_called_once_with( 1, frozenset( {release_constants.TRANSFER_TO_CONTENT, release_constants.IN_CONTENT} ), session=session, ) _final_approval_mock.assert_not_called() upsert_mock.assert_not_called() assert change_response == release_record def test_change_invalid_status(mocker: MockerFixture) -> None: """Test approval when status is invalid forwards error.""" session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={"release_id": 1}, ) _get_product_not_for_distribution_mock = mocker.patch.object( approval_logic, "_get_product_not_for_distribution", autospec=True, ) check_release_status_before_action_mock = mocker.patch.object( status_checker, "check_release_status_before_action", autospec=True, side_effect=BadRequest("invalid status"), ) _update_upc_and_isrc_if_empty_mock = mocker.patch.object( approval_logic, "_update_upc_and_isrc_if_empty", autospec=True, ) _final_approval_mock = mocker.patch.object( approval_logic, "_final_approval", autospec=True, ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, ) update_mock = mocker.patch.object( release, "update", autospec=True, ) with pytest.raises(BadRequest): approval_logic.change( 1, "bobby", {"approval_type": "release", "value": True}, ) get_mock.assert_called_with( 1, session=session, for_update=True, ) _get_product_not_for_distribution_mock.assert_not_called() check_release_status_before_action_mock.assert_called_with( 1, frozenset({release_constants.TRANSFER_TO_CONTENT}), session=session, ) _update_upc_and_isrc_if_empty_mock.assert_not_called() _final_approval_mock.assert_not_called() upsert_mock.assert_not_called() update_mock.assert_not_called() def test_change_update_upc_and_isrc_if_empty_failure(mocker: MockerFixture) -> None: """Test final_approval _update_upc_and_isrc_if_empty failure.""" session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={"release_id": 1}, ) _get_product_not_for_distribution_mock = mocker.patch.object( approval_logic, "_get_product_not_for_distribution", autospec=True, ) check_release_status_before_action_mock = mocker.patch.object( status_checker, "check_release_status_before_action", autospec=True, return_value={"release_status": release_constants.TRANSFER_TO_CONTENT}, ) _update_upc_and_isrc_if_empty_mock = mocker.patch.object( approval_logic, "_update_upc_and_isrc_if_empty", autospec=True, side_effect=BadRequest("upc generation failed"), ) _final_approval_mock = mocker.patch.object( approval_logic, "_final_approval", autospec=True, ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, ) update_mock = mocker.patch.object( release, "update", autospec=True, ) with pytest.raises(BadRequest): approval_logic.change( 1, "bobby", {"approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": True}, ) get_mock.assert_called_with( 1, session=session, for_update=True, ) _get_product_not_for_distribution_mock.assert_not_called() check_release_status_before_action_mock.assert_called_with( 1, frozenset( {release_constants.TRANSFER_TO_CONTENT, release_constants.IN_CONTENT} ), session=session, ) _update_upc_and_isrc_if_empty_mock.assert_called_with( 1, {"approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": True}, session=session, ) _final_approval_mock.assert_not_called() upsert_mock.assert_not_called() update_mock.assert_not_called() def test_change_final_approval(mocker: MockerFixture) -> None: """Test final_approval for a product.""" expected_result = {"release_status": "in_content"} session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={"release_id": 1}, ) _get_product_not_for_distribution_mock = mocker.patch.object( approval_logic, "_get_product_not_for_distribution", autospec=True, ) check_release_status_before_action_mock = mocker.patch.object( status_checker, "check_release_status_before_action", autospec=True, return_value={"release_status": release_constants.TRANSFER_TO_CONTENT}, ) _update_upc_and_isrc_if_empty_mock = mocker.patch.object( approval_logic, "_update_upc_and_isrc_if_empty", autospec=True, return_value=None, ) _final_approval_mock = mocker.patch.object( approval_logic, "_final_approval", autospec=True, return_value=expected_result, ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, ) update_mock = mocker.patch.object( release, "update", autospec=True, ) change_response = approval_logic.change( 1, "bobby", {"approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": True}, ) get_mock.assert_called_with( 1, session=session, for_update=True, ) _get_product_not_for_distribution_mock.assert_not_called() check_release_status_before_action_mock.assert_called_with( 1, frozenset( {release_constants.TRANSFER_TO_CONTENT, release_constants.IN_CONTENT} ), session=session, ) _update_upc_and_isrc_if_empty_mock.assert_called_with( 1, {"approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": True}, session=session, ) _final_approval_mock.assert_called_with( 1, "bobby", False, False, session=session, ) # This is handled by _final_approval. upsert_mock.assert_not_called() update_mock.assert_not_called() assert change_response is expected_result def test_change_rejection(mocker: MockerFixture) -> None: """Test rejection for a product changes release to LABEL_PROCESSING.""" expected_result = {"release_status": release_constants.LABEL_PROCESSING} session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={"release_id": 1}, ) _get_product_not_for_distribution_mock = mocker.patch.object( approval_logic, "_get_product_not_for_distribution", autospec=True, ) check_release_status_before_action_mock = mocker.patch.object( status_checker, "check_release_status_before_action", autospec=True, return_value={"release_status": release_constants.TRANSFER_TO_CONTENT}, ) _update_upc_and_isrc_if_empty_mock = mocker.patch.object( approval_logic, "_update_upc_and_isrc_if_empty", autospec=True, ) _final_approval_mock = mocker.patch.object( approval_logic, "_final_approval", autospec=True, ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, ) update_mock = mocker.patch.object( release, "update", autospec=True, return_value=expected_result, ) change_response = approval_logic.change( 1, "bobby", {"approval_type": approval_constants.REJECTION_TYPE, "value": True}, ) get_mock.assert_called_with( 1, session=session, for_update=True, ) _get_product_not_for_distribution_mock.assert_not_called() check_release_status_before_action_mock.assert_called_with( 1, frozenset({release_constants.TRANSFER_TO_CONTENT}), session=session, ) _update_upc_and_isrc_if_empty_mock.assert_not_called() _final_approval_mock.assert_not_called() upsert_mock.assert_called_with( 1, "bobby", approval_constants.REJECTION_TYPE, True, session=session, ) update_mock.assert_called_with( 1, { "release_status": release_constants.LABEL_PROCESSING, }, session=session, ) assert change_response == expected_result def test_change_final_approval_with_bypass_validation_for_dummy( mocker: MockerFixture, ) -> None: """Test setting final approval with bypass_validation = true.""" expected_result = {"release_status": "in_content"} session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={"release_id": 1}, ) _get_product_not_for_distribution_mock = mocker.patch.object( approval_logic, "_get_product_not_for_distribution", autospec=True, return_value="AccountingDummy", ) check_release_status_before_action_mock = mocker.patch.object( status_checker, "check_release_status_before_action", autospec=True, ) _update_upc_and_isrc_if_empty_mock = mocker.patch.object( approval_logic, "_update_upc_and_isrc_if_empty", autospec=True, ) _final_approval_mock = mocker.patch.object( approval_logic, "_final_approval", autospec=True, return_value=expected_result, ) mocker.patch.object( approval, "upsert", autospec=True, ) update_mock = mocker.patch.object( release, "update", autospec=True, ) change_response = approval_logic.change( 1, "bobby", { "approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": True, "bypass_validation": True, }, ) get_mock.assert_called_with( 1, session=session, for_update=True, ) _get_product_not_for_distribution_mock.assert_called_with(1, session=session) check_release_status_before_action_mock.assert_not_called() _update_upc_and_isrc_if_empty_mock.assert_called_with( 1, { "approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": True, "bypass_validation": True, }, session=session, ) _final_approval_mock.assert_called_with( 1, "bobby", True, False, session=session, ) update_mock.assert_not_called() assert change_response is expected_result def test_change_final_approval_with_bypass_validation_nfd_lookup_failure( mocker: MockerFixture, ) -> None: """Test final approval with bypass_validation NFD lookup failure.""" session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={"release_id": 1}, ) _get_product_not_for_distribution_mock = mocker.patch.object( approval_logic, "_get_product_not_for_distribution", autospec=True, side_effect=ProductVideoNotFound(), ) check_release_status_before_action_mock = mocker.patch.object( status_checker, "check_release_status_before_action", autospec=True, ) _update_upc_and_isrc_if_empty_mock = mocker.patch.object( approval_logic, "_update_upc_and_isrc_if_empty", autospec=True, ) _final_approval_mock = mocker.patch.object( approval_logic, "_final_approval", autospec=True, ) mocker.patch.object( approval, "upsert", autospec=True, ) update_mock = mocker.patch.object( release, "update", autospec=True, ) with pytest.raises(ProductVideoNotFound): approval_logic.change( 1, "bobby", { "approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": True, "bypass_validation": True, }, ) get_mock.assert_called_with( 1, session=session, for_update=True, ) _get_product_not_for_distribution_mock.assert_called_with(1, session=session) check_release_status_before_action_mock.assert_not_called() _update_upc_and_isrc_if_empty_mock.assert_not_called() _final_approval_mock.assert_not_called() update_mock.assert_not_called() def test_change_final_approval_bypass_validation_non_dummy( mocker: MockerFixture, ) -> None: """Test setting final approval with bypass_validation non dummy.""" session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={"release_id": 1}, ) _get_product_not_for_distribution_mock = mocker.patch.object( approval_logic, "_get_product_not_for_distribution", autospec=True, return_value="N", ) check_release_status_before_action_mock = mocker.patch.object( status_checker, "check_release_status_before_action", autospec=True, ) _update_upc_and_isrc_if_empty_mock = mocker.patch.object( approval_logic, "_update_upc_and_isrc_if_empty", autospec=True, ) _final_approval_mock = mocker.patch.object( approval_logic, "_final_approval", autospec=True, ) mocker.patch.object( approval, "upsert", autospec=True, ) update_mock = mocker.patch.object( release, "update", autospec=True, ) with pytest.raises(InvalidRequest): approval_logic.change( 1, "bobby", { "approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": True, "bypass_validation": True, }, ) get_mock.assert_called_with( 1, session=session, for_update=True, ) _get_product_not_for_distribution_mock.assert_called_with(1, session=session) check_release_status_before_action_mock.assert_not_called() _update_upc_and_isrc_if_empty_mock.assert_not_called() _final_approval_mock.assert_not_called() update_mock.assert_not_called() # End change tests # Start _map_video_data_to_track tests @pytest.mark.parametrize( ("test_name", "video_data", "expected_track_data"), ( ( "test_everything_unset", { "isrc": None, "version": None, "upc": None, "lyrics": None, "video_title": None, "preview_start_time": None, "parental_advisory": None, "p_line_year": None, "p_line_copyright_holder": None, }, { "track_type": "video", "isrc": None, "version": None, "upc": None, "lyrics": None, "track_name": None, "preview_start_time": None, }, ), ( "test_parental_advisory_yes", { "isrc": None, "version": None, "upc": None, "lyrics": None, "video_title": None, "preview_start_time": None, "parental_advisory": "Yes", "p_line_year": None, "p_line_copyright_holder": None, }, { "track_type": "video", "isrc": None, "version": None, "upc": None, "lyrics": None, "track_name": None, "preview_start_time": None, "explicit": "Y", }, ), ( "test_parental_advisory_no", { "isrc": None, "version": None, "upc": None, "lyrics": None, "video_title": None, "preview_start_time": None, "parental_advisory": "No", "p_line_year": None, "p_line_copyright_holder": None, }, { "track_type": "video", "isrc": None, "version": None, "upc": None, "lyrics": None, "track_name": None, "preview_start_time": None, "explicit": "N", }, ), ( "test_parental_advisory_clean_version", { "isrc": None, "version": None, "upc": None, "lyrics": None, "video_title": None, "preview_start_time": None, "parental_advisory": "Clean Version", "p_line_year": None, "p_line_copyright_holder": None, }, { "track_type": "video", "isrc": None, "version": None, "upc": None, "lyrics": None, "track_name": None, "preview_start_time": None, "explicit": "C", }, ), ( "test_partial_pline_data_no_year", { "isrc": None, "version": None, "upc": None, "lyrics": None, "video_title": None, "preview_start_time": None, "parental_advisory": None, "p_line_year": None, "p_line_copyright_holder": "TEST COPYRIGHT HOLDER", }, { "track_type": "video", "isrc": None, "version": None, "upc": None, "lyrics": None, "track_name": None, "preview_start_time": None, }, ), ( "test_partial_pline_data_no_copyright_holder", { "isrc": None, "version": None, "upc": None, "lyrics": None, "video_title": None, "preview_start_time": None, "parental_advisory": None, "p_line_year": 2000, "p_line_copyright_holder": None, }, { "track_type": "video", "isrc": None, "version": None, "upc": None, "lyrics": None, "track_name": None, "preview_start_time": None, }, ), ( "test_pline_complete", { "isrc": None, "version": None, "upc": None, "lyrics": None, "video_title": None, "preview_start_time": None, "parental_advisory": None, "p_line_year": 2000, "p_line_copyright_holder": "TEST COPYRIGHT HOLDER", }, { "track_type": "video", "isrc": None, "version": None, "upc": None, "lyrics": None, "track_name": None, "preview_start_time": None, "p_info": "2000 TEST COPYRIGHT HOLDER", }, ), ( "test_everything_set", { "isrc": "TEST_ISRC", "version": "TEST_VERSION", "upc": "TEST_UPC", "lyrics": "TEST_LYRICS", "video_title": "TEST_TITLE", "preview_start_time": 100, "parental_advisory": "Yes", "p_line_year": 2000, "p_line_copyright_holder": "TEST COPYRIGHT HOLDER", }, { "track_type": "video", "isrc": "TEST_ISRC", "version": "TEST_VERSION", "upc": "TEST_UPC", "lyrics": "TEST_LYRICS", "track_name": "TEST_TITLE", "preview_start_time": 100, "explicit": "Y", "p_info": "2000 TEST COPYRIGHT HOLDER", }, ), ), ) def test_map_video_data_to_track( test_name: Any, video_data: Any, expected_track_data: Any ) -> None: """Test that video_data maps to expected_track_data.""" track_data = approval_logic._map_video_data_to_track(video_data) assert track_data == expected_track_data # End _map_video_data_to_track tests # Start update_track tests def test_update_track_get_track_failure(mocker: MockerFixture) -> None: """Tests error propagation when get_track_id fails.""" product_id = 100 video_data = { "video_data_key": "test_video_data", } _map_video_data_to_track_mock = mocker.patch.object( approval_logic, "_map_video_data_to_track", autospec=True, return_value={ "track_data_key": "track_data_value", }, ) get_track_id_mock = mocker.patch.object( ows_track, "get_track_id", autospec=True, side_effect=HTTPError("error"), ) update_track_mock = mocker.patch.object( ows_track, "update_track", autospec=True, ) with pytest.raises(HTTPError): approval_logic.update_track(product_id, video_data) _map_video_data_to_track_mock.assert_called_with(video_data) get_track_id_mock.assert_called_with(product_id) update_track_mock.assert_not_called() def test_update_track_update_track_failure(mocker: MockerFixture) -> None: """Tests error propagation when update_track fails.""" expected_error = {"error": "test_error"} product_id = 100 video_data = { "video_data_key": "test_video_data", } _map_video_data_to_track_mock = mocker.patch.object( approval_logic, "_map_video_data_to_track", autospec=True, return_value={ "track_data_key": "track_data_value", }, ) get_track_id_mock = mocker.patch.object( ows_track, "get_track_id", autospec=True, # TODO: This return value should be wrapped in a result, but it is not. return_value=10, ) update_track_mock = mocker.patch.object( ows_track, "update_track", autospec=True, return_value=expected_error, ) result = approval_logic.update_track(product_id, video_data) _map_video_data_to_track_mock.assert_called_with(video_data) get_track_id_mock.assert_called_with(product_id) update_track_mock.assert_called_with( 10, { "track_data_key": "track_data_value", }, ) assert result is expected_error def test_update_track_success(mocker: MockerFixture) -> None: """Tests success is returned as a response.""" expected_result = {"track": "updated"} product_id = 100 video_data = { "video_data_key": "test_video_data", } _map_video_data_to_track_mock = mocker.patch.object( approval_logic, "_map_video_data_to_track", autospec=True, return_value={ "track_data_key": "track_data_value", }, ) get_track_id_mock = mocker.patch.object( ows_track, "get_track_id", autospec=True, # TODO: This return value should be wrapped in a result, but it is not. return_value=10, ) update_track_mock = mocker.patch.object( ows_track, "update_track", autospec=True, return_value=expected_result, ) result = approval_logic.update_track(product_id, video_data) _map_video_data_to_track_mock.assert_called_with(video_data) get_track_id_mock.assert_called_with(product_id) update_track_mock.assert_called_with( 10, { "track_data_key": "track_data_value", }, ) assert result assert result is expected_result # End update_track tests # Start update_genre tests def test_update_genre_delete_fail(mocker: MockerFixture) -> None: """Tests that for a null subgenre, delete failure is propagated.""" expected_error = {"error": "delete_failed"} product_id = 100 video_data = { "subgenre_id": None, "upc": "TESTUPC", "isrc": "OTHER KEY", } delete_subgenre_mock = mocker.patch.object( ows_product, "delete_subgenre", autospec=True, return_value=expected_error, ) get_subgenre_mock = mocker.patch.object( ows_product, "get_subgenre", autospec=True, ) create_subgenre_mock = mocker.patch.object( ows_product, "create_subgenre", autospec=True, ) update_subgenre_mock = mocker.patch.object( ows_product, "update_subgenre", autospec=True, ) approval_logic.update_genre(product_id, video_data) delete_subgenre_mock.assert_called_with(product_id) get_subgenre_mock.assert_not_called() create_subgenre_mock.assert_not_called() update_subgenre_mock.assert_not_called() def test_update_genre_delete_success(mocker: MockerFixture) -> None: """Tests that for a null subgenre, the subgenre is deleted successfully.""" product_id = 100 video_data = { "subgenre_id": None, "upc": "TESTUPC", "isrc": "OTHER KEY", } delete_subgenre_mock = mocker.patch.object( ows_product, "delete_subgenre", autospec=True, return_value={"success": True}, ) get_subgenre_mock = mocker.patch.object( ows_product, "get_subgenre", autospec=True, ) create_subgenre_mock = mocker.patch.object( ows_product, "create_subgenre", autospec=True, ) update_subgenre_mock = mocker.patch.object( ows_product, "update_subgenre", autospec=True, ) approval_logic.update_genre(product_id, video_data) delete_subgenre_mock.assert_called_with(product_id) get_subgenre_mock.assert_not_called() create_subgenre_mock.assert_not_called() update_subgenre_mock.assert_not_called() def test_update_genre_get_fail(mocker: MockerFixture) -> None: """Tests that for a non-null subgenre, get failure is propagated.""" product_id = 100 video_data = { "subgenre_id": 10, "upc": "TESTUPC", "isrc": "OTHER KEY", } delete_subgenre_mock = mocker.patch.object( ows_product, "delete_subgenre", autospec=True, ) get_subgenre_mock = mocker.patch.object( ows_product, "get_subgenre", autospec=True, side_effect=HTTPError("error"), ) create_subgenre_mock = mocker.patch.object( ows_product, "create_subgenre", autospec=True, ) update_subgenre_mock = mocker.patch.object( ows_product, "update_subgenre", autospec=True, ) with pytest.raises(HTTPError): approval_logic.update_genre(product_id, video_data) delete_subgenre_mock.assert_not_called() get_subgenre_mock.assert_called_with(product_id) create_subgenre_mock.assert_not_called() update_subgenre_mock.assert_not_called() def test_update_genre_update_failure(mocker: MockerFixture) -> None: """Tests that for a non-null subgenre, update failure is propagated.""" product_id = 100 video_data = { "subgenre_id": 10, "upc": "TESTUPC", "isrc": "OTHER KEY", } delete_subgenre_mock = mocker.patch.object( ows_product, "delete_subgenre", autospec=True, ) get_subgenre_mock = mocker.patch.object( ows_product, "get_subgenre", autospec=True, return_value={}, ) create_subgenre_mock = mocker.patch.object( ows_product, "create_subgenre", autospec=True, side_effect=HTTPError("error"), ) update_subgenre_mock = mocker.patch.object( ows_product, "update_subgenre", autospec=True, ) with pytest.raises(HTTPError): approval_logic.update_genre(product_id, video_data) delete_subgenre_mock.assert_not_called() get_subgenre_mock.assert_called_with(product_id) create_subgenre_mock.assert_called_with( product_id, { "subgenre_id": 10, "upc": "TESTUPC", }, ) update_subgenre_mock.assert_not_called() def test_update_genre_update_success(mocker: MockerFixture) -> None: """Tests that for a non-null subgenre, update success is propagated.""" product_id = 100 video_data = { "subgenre_id": 10, "upc": "TESTUPC", "isrc": "OTHER KEY", } delete_subgenre_mock = mocker.patch.object( ows_product, "delete_subgenre", autospec=True, ) get_subgenre_mock = mocker.patch.object( ows_product, "get_subgenre", autospec=True, return_value={}, ) create_subgenre_mock = mocker.patch.object( ows_product, "create_subgenre", autospec=True, return_value=None, ) update_subgenre_mock = mocker.patch.object( ows_product, "update_subgenre", autospec=True, ) approval_logic.update_genre(product_id, video_data) delete_subgenre_mock.assert_not_called() get_subgenre_mock.assert_called_with(product_id) create_subgenre_mock.assert_called_with( product_id, { "subgenre_id": 10, "upc": "TESTUPC", }, ) update_subgenre_mock.assert_not_called() def test_update_genre_create_failure(mocker: MockerFixture) -> None: """Tests that for a non-null subgenre, create failure is propagated.""" product_id = 100 video_data = { "subgenre_id": 10, "upc": "TESTUPC", "isrc": "OTHER KEY", } delete_subgenre_mock = mocker.patch.object( ows_product, "delete_subgenre", autospec=True, ) get_subgenre_mock = mocker.patch.object( ows_product, "get_subgenre", autospec=True, return_value={"subgenre_id": 1}, ) create_subgenre_mock = mocker.patch.object( ows_product, "create_subgenre", autospec=True, ) update_subgenre_mock = mocker.patch.object( ows_product, "update_subgenre", autospec=True, side_effect=HTTPError("error"), ) with pytest.raises(HTTPError): approval_logic.update_genre(product_id, video_data) delete_subgenre_mock.assert_not_called() get_subgenre_mock.assert_called_with(product_id) create_subgenre_mock.assert_not_called() update_subgenre_mock.assert_called_with( product_id, { "subgenre_id": 10, "upc": "TESTUPC", }, ) def test_update_genre_create_success(mocker: MockerFixture) -> None: """Tests that for a non-null subgenre, create success is propagated.""" product_id = 100 video_data = { "subgenre_id": 10, "upc": "TESTUPC", "isrc": "OTHER KEY", } delete_subgenre_mock = mocker.patch.object( ows_product, "delete_subgenre", autospec=True, ) get_subgenre_mock = mocker.patch.object( ows_product, "get_subgenre", autospec=True, return_value={"subgenre_id": 1}, ) create_subgenre_mock = mocker.patch.object( ows_product, "create_subgenre", autospec=True, ) update_subgenre_mock = mocker.patch.object( ows_product, "update_subgenre", autospec=True, return_value=None, ) approval_logic.update_genre(product_id, video_data) delete_subgenre_mock.assert_not_called() get_subgenre_mock.assert_called_with(product_id) create_subgenre_mock.assert_not_called() update_subgenre_mock.assert_called_with( product_id, { "subgenre_id": 10, "upc": "TESTUPC", }, ) # End update_genre tests # Start _map_video_data_to_product_contributors tests @pytest.mark.parametrize( ( "test_name", "video_data", "primary_artist_data", "expected_contributors", ), ( ( "test_null_contributors", { "contributors": None, "primary_artist_id": 1000, "upc": "TESTUPC", }, { "name": "Primary Artist Name", "id": 999, }, [ { "artist_name": "Primary Artist Name", "role": "performer", "artist_info_id": 999, "upc": "TESTUPC", }, ], ), ( "test_empty_contributors", { "contributors": [], "primary_artist_id": 1000, "upc": "TESTUPC", }, { "name": "Primary Artist Name", "id": 999, }, [ { "artist_name": "Primary Artist Name", "role": "performer", "artist_info_id": 999, "upc": "TESTUPC", }, ], ), ( "test_multiple_contributors", { "contributors": [ { "name": "Jim Bob", "role": "featuring", "artist_info_id": 12, }, { "name": "Producer", "role": "producer", "artist_info_id": 14, }, ], "primary_artist_id": 1000, "upc": "TESTUPC", }, { "name": "Primary Artist Name", "id": 999, }, [ { "artist_name": "Jim Bob", "role": "featuring", "artist_info_id": 12, "upc": "TESTUPC", }, { "artist_name": "Producer", "role": "producer", "artist_info_id": 14, "upc": "TESTUPC", }, # NOTE: This artist is out of order (DISTRO-4797 to fix) { "artist_name": "Primary Artist Name", "role": "performer", "artist_info_id": 999, "upc": "TESTUPC", }, ], ), ), ) def test_map_video_data_to_product_contributors( mocker: MockerFixture, test_name: Any, video_data: Any, primary_artist_data: Any, expected_contributors: Any, ) -> None: """Test that video_data maps to expected_contributors.""" get_artist_mock = mocker.patch.object( ows_artist, "get_artist", autospec=True, return_value=primary_artist_data, ) contributors = approval_logic._map_video_data_to_product_contributors( video_data, ) get_artist_mock.assert_called_with(video_data["primary_artist_id"]) assert contributors == expected_contributors # End _map_video_data_to_product_contributors tests # Start update_contributors tests def test_update_contributors_delete_failure(mocker: MockerFixture) -> None: """Test that update_contributors forwards delete_contributor errors.""" product_id = 100 video_data = { "test_video_data": "test_video_value", } _map_video_data_to_product_contributors_mock = mocker.patch.object( approval_logic, "_map_video_data_to_product_contributors", autospec=True, return_value=[ { "artist_name": "contributor1", }, { "artist_name": "contributor2", }, ], ) delete_contributors_mock = mocker.patch.object( ows_product, "delete_contributors", autospec=True, side_effect=HTTPError("error"), ) create_contributor_mock = mocker.patch.object( ows_product, "create_contributor", autospec=True, ) with pytest.raises(HTTPError): approval_logic.update_contributors(product_id, video_data) _map_video_data_to_product_contributors_mock.assert_called_with( video_data, ) delete_contributors_mock.assert_called_with(product_id) create_contributor_mock.assert_not_called() def test_update_contributors_first_contributor_failure(mocker: MockerFixture) -> None: """Test update_contributors forwards first create_contributor error.""" product_id = 100 video_data = { "test_video_data": "test_video_value", } _map_video_data_to_product_contributors_mock = mocker.patch.object( approval_logic, "_map_video_data_to_product_contributors", autospec=True, return_value=[ { "artist_name": "contributor1", }, { "artist_name": "contributor2", }, ], ) delete_contributors_mock = mocker.patch.object( ows_product, "delete_contributors", autospec=True, return_value=None, ) create_contributor_mock = mocker.patch.object( ows_product, "create_contributor", autospec=True, side_effect=[ HTTPError("error"), ], ) with pytest.raises(HTTPError): approval_logic.update_contributors(product_id, video_data) _map_video_data_to_product_contributors_mock.assert_called_with( video_data, ) delete_contributors_mock.assert_called_with(product_id) create_contributor_mock.assert_has_calls( [ call( product_id, { "artist_name": "contributor1", }, ), ] ) def test_update_contributors_second_contributor_failure(mocker: MockerFixture) -> None: """Test update_contributors forwards second create_contributor error.""" product_id = 100 video_data = { "test_video_data": "test_video_value", } _map_video_data_to_product_contributors_mock = mocker.patch.object( approval_logic, "_map_video_data_to_product_contributors", autospec=True, return_value=[ { "artist_name": "contributor1", }, { "artist_name": "contributor2", }, ], ) delete_contributors_mock = mocker.patch.object( ows_product, "delete_contributors", autospec=True, return_value=None, ) create_contributor_mock = mocker.patch.object( ows_product, "create_contributor", autospec=True, side_effect=[ None, HTTPError("error"), ], ) with pytest.raises(HTTPError): approval_logic.update_contributors(product_id, video_data) _map_video_data_to_product_contributors_mock.assert_called_with( video_data, ) delete_contributors_mock.assert_called_with(product_id) create_contributor_mock.assert_has_calls( [ call( product_id, { "artist_name": "contributor1", }, ), call( product_id, { "artist_name": "contributor2", }, ), ] ) def test_update_contributors_success(mocker: MockerFixture) -> None: """Test update_contributors successfully sets contributors.""" product_id = 100 video_data = { "test_video_data": "test_video_value", } _map_video_data_to_product_contributors_mock = mocker.patch.object( approval_logic, "_map_video_data_to_product_contributors", autospec=True, return_value=[ { "artist_name": "contributor1", }, { "artist_name": "contributor2", }, ], ) delete_contributors_mock = mocker.patch.object( ows_product, "delete_contributors", autospec=True, return_value=None, ) create_contributor_mock = mocker.patch.object( ows_product, "create_contributor", autospec=True, side_effect=[ None, None, ], ) approval_logic.update_contributors(product_id, video_data) _map_video_data_to_product_contributors_mock.assert_called_with( video_data, ) delete_contributors_mock.assert_called_with(product_id) create_contributor_mock.assert_has_calls( [ call( product_id, { "artist_name": "contributor1", }, ), call( product_id, { "artist_name": "contributor2", }, ), ] ) # End update_contributors tests def _product_video_response() -> dict[str, Any]: return { "p_line_copyright_holder": "Cowbell", "p_line_year": 2017, "subgenre_id": 12, "upc": 12345, "contributors": [ {"name": "Jane Music", "role": "Composer"}, {"name": "George", "role": "Baby"}, {"name": "Sam", "role": "Producer"}, ], "isrc": "a", "version": "b", "lyrics": 12, "video_title": "description", "parental_advisory": "Clean Version", "not_for_distribution": "N", "channel_selection": "Powerhouse", "primary_artist_id": 123, "preview_start_time": "10:00", } # def _mock_get_product_video(mocker): # mocker.patch.object( # product_video, # 'get', # return_value=response.Response(message=_product_video_response()), # ) # def _mock_get_subgenre(mocker): # mocker.patch.object( # ows_product, # 'get_subgenre', # return_value=response.Response(message={}) # ) # def _mock_update_subgenre(mocker): # mocker.patch.object( # ows_product, # 'update_subgenre', # return_value=response.Response(message={}) # ) # def _mock_get_artist(mocker): # mocker.patch.object( # ows_artist, # 'get_artist', # return_value={ # 'id': 123, # 'name': 'cool', # }, # ) # Start update_track tests # End update_track tests # Start update_genre tests # End update_genre tests # Start update_contributors tests # def test_update_contributors_empty(mocker): # """Test empty contributors.""" # _mock_all(mocker) # contributors_response = approval_logic.update_contributors( # 1, # {'contributors': None, 'primary_artist_id': 123, 'upc': 123} # ) # assert contributors_response.status == 200 # End update_contributors tests # Start write_to_delivery_tables tests def test_write_to_delivery_tables_product_video_failure(mocker: MockerFixture) -> None: """Test write_to_delivery_tables forwards product_video failure.""" session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value=None, ) update_track_mock = mocker.patch.object( approval_logic, "update_track", autospec=True, ) update_genre_mock = mocker.patch.object( approval_logic, "update_genre", autospec=True, ) update_contributors_mock = mocker.patch.object( approval_logic, "update_contributors", autospec=True, ) product_id = 123456 with pytest.raises(ProductVideoNotFound): approval_logic.write_to_delivery_tables( product_id, session=session, ) get_mock.assert_called_once_with(product_id, session=session) update_track_mock.assert_not_called() update_genre_mock.assert_not_called() update_contributors_mock.assert_not_called() def test_write_to_delivery_tables_update_track_failure(mocker: MockerFixture) -> None: """Test write_to_delivery_tables forwards update_track failure.""" product = { "test_product_key": "test_product_value", } session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value=product, ) update_track_mock = mocker.patch.object( approval_logic, "update_track", autospec=True, side_effect=HTTPError("error"), ) update_genre_mock = mocker.patch.object( approval_logic, "update_genre", autospec=True, ) update_contributors_mock = mocker.patch.object( approval_logic, "update_contributors", autospec=True, ) product_id = 123456 with pytest.raises(HTTPError): approval_logic.write_to_delivery_tables( product_id, session=session, ) get_mock.assert_called_once_with(product_id, session=session) update_track_mock.assert_called_once_with(product_id, product) update_genre_mock.assert_not_called() update_contributors_mock.assert_not_called() def test_write_to_delivery_tables_update_genre_failure(mocker: MockerFixture) -> None: """Test write_to_delivery_tables forwards update_genre failure.""" product = { "test_product_key": "test_product_value", } session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value=product, ) update_track_mock = mocker.patch.object( approval_logic, "update_track", autospec=True, return_value=None, ) update_genre_mock = mocker.patch.object( approval_logic, "update_genre", autospec=True, side_effect=HTTPError("error"), ) update_contributors_mock = mocker.patch.object( approval_logic, "update_contributors", autospec=True, ) product_id = 123456 with pytest.raises(HTTPError): approval_logic.write_to_delivery_tables( product_id, session=session, ) get_mock.assert_called_once_with(product_id, session=session) update_track_mock.assert_called_once_with(product_id, product) update_genre_mock.assert_called_once_with(product_id, product) update_contributors_mock.assert_not_called() def test_write_to_delivery_tables_update_contributors_failure( mocker: MockerFixture, ) -> None: """Test write_to_delivery_tables forwards update_contributors failure.""" product = { "test_product_key": "test_product_value", } session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value=product, ) update_track_mock = mocker.patch.object( approval_logic, "update_track", autospec=True, ) update_genre_mock = mocker.patch.object( approval_logic, "update_genre", autospec=True, return_value=None, ) update_contributors_mock = mocker.patch.object( approval_logic, "update_contributors", autospec=True, side_effect=HTTPError("error"), ) product_id = 123456 with pytest.raises(HTTPError): approval_logic.write_to_delivery_tables( product_id, session=session, ) get_mock.assert_called_once_with(product_id, session=session) update_track_mock.assert_called_once_with(product_id, product) update_genre_mock.assert_called_once_with(product_id, product) update_contributors_mock.assert_called_once_with( product_id, product, ) def test_write_to_delivery_tables_success_without_video_data( mocker: MockerFixture, ) -> None: """Test write_to_delivery_tables succeeds without video_data.""" product = { "test_product_key": "test_product_value", } session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value=product, ) update_track_mock = mocker.patch.object( approval_logic, "update_track", autospec=True, ) update_genre_mock = mocker.patch.object( approval_logic, "update_genre", autospec=True, return_value=None, ) update_contributors_mock = mocker.patch.object( approval_logic, "update_contributors", autospec=True, return_value=None, ) product_id = 123456 approval_logic.write_to_delivery_tables( product_id, session=session, ) get_mock.assert_called_once_with(product_id, session=session) update_track_mock.assert_called_once_with(product_id, product) update_genre_mock.assert_called_once_with(product_id, product) update_contributors_mock.assert_called_once_with( product_id, product, ) def test_write_to_delivery_tables_success_with_video_data( mocker: MockerFixture, ) -> None: """Test write_to_delivery_tables succeeds with video_data.""" product = { "test_product_key": "test_product_value", } session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, ) update_track_mock = mocker.patch.object( approval_logic, "update_track", autospec=True, ) update_genre_mock = mocker.patch.object( approval_logic, "update_genre", autospec=True, return_value=None, ) update_contributors_mock = mocker.patch.object( approval_logic, "update_contributors", autospec=True, return_value=None, ) product_id = 123456 approval_logic.write_to_delivery_tables( product_id, product, session=session, ) get_mock.assert_not_called() update_track_mock.assert_called_once_with(product_id, product) update_genre_mock.assert_called_once_with(product_id, product) update_contributors_mock.assert_called_once_with( product_id, product, ) # End write_to_delivery_tables tests # Start validate tests def test_validate_propagates_is_isrc_used_error(mocker: MockerFixture) -> None: """Tests that is_isrc_used error is propagated.""" product_id = 100 video_data = { "isrc": "TESTISRC", "test_video_data": "test_video_data_value", } is_isrc_used_mock = mocker.patch.object( ows_track, "is_isrc_used", autospec=True, side_effect=HTTPError("error"), ) get_mock = mocker.patch.object( release, "get", autospec=True, ) with pytest.raises(HTTPError): approval_logic.validate(product_id, video_data) is_isrc_used_mock.assert_called_with("TESTISRC", "video") get_mock.assert_not_called() def test_validate_bad_channel(mocker: MockerFixture) -> None: """Tests that badChannel error is produced.""" product_id = 100 video_data = { "channel_selection": approval_constants.BAD_CHANNEL, "isrc": "TESTISRC", "test_video_data": "test_video_data_value", } is_isrc_used_mock = mocker.patch.object( ows_track, "is_isrc_used", autospec=True, return_value={"used": False, "used_by_product_id": None}, ) get_mock = mocker.patch.object( release, "get", autospec=True, ) result = approval_logic.validate(product_id, video_data) is_isrc_used_mock.assert_called_with("TESTISRC", "video") get_mock.assert_not_called() assert result == ["badChannel"] def test_validate_video_isrc_in_use_on_this_product(mocker: MockerFixture) -> None: """Tests that videoIsrcInUse error is not produced if on this product.""" product_id = 100 video_data = { "channel_selection": "", "isrc": "TESTISRC", "test_video_data": "test_video_data_value", } is_isrc_used_mock = mocker.patch.object( ows_track, "is_isrc_used", autospec=True, return_value={"used": True, "used_by_product_id": 100}, ) get_mock = mocker.patch.object( release, "get", autospec=True, return_value={}, ) result = approval_logic.validate(product_id, video_data) is_isrc_used_mock.assert_called_with("TESTISRC", "video") # TODO: This doesn't need to be called in this case. get_mock.assert_called_with(100) assert result == [] def test_validate_video_isrc_in_use_on_dummy_product(mocker: MockerFixture) -> None: """Tests that videoIsrcInUse error is not produced if used on dummy.""" product_id = 100 video_data = { "channel_selection": "", "isrc": "TESTISRC", "test_video_data": "test_video_data_value", "not_for_distribution": "N", } is_isrc_used_mock = mocker.patch.object( ows_track, "is_isrc_used", autospec=True, return_value={"used": True, "used_by_product_id": 999}, ) get_mock = mocker.patch.object( release, "get", autospec=True, return_value={ "not_for_distribution": "AccountingDummy", }, ) result = approval_logic.validate(product_id, video_data) is_isrc_used_mock.assert_called_with("TESTISRC", "video") get_mock.assert_called_with(999) assert result == [] def test_validate_video_isrc_in_use_on_product_this_is_dummy( mocker: MockerFixture, ) -> None: """Tests that videoIsrcInUse error is not produced if this is a dummy.""" product_id = 100 video_data = { "channel_selection": "", "isrc": "TESTISRC", "test_video_data": "test_video_data_value", "not_for_distribution": "AccountingDummy", } is_isrc_used_mock = mocker.patch.object( ows_track, "is_isrc_used", autospec=True, return_value={"used": True, "used_by_product_id": 999}, ) get_mock = mocker.patch.object( release, "get", autospec=True, return_value={ "not_for_distribution": "N", }, ) result = approval_logic.validate(product_id, video_data) is_isrc_used_mock.assert_called_with("TESTISRC", "video") get_mock.assert_called_with(999) assert result == [] def test_validate_video_isrc_in_use_on_different_product(mocker: MockerFixture) -> None: """Tests that videoIsrcInUse error is produced if isrc is used.""" product_id = 100 video_data = { "channel_selection": "", "isrc": "TESTISRC", "test_video_data": "test_video_data_value", "not_for_distribution": "N", } is_isrc_used_mock = mocker.patch.object( ows_track, "is_isrc_used", autospec=True, return_value={"used": True, "used_by_product_id": 999}, ) get_mock = mocker.patch.object( release, "get", autospec=True, return_value={ "not_for_distribution": "N", }, ) result = approval_logic.validate(product_id, video_data) is_isrc_used_mock.assert_called_with("TESTISRC", "video") get_mock.assert_called_with(999) assert result == ["videoIsrcInUse"] def test_validate_all_errors_at_once(mocker: MockerFixture) -> None: """Tests validate produces all errors at once.""" product_id = 100 video_data = { "channel_selection": approval_constants.BAD_CHANNEL, "isrc": "TESTISRC", "test_video_data": "test_video_data_value", "not_for_distribution": "N", } is_isrc_used_mock = mocker.patch.object( ows_track, "is_isrc_used", autospec=True, return_value={"used": True, "used_by_product_id": 999}, ) get_mock = mocker.patch.object( release, "get", autospec=True, return_value={ "not_for_distribution": "N", }, ) result = approval_logic.validate(product_id, video_data) is_isrc_used_mock.assert_called_with("TESTISRC", "video") get_mock.assert_called_with(999) assert result == ["badChannel", "videoIsrcInUse"] # End validate tests # Start_update_upc_and_isrc_if_empty tests def test_update_upc_and_isrc_if_empty_upc_and_isrc_set(mocker: MockerFixture) -> None: """Test no updates occur if data is already valid.""" product_id = 1234 video_data = { "approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": "yum", "upc": "TESTUPC", "isrc": "TESTISRC", } session = mock_db_session(mocker) get_upc_mock = mocker.patch.object( release, "get_upc", autospec=True, ) get_isrc_mock = mocker.patch.object( release, "get_isrc", autospec=True, ) upsert_mock = mocker.patch.object( product_video, "upsert", autospec=True, ) update_mock = mocker.patch.object( release, "update", autospec=True, ) approval_logic._update_upc_and_isrc_if_empty( product_id, video_data, session=session, ) get_upc_mock.assert_not_called() get_isrc_mock.assert_not_called() upsert_mock.assert_not_called() update_mock.assert_not_called() def test_update_upc_and_isrc_if_empty_product_video_upsert_failure( mocker: MockerFixture, ) -> None: """Test error propagation from product_video.upsert.""" product_id = 1234 video_data = { "approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": "yum", "upc": None, "isrc": "TESTISRC", } session = mock_db_session(mocker) get_upc_mock = mocker.patch.object( release, "get_upc", autospec=True, return_value="GETUPC", ) get_isrc_mock = mocker.patch.object( release, "get_isrc", autospec=True, ) upsert_mock = mocker.patch.object( product_video, "upsert", autospec=True, return_value=None, ) update_mock = mocker.patch.object( release, "update", autospec=True, return_value=None, ) approval_logic._update_upc_and_isrc_if_empty( product_id, video_data, session=session, ) get_upc_mock.assert_called_with(session=None) get_isrc_mock.assert_not_called() upsert_mock.assert_called_with( { "release_id": 1234, "upc": "GETUPC", "display_upc": "GETUPC", }, session=session, ) update_mock.assert_called_with( 1234, {"upc": "GETUPC", "display_upc": "GETUPC"}, session=None ) def test_update_upc_and_isrc_if_empty_release_update_failure( mocker: MockerFixture, ) -> None: """Test error propagation from release.update.""" product_id = 1234 video_data = { "approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": "yum", "upc": None, "isrc": "TESTISRC", } session = mock_db_session(mocker) get_upc_mock = mocker.patch.object( release, "get_upc", autospec=True, return_value="GETUPC", ) get_isrc_mock = mocker.patch.object( release, "get_isrc", autospec=True, ) upsert_mock = mocker.patch.object( product_video, "upsert", autospec=True, return_value=None ) update_mock = mocker.patch.object( release, "update", autospec=True, return_value=None, ) approval_logic._update_upc_and_isrc_if_empty( product_id, video_data, session=session, ) get_upc_mock.assert_called_with(session=None) get_isrc_mock.assert_not_called() upsert_mock.assert_called_with( { "release_id": 1234, "upc": "GETUPC", "display_upc": "GETUPC", }, session=session, ) update_mock.assert_called_with( 1234, { "upc": "GETUPC", "display_upc": "GETUPC", }, session=None, ) def test_update_upc_and_isrc_if_empty_no_upc(mocker: MockerFixture) -> None: """Test update upc if upc is blank.""" product_id = 1234 video_data = { "approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": "yum", "upc": None, "isrc": "TESTISRC", } session = mock_db_session(mocker) get_upc_mock = mocker.patch.object( release, "get_upc", autospec=True, return_value="GETUPC", ) get_isrc_mock = mocker.patch.object( release, "get_isrc", autospec=True, ) upsert_mock = mocker.patch.object( product_video, "upsert", autospec=True, return_value=None, ) update_mock = mocker.patch.object( release, "update", autospec=True, return_value=None, ) approval_logic._update_upc_and_isrc_if_empty( product_id, video_data, session=session, ) get_upc_mock.assert_called_with(session=None) get_isrc_mock.assert_not_called() upsert_mock.assert_called_with( { "release_id": 1234, "upc": "GETUPC", "display_upc": "GETUPC", }, session=session, ) update_mock.assert_called_with( 1234, { "upc": "GETUPC", "display_upc": "GETUPC", }, session=None, ) def test_update_upc_and_isrc_if_empty_no_isrc(mocker: MockerFixture) -> None: """Test update upc if ISRC is blank.""" product_id = 1234 video_data = { "approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": "yum", "upc": "TESTUPC", "isrc": None, } session = mock_db_session(mocker) get_upc_mock = mocker.patch.object( release, "get_upc", autospec=True, ) get_isrc_mock = mocker.patch.object( release, "get_isrc", autospec=True, return_value="GETISRC", ) upsert_mock = mocker.patch.object( product_video, "upsert", autospec=True, return_value=None, ) update_mock = mocker.patch.object( release, "update", autospec=True, return_value=None, ) approval_logic._update_upc_and_isrc_if_empty( product_id, video_data, session=session, ) get_upc_mock.assert_not_called() get_isrc_mock.assert_called_with(session=None) upsert_mock.assert_called_with( { "release_id": 1234, "isrc": "GETISRC", }, session=session, ) # NOTE: This is weird update_mock.assert_called_with( 1234, { "isrc": "GETISRC", }, session=None, ) def test_update_upc_and_isrc_if_empty_no_upc_no_isrc(mocker: MockerFixture) -> None: """Test update upc if UPC and ISRC are blank.""" product_id = 1234 video_data = { "approval_type": approval_constants.FINAL_APPROVAL_TYPE, "value": "yum", "upc": None, "isrc": None, } session = mock_db_session(mocker) get_upc_mock = mocker.patch.object( release, "get_upc", autospec=True, return_value="GETUPC", ) get_isrc_mock = mocker.patch.object( release, "get_isrc", autospec=True, return_value="GETISRC", ) upsert_mock = mocker.patch.object( product_video, "upsert", autospec=True, return_value=None, ) update_mock = mocker.patch.object( release, "update", autospec=True, return_value=None, ) approval_logic._update_upc_and_isrc_if_empty( product_id, video_data, session=session, ) get_upc_mock.assert_called_with(session=None) get_isrc_mock.assert_called_with(session=None) upsert_mock.assert_called_with( { "release_id": 1234, "upc": "GETUPC", "display_upc": "GETUPC", "isrc": "GETISRC", }, session=session, ) update_mock.assert_called_with( 1234, { "upc": "GETUPC", "display_upc": "GETUPC", "isrc": "GETISRC", }, session=None, ) # End _update_upc_and_isrc_if_empty tests # Start _final_approval tests def test_final_approval_product_video_get_failure(mocker: MockerFixture) -> None: """Tests _final_approval propagates product_video.get error.""" product_id = 123 user_id = "oa:123" bypass_validation = False approval_in_progress = False session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value=None, ) validate_mock = mocker.patch.object( approval_logic, "validate", autospec=True, ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, ) write_to_delivery_tables_mock = mocker.patch.object( approval_logic, "write_to_delivery_tables", autospec=True, ) update_mock = mocker.patch.object( release, "update", autospec=True, ) with pytest.raises(ProductVideoNotFound): approval_logic._final_approval( product_id, user_id, bypass_validation, approval_in_progress, session=session, ) get_mock.assert_called_with(product_id, session=session) validate_mock.assert_not_called() upsert_mock.assert_not_called() write_to_delivery_tables_mock.assert_not_called() update_mock.assert_not_called() def test_final_approval_validation_failure(mocker: MockerFixture) -> None: """Tests _final_approval propagates validation error.""" product_id = 123 user_id = "oa:123" bypass_validation = False approval_in_progress = False session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={ "test_video_data_key": "test_video_data_value", }, ) validate_mock = mocker.patch.object( approval_logic, "validate", autospec=True, return_value=["badChannel"], ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, ) write_to_delivery_tables_mock = mocker.patch.object( approval_logic, "write_to_delivery_tables", autospec=True, ) update_mock = mocker.patch.object( release, "update", autospec=True, ) with pytest.raises(VideoValidationError) as exc_info: approval_logic._final_approval( product_id, user_id, bypass_validation, approval_in_progress, session=session, ) assert exc_info.value.validation_errors == ["badChannel"] get_mock.assert_called_with(product_id, session=session) validate_mock.assert_called_with( product_id, { "test_video_data_key": "test_video_data_value", }, ) upsert_mock.assert_not_called() write_to_delivery_tables_mock.assert_not_called() update_mock.assert_not_called() def test_final_approval_upsert_failure(mocker: MockerFixture) -> None: """Tests _final_approval propagates upsert error.""" product_id = 123 user_id = "oa:123" bypass_validation = False approval_in_progress = False session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={ "test_video_data_key": "test_video_data_value", }, ) validate_mock = mocker.patch.object( approval_logic, "validate", autospec=True, return_value=[], ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, side_effect=HTTPError("upsert error"), ) write_to_delivery_tables_mock = mocker.patch.object( approval_logic, "write_to_delivery_tables", autospec=True, ) update_mock = mocker.patch.object( release, "update", autospec=True, ) with pytest.raises(HTTPError): approval_logic._final_approval( product_id, user_id, bypass_validation, approval_in_progress, session=session, ) get_mock.assert_called_with(product_id, session=session) validate_mock.assert_called_with( product_id, { "test_video_data_key": "test_video_data_value", }, ) upsert_mock.assert_called_with( product_id, user_id, approval_constants.FINAL_APPROVAL_TYPE, True, session=session, ) write_to_delivery_tables_mock.assert_not_called() update_mock.assert_not_called() def test_final_approval_write_to_delivery_tables_failure(mocker: MockerFixture) -> None: """Tests _final_approval propagates write_to_delivery_tables error.""" product_id = 123 user_id = "oa:123" bypass_validation = False approval_in_progress = True session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={ "test_video_data_key": "test_video_data_value", }, ) validate_mock = mocker.patch.object( approval_logic, "validate", autospec=True, return_value=[], ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, return_value=None, ) write_to_delivery_tables_mock = mocker.patch.object( approval_logic, "write_to_delivery_tables", autospec=True, side_effect=HTTPError("error"), ) update_mock = mocker.patch.object( release, "update", autospec=True, ) with pytest.raises(HTTPError): approval_logic._final_approval( product_id, user_id, bypass_validation, approval_in_progress, session=session, ) get_mock.assert_called_with(product_id, session=session) validate_mock.assert_called_with( product_id, { "test_video_data_key": "test_video_data_value", }, ) upsert_mock.assert_not_called() write_to_delivery_tables_mock.assert_called_with( product_id, { "test_video_data_key": "test_video_data_value", }, session=session, ) update_mock.assert_not_called() def test_final_approval_release_update_failure(mocker: MockerFixture) -> None: """Tests _final_approval propagates release.update error.""" expected_error = {"release_status": "error_state"} product_id = 123 user_id = "oa:123" bypass_validation = False approval_in_progress = False session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={ "test_video_data_key": "test_video_data_value", }, ) validate_mock = mocker.patch.object( approval_logic, "validate", autospec=True, return_value=[], ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, return_value=None, ) write_to_delivery_tables_mock = mocker.patch.object( approval_logic, "write_to_delivery_tables", autospec=True, return_value=None, ) update_mock = mocker.patch.object( release, "update", autospec=True, return_value=expected_error, ) result = approval_logic._final_approval( product_id, user_id, bypass_validation, approval_in_progress, session=session, ) get_mock.assert_called_with(product_id, session=session) validate_mock.assert_called_with( product_id, { "test_video_data_key": "test_video_data_value", }, ) upsert_mock.assert_called_with( product_id, user_id, approval_constants.FINAL_APPROVAL_TYPE, True, session=session, ) write_to_delivery_tables_mock.assert_not_called() tz = ZoneInfo("America/New_York") update_mock.assert_called_with( product_id, { "release_status": release_constants.IN_CONTENT, "ingestion_completed": datetime.now(tz).replace(microsecond=0), }, session=session, ) assert result is expected_error def test_final_approval_success(mocker: MockerFixture) -> None: """Tests _final_approval success.""" product_id = 123 user_id = "oa:123" bypass_validation = False approval_in_progress = False session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={ "test_video_data_key": "test_video_data_value", }, ) validate_mock = mocker.patch.object( approval_logic, "validate", autospec=True, return_value=[], ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, return_value=None, ) write_to_delivery_tables_mock = mocker.patch.object( approval_logic, "write_to_delivery_tables", autospec=True, return_value=None, ) update_mock = mocker.patch.object( release, "update", autospec=True, return_value={"release_status": "in_content"}, ) result = approval_logic._final_approval( product_id, user_id, bypass_validation, approval_in_progress, session=session, ) get_mock.assert_called_with(product_id, session=session) validate_mock.assert_called_with( product_id, { "test_video_data_key": "test_video_data_value", }, ) upsert_mock.assert_called_with( product_id, user_id, approval_constants.FINAL_APPROVAL_TYPE, True, session=session, ) write_to_delivery_tables_mock.assert_not_called() tz = ZoneInfo("America/New_York") update_mock.assert_called_with( product_id, { "release_status": release_constants.IN_CONTENT, "ingestion_completed": datetime.now(tz).replace(microsecond=0), }, session=session, ) assert result def test_final_approval_bypass_validation_success(mocker: MockerFixture) -> None: """Tests _final_approval bypass validation and approve release success.""" product_id = 123 user_id = "oa:123" bypass_validation = True approval_in_progress = False session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={ "test_video_data_key": "test_video_data_value", }, ) validate_mock = mocker.patch.object( approval_logic, "validate", autospec=True, ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, return_value=None, ) write_to_delivery_tables_mock = mocker.patch.object( approval_logic, "write_to_delivery_tables", autospec=True, return_value=None, ) update_mock = mocker.patch.object( release, "update", autospec=True, return_value={"release_status": "in_content"}, ) result = approval_logic._final_approval( product_id, user_id, bypass_validation, approval_in_progress, session=session, ) get_mock.assert_called_with(product_id, session=session) validate_mock.assert_not_called() upsert_mock.assert_called_with( product_id, user_id, approval_constants.FINAL_APPROVAL_TYPE, True, session=session, ) write_to_delivery_tables_mock.assert_not_called() tz = ZoneInfo("America/New_York") update_mock.assert_called_with( product_id, { "release_status": release_constants.IN_CONTENT, "ingestion_completed": datetime.now(tz).replace(microsecond=0), }, session=session, ) assert result def test_final_approval_in_progress_success(mocker: MockerFixture) -> None: """Tests _final_approval success.""" product_id = 123 user_id = "oa:123" bypass_validation = False approval_in_progress = True session = mock_db_session(mocker) get_mock = mocker.patch.object( product_video, "get", autospec=True, return_value={ "test_video_data_key": "test_video_data_value", }, ) validate_mock = mocker.patch.object( approval_logic, "validate", autospec=True, return_value=[], ) upsert_mock = mocker.patch.object( approval, "upsert", autospec=True, return_value=None, ) write_to_delivery_tables_mock = mocker.patch.object( approval_logic, "write_to_delivery_tables", autospec=True, return_value=None, ) update_mock = mocker.patch.object( release, "update", autospec=True, return_value=None, ) result = approval_logic._final_approval( product_id, user_id, bypass_validation, approval_in_progress, session=session, ) get_mock.assert_called_with(product_id, session=session) validate_mock.assert_called_with( product_id, { "test_video_data_key": "test_video_data_value", }, ) upsert_mock.assert_not_called() write_to_delivery_tables_mock.assert_called_with( product_id, { "test_video_data_key": "test_video_data_value", }, session=session, ) update_mock.assert_not_called() assert result # End _final_approval tests # Start _get_product_not_for_distribution tests def test_get_not_for_distribution_forwards_failure(mocker: MockerFixture) -> None: """Tests that product_video failure is propagated.""" mocker.patch.object( product_video, "get", autospec=True, return_value=None, ) with pytest.raises(ProductVideoNotFound): approval_logic._get_product_not_for_distribution(1) def test_get_not_for_distribution(mocker: MockerFixture) -> None: """Tests that the not_for_distribution field is returned.""" mocker.patch.object( product_video, "get", autospec=True, return_value={ **_product_video_response(), "not_for_distribution": "TestNFDFlag", }, ) result = approval_logic._get_product_not_for_distribution(1) assert result == "TestNFDFlag" # End _get_product_not_for_distribution tests # Start revert_label_mgr_approval tests @patch("video.logic.approval.g", spec=["log"]) def test_revert_label_mgr_approval(logger_mock: Any, mocker: MockerFixture) -> None: """Test revert_label_mgr_approval function.""" approval_change_mock = mocker.patch.object(approval_logic, "change") product_id = 1337 approval_logic.revert_label_mgr_approval(product_id) approval_change_mock.assert_called_with( product_id, None, data={"approval_type": approval_constants.RELEASE_TYPE, "value": None}, ) logger_mock.log.info.assert_called_with( "Reverted release approval (label manager) " "for product id: {} due to ISRC conflict".format(product_id) )