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."