from unittest.mock import AsyncMock, Mock import numpy as np import pytest from pandas import DataFrame, isna from src.backend.constants import Flags, OutputColumns from src.backend.constants import SnowFlakeColumns as SFCols from src.backend.constants import YTAssetNotActiveReasons, YTMatchPolicies from src.backend.logic.audit import flags from src.backend.models import NewAuditFlag class TestFlagChecksSR: _class = flags.FlagChecksSR @pytest.fixture def assert_args(self, mocker_add_flag_db, mock_flagger): def _(flag): arg0, arg1, arg2 = mocker_add_flag_db.call_args.args assert arg0.empty != flag assert arg1 == mock_flagger assert arg2 == flag def test_no_asset_id(self): """Inherited from _FlagChecks.""" assert self._class.no_asset_id is flags.base._FlagChecks.no_asset_id @pytest.mark.parametrize("has_asset_id", [True, False]) @pytest.mark.parametrize( "right_df_missing_territories,should_flag", [ ("US,AT", True), (" US,AT,DE ", True), ("", False), (" ", False), (np.nan, False), (float("nan"), False), (None, False), ], ) def test_territories_missing( self, mock_df, mock_flagger, mocker_add_flag_db, right_df_missing_territories, has_asset_id, should_flag, assert_args, ): sr = mock_df(has_asset_id=has_asset_id) sr_missing_territories = DataFrame( [ sr.loc[0].to_dict() | {OutputColumns.TERRITORIES_MISSING: right_df_missing_territories} ] ) instance = self._class(sr, mock_flagger) instance.territories_missing(sr_missing_territories) value = sr.loc[0][OutputColumns.AUDIT_FLAG_TERRITORIES_MISSING] if isna(right_df_missing_territories): assert isna(value) else: assert value == right_df_missing_territories if should_flag and has_asset_id: mocker_add_flag_db_args = mocker_add_flag_db.call_args.args assert mocker_add_flag_db_args[0].empty != should_flag assert mocker_add_flag_db_args[1] == mock_flagger assert mocker_add_flag_db_args[2] == Flags.TERRITORIES_MISSING else: assert not mocker_add_flag_db.called @pytest.mark.parametrize("has_asset_id", [True, False]) @pytest.mark.parametrize( "reasons,should_flag", [ ("Mock reasons", True), ({"reason1", "reason2"}, True), (set(), False), ("", False), (" ", False), (np.nan, False), (float("nan"), False), (None, False), ], ) def test_no_active_references_reasons_orchard( self, mock_df, mock_flagger, mocker_add_flag_db, has_asset_id, reasons, should_flag, ): sr = mock_df(has_asset_id=has_asset_id) sr_no_active_references = DataFrame( [sr.loc[0].to_dict() | {OutputColumns.REASONS_ORCHARD: reasons}] ) instance = self._class(sr, mock_flagger) instance.no_active_references_reasons_orchard(sr_no_active_references) value = sr.loc[0][OutputColumns.AUDIT_FLAG_NO_ACTIVE_REFERENCES_REASONS_ORCHARD] if isna(reasons): assert isna(value) else: assert value == reasons if should_flag and has_asset_id: mocker_add_flag_db_args = mocker_add_flag_db.call_args.args assert mocker_add_flag_db_args[0].empty != should_flag assert mocker_add_flag_db_args[1] == mock_flagger assert ( mocker_add_flag_db_args[2] == Flags.NO_ACTIVE_REFERENCES_REASONS_ORCHARD ) else: assert not mocker_add_flag_db.called @pytest.mark.parametrize("has_asset_id", [True, False]) @pytest.mark.parametrize( "reasons,should_flag", [ ("Mock reasons", True), (set(), False), ("", False), (" ", False), (np.nan, False), (float("nan"), False), (None, False), ({"reason1", "reason2"}, True), ], ) def test_no_active_references_reasons_third_party( self, mock_df, mock_flagger, mocker_add_flag_db, has_asset_id, reasons, should_flag, ): sr = mock_df(has_asset_id=has_asset_id) sr_no_active_references = DataFrame( [sr.loc[0].to_dict() | {OutputColumns.REASONS_THIRD_PARTY: reasons}] ) instance = self._class(sr, mock_flagger) instance.no_active_references_reasons_third_party(sr_no_active_references) value = sr.loc[0][ OutputColumns.AUDIT_FLAG_NO_ACTIVE_REFERENCES_REASONS_THIRD_PARTY ] if isna(reasons): assert isna(value) else: assert value == reasons if should_flag and has_asset_id: mocker_add_flag_db_args = mocker_add_flag_db.call_args.args assert mocker_add_flag_db_args[0].empty != should_flag assert mocker_add_flag_db_args[1] == mock_flagger assert ( mocker_add_flag_db_args[2] == Flags.NO_ACTIVE_REFERENCES_REASONS_THIRD_PARTY ) else: assert not mocker_add_flag_db.called @pytest.mark.parametrize("has_asset_id", [True, False]) @pytest.mark.parametrize( "can_reactivate,should_flag", [ (True, True), ("true", True), (1, True), ("1", True), ("", False), (" ", False), (np.nan, False), (float("nan"), False), (None, False), (0, False), ("false", False), (False, False), ], ) @pytest.mark.parametrize( "no_active_ref_reasons", [{"MOCK_REASON"}, set(), {YTAssetNotActiveReasons.CLOSED_BY_OWNER}, np.nan], ) def test_can_reactivate( self, mock_df, mock_flagger, mocker_add_flag_db, has_asset_id, can_reactivate, no_active_ref_reasons, should_flag, ): should_flag = should_flag and ( isna(no_active_ref_reasons) or YTAssetNotActiveReasons.CLOSED_BY_OWNER not in no_active_ref_reasons ) sr = mock_df(has_asset_id=has_asset_id) sr[OutputColumns.AUDIT_FLAG_NO_ACTIVE_REFERENCES_REASONS_ORCHARD] = [ no_active_ref_reasons for _ in range(len(sr)) ] sr_no_active_references = DataFrame( [sr.loc[0].to_dict() | {OutputColumns.CAN_REACTIVATE: can_reactivate}] ) instance = self._class(sr, mock_flagger) instance.can_reactivate(sr_no_active_references) value = sr.loc[0][OutputColumns.AUDIT_FLAG_CAN_REACTIVATE] if isna(can_reactivate): assert isna(value) else: assert value == can_reactivate if should_flag and has_asset_id: arg0, arg1, arg2 = mocker_add_flag_db.call_args.args assert arg0.empty != should_flag assert arg1 == mock_flagger assert arg2 == Flags.CAN_REACTIVATE else: assert not mocker_add_flag_db.called @pytest.mark.asyncio @pytest.mark.parametrize("has_asset_id", [True, False]) @pytest.mark.parametrize( "policy,should_flag", [ (YTMatchPolicies.MONETIZE_IN_ALL_COUNTRIES, False), (YTMatchPolicies.BLOCK_IN_ALL_COUNTRIES, False), ("another policy", True), (" ", True), ("", True), ], ) async def test_bad_match_policy( self, mocker, mock_df, mock_flagger, mocker_add_flag_db, has_asset_id, policy, should_flag, ): sr = mock_df(has_asset_id=has_asset_id) sr[SFCols.MATCH_POLICY] = policy instance = self._class(sr, mock_flagger) mocker.patch.object(instance, "_get_match_policies_as_string") await instance.bad_match_policy() value = sr.loc[0][OutputColumns.AUDIT_FLAG_BAD_MATCH_POLICY] assert value == should_flag if has_asset_id and should_flag: arg0, arg1, arg2, arg3 = mocker_add_flag_db.call_args.args assert arg0.empty != should_flag assert arg1 == mock_flagger assert arg2 == Flags.BAD_MATCH_POLICY else: assert not mocker_add_flag_db.called @pytest.mark.asyncio @pytest.mark.parametrize( "mock_policy_rules,expected", [ ([], "None"), ( [Mock(action="monetize", territories=["US", "CA"], type="include")], "monetize (include 2)", ), ], ) async def test_get_match_policies_as_string(self, mock_policy_rules, expected): mock_asset_id = "test_asset_id" mock_instance = AsyncMock() mock_instance._yt_checker.get_asset_match_policies.return_value = { mock_asset_id: mock_policy_rules } result = await flags.FlagChecksSR._get_match_policies_as_string( mock_instance, [mock_asset_id] ) assert result == {mock_asset_id: expected} class TestFlagCleanup: _class = flags.FlagCleanupSR def test_rule_can_reactivate(self): """Inherited from _FlagCleanUp.""" flag = Flags.CAN_REACTIVATE discard_flags = [ Flags.NO_ACTIVE_REFERENCES_REASONS_ORCHARD, Flags.NO_ACTIVE_REFERENCES_REASONS_THIRD_PARTY, ] _flags = [ NewAuditFlag(text=flag, row_idx=i, asset_id="123", upc="000") for i, flag in enumerate([flag] + discard_flags) ] instance = self._class(_flags) returns = instance.rule_can_reactivate() assert returns is instance, "Method should return self (i.e. be chainable)." assert instance.flags == [ _flags[0] ], "Only the CAN REACTIVATE flag should remain."