Re-implement cinder forwards (escalations) as a new job in the content infringement queue (#22582)
* Re-implement cinder forwards (escalations) as a property of the job * create report in reviewer queue for escalations * Rename escalations waffle switch to forwarded * limit notes migrated to first 255 characters * Retain existing CinderJob instance; create new, as cinder does * add handle_escalate_action to CELERY_TASK_ROUTES * add backfill_cinder_escalations command * extract clearing NHR from CinderJob.resolve_job to test
This commit is contained in:
Родитель
82ed35e1d5
Коммит
ccf3fdab41
|
@ -79,7 +79,7 @@ class CinderEntity:
|
|||
'authorization': f'Bearer {settings.CINDER_API_TOKEN}',
|
||||
}
|
||||
|
||||
def build_report_payload(self, *, report, reporter):
|
||||
def build_report_payload(self, *, report, reporter, message=''):
|
||||
generator = self.get_context_generator()
|
||||
context = next(generator, self.get_empty_context())
|
||||
if report:
|
||||
|
@ -96,9 +96,7 @@ class CinderEntity:
|
|||
context['relationships'] += [
|
||||
reporter.get_relationship_data(report, 'amo_reporter_of')
|
||||
]
|
||||
message = report.abuse_report.message
|
||||
else:
|
||||
message = ''
|
||||
message = message or report.abuse_report.message
|
||||
entity_attributes = {**self.get_attributes(), **self.get_extended_attributes()}
|
||||
return {
|
||||
'queue_slug': self.queue,
|
||||
|
@ -108,7 +106,7 @@ class CinderEntity:
|
|||
'context': context,
|
||||
}
|
||||
|
||||
def report(self, *, report, reporter):
|
||||
def report(self, *, report, reporter, message=''):
|
||||
"""Build the payload and send the report to Cinder API.
|
||||
|
||||
Return a job_id that can be used by CinderJob.report() to either get an
|
||||
|
@ -117,7 +115,9 @@ class CinderEntity:
|
|||
# type needs to be defined by subclasses
|
||||
raise NotImplementedError
|
||||
url = f'{settings.CINDER_SERVER_URL}create_report'
|
||||
data = self.build_report_payload(report=report, reporter=reporter)
|
||||
data = self.build_report_payload(
|
||||
report=report, reporter=reporter, message=message
|
||||
)
|
||||
response = requests.post(url, json=data, headers=self.get_cinder_http_headers())
|
||||
if response.status_code == 201:
|
||||
return response.json().get('job_id')
|
||||
|
@ -206,6 +206,10 @@ class CinderEntity:
|
|||
a keyword argument."""
|
||||
pass
|
||||
|
||||
def workflow_recreate(self, *, job):
|
||||
"""Recreate a job in a queue."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class CinderUser(CinderEntity):
|
||||
type = 'amo_user'
|
||||
|
@ -294,9 +298,9 @@ class CinderUnauthenticatedReporter(CinderEntity):
|
|||
class CinderAddon(CinderEntity):
|
||||
type = 'amo_addon'
|
||||
|
||||
def __init__(self, addon, version=None):
|
||||
def __init__(self, addon, version_string=None):
|
||||
self.addon = addon
|
||||
self.version = version
|
||||
self.version_string = version_string
|
||||
self.related_users = self.addon.authors.all()
|
||||
|
||||
@property
|
||||
|
@ -470,63 +474,103 @@ class CinderAddonHandledByReviewers(CinderAddon):
|
|||
def queue(cls):
|
||||
return f'{settings.CINDER_QUEUE_PREFIX}{cls.queue_suffix}'
|
||||
|
||||
def flag_for_human_review(self, appeal=False):
|
||||
def flag_for_human_review(
|
||||
self, *, reported_versions, appeal=False, forwarded=False
|
||||
):
|
||||
from olympia.reviewers.models import NeedsHumanReview
|
||||
|
||||
waffle_switch_name = (
|
||||
'dsa-appeals-review' if appeal else 'dsa-abuse-reports-review'
|
||||
'dsa-appeals-review'
|
||||
if appeal
|
||||
else 'dsa-cinder-forwarded-review'
|
||||
if forwarded
|
||||
else 'dsa-abuse-reports-review'
|
||||
)
|
||||
if not waffle.switch_is_active(waffle_switch_name):
|
||||
log.info(
|
||||
'Not adding %s to review queue despite %s because %s switch is off',
|
||||
self.addon,
|
||||
'appeal' if appeal else 'report',
|
||||
'appeal' if appeal else 'forward' if forwarded else 'report',
|
||||
waffle_switch_name,
|
||||
)
|
||||
return
|
||||
|
||||
reason = (
|
||||
NeedsHumanReview.REASONS.ADDON_REVIEW_APPEAL
|
||||
if appeal
|
||||
else NeedsHumanReview.REASONS.CINDER_ESCALATION
|
||||
if forwarded
|
||||
else NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION
|
||||
)
|
||||
nhr_object = NeedsHumanReview(
|
||||
version=self.version, reason=reason, is_active=True
|
||||
)
|
||||
if self.version:
|
||||
if not NeedsHumanReview.objects.filter(
|
||||
version=self.version, reason=reason, is_active=True
|
||||
).exists():
|
||||
nhr_object.save(_no_automatic_activity_log=True)
|
||||
versions_to_log = [self.version]
|
||||
else:
|
||||
versions_to_log = []
|
||||
else:
|
||||
versions_to_log = self.addon.set_needs_human_review_on_latest_versions(
|
||||
reason=reason,
|
||||
ignore_reviewed=False,
|
||||
unique_reason=True,
|
||||
skip_activity_log=True,
|
||||
|
||||
version_objs = (
|
||||
set(
|
||||
self.addon.versions(manager='unfiltered_for_relations')
|
||||
.filter(version__in=reported_versions)
|
||||
.exclude(
|
||||
needshumanreview__reason=reason,
|
||||
needshumanreview__is_active=True,
|
||||
)
|
||||
.no_transforms()
|
||||
)
|
||||
if reported_versions
|
||||
else set()
|
||||
)
|
||||
nhr_object = None
|
||||
# We need custom save() and post_save to be triggered, so we can't
|
||||
# optimize this via bulk_create().
|
||||
for version in version_objs:
|
||||
nhr_object = NeedsHumanReview(
|
||||
version=version, reason=reason, is_active=True
|
||||
)
|
||||
nhr_object.save(_no_automatic_activity_log=True)
|
||||
# If we have more versions specified than versions we flagged, flag latest
|
||||
# to be safe. (Either because there was an unknown version, or a None)
|
||||
if len(version_objs) != len(reported_versions) or len(reported_versions) == 0:
|
||||
version_objs = version_objs.union(
|
||||
self.addon.set_needs_human_review_on_latest_versions(
|
||||
reason=reason,
|
||||
ignore_reviewed=False,
|
||||
unique_reason=True,
|
||||
skip_activity_log=True,
|
||||
)
|
||||
)
|
||||
if version_objs:
|
||||
version_objs = sorted(version_objs, key=lambda v: v.id)
|
||||
# we just need this for get_reason_display
|
||||
nhr_object = nhr_object or NeedsHumanReview(
|
||||
version=version_objs[-1],
|
||||
reason=reason,
|
||||
is_active=True,
|
||||
)
|
||||
if versions_to_log:
|
||||
activity.log_create(
|
||||
amo.LOG.NEEDS_HUMAN_REVIEW_CINDER,
|
||||
*versions_to_log,
|
||||
*version_objs,
|
||||
details={'comments': nhr_object.get_reason_display()},
|
||||
user=core.get_user() or get_task_user(),
|
||||
)
|
||||
|
||||
def post_report(self, job):
|
||||
if not job.is_appeal:
|
||||
self.flag_for_human_review(appeal=False)
|
||||
reported_version = self.version_string
|
||||
self.flag_for_human_review(
|
||||
reported_versions={reported_version}, appeal=False
|
||||
)
|
||||
# If our report was added to an appeal job (i.e. an appeal was ongoing,
|
||||
# and a report was made against the add-on), don't flag the add-on for
|
||||
# human review again: we should already have one because of the appeal.
|
||||
|
||||
def appeal(self, *args, **kwargs):
|
||||
self.flag_for_human_review(appeal=True)
|
||||
self.flag_for_human_review(reported_versions=set(), appeal=True)
|
||||
return super().appeal(*args, **kwargs)
|
||||
|
||||
def workflow_recreate(self, *, job):
|
||||
reported_versions = set(
|
||||
job.abusereport_set.values_list('addon_version', flat=True)
|
||||
)
|
||||
notes = job.decision.notes if job.decision else ''
|
||||
self.flag_for_human_review(reported_versions=reported_versions, forwarded=True)
|
||||
return self.report(report=None, reporter=None, message=notes)
|
||||
|
||||
|
||||
class CinderReport(CinderEntity):
|
||||
type = 'amo_report'
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
import olympia.core.logger
|
||||
from olympia.abuse.models import CinderDecision
|
||||
from olympia.abuse.tasks import handle_escalate_action
|
||||
from olympia.constants.abuse import DECISION_ACTIONS
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
log = olympia.core.logger.getLogger('z.abuse')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
qs = CinderDecision.objects.filter(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
|
||||
cinder_job__forwarded_to_job__isnull=True,
|
||||
cinder_job__isnull=False,
|
||||
)
|
||||
for decision in qs:
|
||||
handle_escalate_action.delay(job_pk=decision.cinder_job.pk)
|
|
@ -0,0 +1,16 @@
|
|||
# Generated by Django 4.2.15 on 2024-08-23 12:36
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
from olympia.core.db.migrations import RenameWaffleSwitch
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('abuse', '0036_alter_abusereport_appellant_job_cinderappealtext'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
RenameWaffleSwitch('dsa-cinder-escalations-review', 'dsa-cinder-forwarded-review'),
|
||||
]
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 4.2.15 on 2024-08-27 17:44
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('abuse', '0037_auto_20240823_1236'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='cinderjob',
|
||||
name='forwarded_to_job',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='forwarded_from_jobs', to='abuse.cinderjob'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cinderdecision',
|
||||
name='action',
|
||||
field=models.PositiveSmallIntegerField(choices=[(1, 'User ban'), (2, 'Add-on disable'), (3, 'Forward add-on to reviewers'), (5, 'Rating delete'), (6, 'Collection delete'), (7, 'Approved (no action)'), (8, 'Add-on version reject'), (9, 'Add-on version delayed reject warning'), (10, 'Approved (new version approval)'), (11, 'Invalid report, so ignored'), (12, 'Content already removed (no action)')]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cinderpolicy',
|
||||
name='default_cinder_action',
|
||||
field=models.PositiveSmallIntegerField(blank=True, choices=[(1, 'User ban'), (2, 'Add-on disable'), (3, 'Forward add-on to reviewers'), (5, 'Rating delete'), (6, 'Collection delete'), (7, 'Approved (no action)'), (8, 'Add-on version reject'), (9, 'Add-on version delayed reject warning'), (10, 'Approved (new version approval)'), (11, 'Invalid report, so ignored'), (12, 'Content already removed (no action)')], null=True),
|
||||
),
|
||||
]
|
|
@ -55,10 +55,7 @@ class CinderJobQuerySet(BaseQuerySet):
|
|||
return self.filter(target_addon=addon).order_by('-pk')
|
||||
|
||||
def unresolved(self):
|
||||
return self.filter(
|
||||
models.Q(decision__isnull=True)
|
||||
| models.Q(decision__action__in=tuple(DECISION_ACTIONS.UNRESOLVED.values))
|
||||
)
|
||||
return self.filter(decision__isnull=True)
|
||||
|
||||
def resolvable_in_reviewer_tools(self):
|
||||
return self.filter(resolvable_in_reviewer_tools=True)
|
||||
|
@ -89,6 +86,12 @@ class CinderJob(ModelBase):
|
|||
related_name='cinder_job',
|
||||
)
|
||||
resolvable_in_reviewer_tools = models.BooleanField(default=None, null=True)
|
||||
forwarded_to_job = models.ForeignKey(
|
||||
to='self',
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='forwarded_from_jobs',
|
||||
)
|
||||
|
||||
objects = CinderJobManager()
|
||||
|
||||
|
@ -129,17 +132,10 @@ class CinderJob(ModelBase):
|
|||
cls, target, *, resolved_in_reviewer_tools, addon_version_string=None
|
||||
):
|
||||
if isinstance(target, Addon):
|
||||
version = (
|
||||
addon_version_string
|
||||
and target.versions(manager='unfiltered_for_relations')
|
||||
.filter(version=addon_version_string)
|
||||
.no_transforms()
|
||||
.first()
|
||||
)
|
||||
if resolved_in_reviewer_tools:
|
||||
return CinderAddonHandledByReviewers(target, version)
|
||||
return CinderAddonHandledByReviewers(target, addon_version_string)
|
||||
else:
|
||||
return CinderAddon(target, version)
|
||||
return CinderAddon(target, addon_version_string)
|
||||
elif isinstance(target, UserProfile):
|
||||
return CinderUser(target)
|
||||
elif isinstance(target, Rating):
|
||||
|
@ -235,6 +231,20 @@ class CinderJob(ModelBase):
|
|||
is_appeal=True,
|
||||
)
|
||||
|
||||
def handle_job_recreated(self, new_job_id):
|
||||
new_job, _ = CinderJob.objects.update_or_create(
|
||||
job_id=new_job_id,
|
||||
defaults={
|
||||
'resolvable_in_reviewer_tools': True,
|
||||
'target_addon': self.target_addon,
|
||||
},
|
||||
)
|
||||
# Update our fks to connected objects
|
||||
AbuseReport.objects.filter(cinder_job=self).update(cinder_job=new_job)
|
||||
AbuseReport.objects.filter(appellant_job=self).update(appellant_job=new_job)
|
||||
CinderDecision.objects.filter(appeal_job=self).update(appeal_job=new_job)
|
||||
self.update(forwarded_to_job=new_job)
|
||||
|
||||
def process_decision(
|
||||
self,
|
||||
*,
|
||||
|
@ -270,11 +280,7 @@ class CinderJob(ModelBase):
|
|||
],
|
||||
},
|
||||
)
|
||||
self.update(
|
||||
decision=cinder_decision,
|
||||
resolvable_in_reviewer_tools=self.resolvable_in_reviewer_tools
|
||||
or decision_action == DECISION_ACTIONS.AMO_ESCALATE_ADDON,
|
||||
)
|
||||
self.update(decision=cinder_decision)
|
||||
policies = CinderPolicy.objects.filter(
|
||||
uuid__in=policy_ids
|
||||
).without_parents_if_their_children_are_present()
|
||||
|
@ -290,8 +296,6 @@ class CinderJob(ModelBase):
|
|||
def resolve_job(self, *, log_entry):
|
||||
"""This is called for reviewer tools originated decisions.
|
||||
See process_decision for cinder originated decisions."""
|
||||
from olympia.reviewers.models import NeedsHumanReview
|
||||
|
||||
abuse_report_or_decision = (
|
||||
self.appealed_decisions.first() or self.abusereport_set.first()
|
||||
)
|
||||
|
@ -302,10 +306,6 @@ class CinderJob(ModelBase):
|
|||
abuse_report_or_decision.target,
|
||||
resolved_in_reviewer_tools=self.resolvable_in_reviewer_tools,
|
||||
)
|
||||
was_escalated = (
|
||||
self.decision
|
||||
and self.decision.action == DECISION_ACTIONS.AMO_ESCALATE_ADDON
|
||||
)
|
||||
|
||||
cinder_decision = self.decision or CinderDecision(
|
||||
addon=abuse_report_or_decision.addon,
|
||||
|
@ -328,41 +328,41 @@ class CinderJob(ModelBase):
|
|||
else:
|
||||
self.pending_rejections.clear()
|
||||
if cinder_decision.addon_id:
|
||||
# We don't want to clear a NeedsHumanReview caused by a job that
|
||||
# isn't resolved yet, but there is no link between NHR and jobs.
|
||||
# So for each possible reason, we look if there are unresolved jobs
|
||||
# and only clear NHR for that reason if there aren't any jobs left.
|
||||
base_unresolved_jobs_qs = (
|
||||
self.__class__.objects.for_addon(cinder_decision.addon)
|
||||
.unresolved()
|
||||
.resolvable_in_reviewer_tools()
|
||||
)
|
||||
if was_escalated:
|
||||
has_unresolved_jobs_with_similar_reason = (
|
||||
base_unresolved_jobs_qs.filter(
|
||||
decision__action=DECISION_ACTIONS.AMO_ESCALATE_ADDON
|
||||
).exists()
|
||||
)
|
||||
reason = NeedsHumanReview.REASONS.CINDER_ESCALATION
|
||||
elif self.is_appeal:
|
||||
has_unresolved_jobs_with_similar_reason = (
|
||||
base_unresolved_jobs_qs.filter(
|
||||
appealed_decisions__isnull=False
|
||||
).exists()
|
||||
)
|
||||
reason = NeedsHumanReview.REASONS.ADDON_REVIEW_APPEAL
|
||||
else:
|
||||
# If the job we're resolving was not an appeal or escalation
|
||||
# then all abuse reports are considered dealt with.
|
||||
has_unresolved_jobs_with_similar_reason = None
|
||||
reason = NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION
|
||||
if not has_unresolved_jobs_with_similar_reason:
|
||||
NeedsHumanReview.objects.filter(
|
||||
version__addon_id=cinder_decision.addon_id,
|
||||
is_active=True,
|
||||
reason=reason,
|
||||
).update(is_active=False)
|
||||
cinder_decision.addon.update_all_due_dates()
|
||||
self.clear_needs_human_review_flags()
|
||||
|
||||
def clear_needs_human_review_flags(self):
|
||||
from olympia.reviewers.models import NeedsHumanReview
|
||||
|
||||
# We don't want to clear a NeedsHumanReview caused by a job that
|
||||
# isn't resolved yet, but there is no link between NHR and jobs.
|
||||
# So for each possible reason, we look if there are unresolved jobs
|
||||
# and only clear NHR for that reason if there aren't any jobs left.
|
||||
addon = self.decision.addon
|
||||
base_unresolved_jobs_qs = (
|
||||
self.__class__.objects.for_addon(addon)
|
||||
.unresolved()
|
||||
.resolvable_in_reviewer_tools()
|
||||
)
|
||||
if self.forwarded_from_jobs.exists():
|
||||
has_unresolved_jobs_with_similar_reason = base_unresolved_jobs_qs.filter(
|
||||
forwarded_from_jobs__isnull=False
|
||||
).exists()
|
||||
reason = NeedsHumanReview.REASONS.CINDER_ESCALATION
|
||||
elif self.is_appeal:
|
||||
has_unresolved_jobs_with_similar_reason = base_unresolved_jobs_qs.filter(
|
||||
appealed_decisions__isnull=False
|
||||
).exists()
|
||||
reason = NeedsHumanReview.REASONS.ADDON_REVIEW_APPEAL
|
||||
else:
|
||||
# If the job we're resolving was not an appeal or escalation
|
||||
# then all abuse reports are considered dealt with.
|
||||
has_unresolved_jobs_with_similar_reason = None
|
||||
reason = NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION
|
||||
if not has_unresolved_jobs_with_similar_reason:
|
||||
NeedsHumanReview.objects.filter(
|
||||
version__addon_id=addon.id, is_active=True, reason=reason
|
||||
).update(is_active=False)
|
||||
addon.update_all_due_dates()
|
||||
|
||||
|
||||
class AbuseReportQuerySet(BaseQuerySet):
|
||||
|
@ -678,6 +678,7 @@ class AbuseReport(ModelBase):
|
|||
)
|
||||
cinder_job = models.ForeignKey(CinderJob, null=True, on_delete=models.SET_NULL)
|
||||
reporter_appeal_date = models.DateTimeField(default=None, null=True)
|
||||
# The appeal from the reporter of this report, if they have appealed
|
||||
appellant_job = models.ForeignKey(
|
||||
CinderJob,
|
||||
null=True,
|
||||
|
|
|
@ -189,3 +189,17 @@ def sync_cinder_policies():
|
|||
raise
|
||||
else:
|
||||
statsd.incr('abuse.tasks.sync_cinder_policies.success')
|
||||
|
||||
|
||||
@task
|
||||
@use_primary_db
|
||||
def handle_escalate_action(*, job_pk):
|
||||
old_job = CinderJob.objects.get(id=job_pk)
|
||||
entity_helper = CinderJob.get_entity_helper(
|
||||
old_job.target,
|
||||
addon_version_string=None,
|
||||
resolved_in_reviewer_tools=True,
|
||||
)
|
||||
job_id = entity_helper.workflow_recreate(job=old_job)
|
||||
|
||||
old_job.handle_job_recreated(new_job_id=job_id)
|
||||
|
|
|
@ -46,13 +46,13 @@ from ..cinder import (
|
|||
|
||||
|
||||
class BaseTestCinderCase:
|
||||
cinder_class = None # Override in child classes
|
||||
CinderClass = None # Override in child classes
|
||||
expected_queue_suffix = None # Override in child classes
|
||||
expected_queries_for_report = -1 # Override in child classes
|
||||
|
||||
def test_queue(self):
|
||||
target = self._create_dummy_target()
|
||||
cinder_entity = self.cinder_class(target)
|
||||
cinder_entity = self.CinderClass(target)
|
||||
assert cinder_entity.queue_suffix == self.expected_queue_suffix
|
||||
assert (
|
||||
cinder_entity.queue
|
||||
|
@ -106,7 +106,7 @@ class BaseTestCinderCase:
|
|||
# loaded before.
|
||||
abuse_report.reload()
|
||||
report = CinderReport(abuse_report)
|
||||
cinder_instance = self.cinder_class(abuse_report.target)
|
||||
cinder_instance = self.CinderClass(abuse_report.target)
|
||||
with self.assertNumQueries(self.expected_queries_for_report):
|
||||
assert cinder_instance.report(report=report, reporter=None) == '1234-xyz'
|
||||
assert (
|
||||
|
@ -132,7 +132,7 @@ class BaseTestCinderCase:
|
|||
|
||||
def _test_appeal(self, appealer, cinder_instance=None):
|
||||
fake_decision_id = 'decision-id-to-appeal-666'
|
||||
cinder_instance = cinder_instance or self.cinder_class(
|
||||
cinder_instance = cinder_instance or self.CinderClass(
|
||||
self._create_dummy_target()
|
||||
)
|
||||
|
||||
|
@ -170,14 +170,14 @@ class BaseTestCinderCase:
|
|||
self._test_appeal(CinderUnauthenticatedReporter('itsme', 'm@r.io'))
|
||||
|
||||
def test_get_str(self):
|
||||
instance = self.cinder_class(self._create_dummy_target())
|
||||
instance = self.CinderClass(self._create_dummy_target())
|
||||
assert instance.get_str(123) == '123'
|
||||
assert instance.get_str(None) == ''
|
||||
assert instance.get_str(' ') == ''
|
||||
|
||||
|
||||
class TestCinderAddon(BaseTestCinderCase, TestCase):
|
||||
cinder_class = CinderAddon
|
||||
CinderClass = CinderAddon
|
||||
# 2 queries expected:
|
||||
# - Authors (can't use the listed_authors transformer, we want non-listed as well,
|
||||
# and we have custom limits for batch-sending relationships)
|
||||
|
@ -190,7 +190,7 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
|
||||
def test_queue_theme(self):
|
||||
target = self._create_dummy_target(type=amo.ADDON_STATICTHEME)
|
||||
cinder_entity = self.cinder_class(target)
|
||||
cinder_entity = self.CinderClass(target)
|
||||
expected_queue_suffix = 'themes'
|
||||
assert cinder_entity.queue_suffix == expected_queue_suffix
|
||||
assert (
|
||||
|
@ -208,7 +208,7 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
version_kw={'release_notes': 'Søme release notes'},
|
||||
)
|
||||
message = ' bad addon!'
|
||||
cinder_addon = self.cinder_class(addon)
|
||||
cinder_addon = self.CinderClass(addon)
|
||||
encoded_message = cinder_addon.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(guid=addon.guid, message=message)
|
||||
data = cinder_addon.build_report_payload(
|
||||
|
@ -373,7 +373,7 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
)
|
||||
self.make_addon_promoted(addon, group=RECOMMENDED)
|
||||
message = ' bad addon!'
|
||||
cinder_addon = self.cinder_class(addon)
|
||||
cinder_addon = self.CinderClass(addon)
|
||||
encoded_message = cinder_addon.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(guid=addon.guid, message=message)
|
||||
data = cinder_addon.build_report_payload(
|
||||
|
@ -440,7 +440,7 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
)
|
||||
self.make_addon_promoted(addon, group=NOTABLE)
|
||||
message = ' bad addon!'
|
||||
cinder_addon = self.cinder_class(addon)
|
||||
cinder_addon = self.CinderClass(addon)
|
||||
encoded_message = cinder_addon.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(guid=addon.guid, message=message)
|
||||
data = cinder_addon.build_report_payload(
|
||||
|
@ -506,7 +506,7 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
author = user_factory()
|
||||
addon = self._create_dummy_target(users=[author])
|
||||
message = '@bad addon!'
|
||||
cinder_addon = self.cinder_class(addon)
|
||||
cinder_addon = self.CinderClass(addon)
|
||||
abuse_report = AbuseReport.objects.create(guid=addon.guid, message=message)
|
||||
|
||||
data = cinder_addon.build_report_payload(
|
||||
|
@ -625,7 +625,7 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
def test_build_report_payload_with_author_and_reporter_being_the_same(self):
|
||||
user = user_factory()
|
||||
addon = self._create_dummy_target(users=[user])
|
||||
cinder_addon = self.cinder_class(addon)
|
||||
cinder_addon = self.CinderClass(addon)
|
||||
message = 'self reporting! '
|
||||
encoded_message = cinder_addon.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(guid=addon.guid, message=message)
|
||||
|
@ -710,7 +710,7 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
)
|
||||
(p0, p1) = list(addon.previews.all())
|
||||
Preview.objects.create(addon=addon, position=5) # No file, ignored
|
||||
cinder_addon = self.cinder_class(addon)
|
||||
cinder_addon = self.CinderClass(addon)
|
||||
message = ' report with images '
|
||||
encoded_message = cinder_addon.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(guid=addon.guid, message=message)
|
||||
|
@ -812,7 +812,7 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
VersionPreview.objects.create(
|
||||
version=addon.current_version, position=5
|
||||
) # No file, ignored
|
||||
cinder_addon = self.cinder_class(addon)
|
||||
cinder_addon = self.CinderClass(addon)
|
||||
message = 'report with images'
|
||||
encoded_message = cinder_addon.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(guid=addon.guid, message=message)
|
||||
|
@ -882,7 +882,7 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
addon = self._create_dummy_target()
|
||||
for _ in range(0, 6):
|
||||
addon.authors.add(user_factory())
|
||||
cinder_addon = self.cinder_class(addon)
|
||||
cinder_addon = self.CinderClass(addon)
|
||||
message = 'report for lots of relationships'
|
||||
abuse_report = AbuseReport.objects.create(guid=addon.guid, message=message)
|
||||
data = cinder_addon.build_report_payload(
|
||||
|
@ -960,7 +960,7 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
addon = self._create_dummy_target()
|
||||
for _ in range(0, 6):
|
||||
addon.authors.add(user_factory())
|
||||
cinder_addon = self.cinder_class(addon)
|
||||
cinder_addon = self.CinderClass(addon)
|
||||
|
||||
responses.add(
|
||||
responses.POST,
|
||||
|
@ -1064,7 +1064,7 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
addon = self._create_dummy_target()
|
||||
for _ in range(0, 6):
|
||||
addon.authors.add(user_factory())
|
||||
cinder_addon = self.cinder_class(addon)
|
||||
cinder_addon = self.CinderClass(addon)
|
||||
|
||||
responses.add(
|
||||
responses.POST,
|
||||
|
@ -1078,8 +1078,9 @@ class TestCinderAddon(BaseTestCinderCase, TestCase):
|
|||
|
||||
@override_switch('dsa-abuse-reports-review', active=True)
|
||||
@override_switch('dsa-appeals-review', active=True)
|
||||
@override_switch('dsa-cinder-forwarded-review', active=True)
|
||||
class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
||||
cinder_class = CinderAddonHandledByReviewers
|
||||
CinderClass = CinderAddonHandledByReviewers
|
||||
# For rendering the payload to Cinder like CinderAddon:
|
||||
# - 1 Fetch Addon authors
|
||||
# - 2 Fetch Promoted Addon
|
||||
|
@ -1089,14 +1090,14 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
def test_queue(self):
|
||||
super().test_queue()
|
||||
# For this class the property should be guaranteed to be static.
|
||||
assert self.cinder_class.queue == 'amo-env-addon-infringement'
|
||||
assert self.CinderClass.queue == 'amo-env-addon-infringement'
|
||||
|
||||
def test_queue_theme(self):
|
||||
# Contrary to reports handled by Cinder moderators, for reports handled
|
||||
# by AMO reviewers the queue should remain the same regardless of the
|
||||
# addon-type.
|
||||
target = self._create_dummy_target(type=amo.ADDON_STATICTHEME)
|
||||
cinder_entity = self.cinder_class(target)
|
||||
cinder_entity = self.CinderClass(target)
|
||||
assert cinder_entity.queue_suffix == self.expected_queue_suffix
|
||||
assert (
|
||||
cinder_entity.queue
|
||||
|
@ -1104,7 +1105,7 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
)
|
||||
|
||||
def setUp(self):
|
||||
user_factory(id=settings.TASK_USER_ID)
|
||||
self.task_user = user_factory(id=settings.TASK_USER_ID)
|
||||
|
||||
def test_report(self):
|
||||
addon = self._create_dummy_target()
|
||||
|
@ -1119,7 +1120,7 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
# Adding the NHR is done by post_report().
|
||||
assert addon.current_version.needshumanreview_set.count() == 0
|
||||
job = CinderJob.objects.create(job_id='1234-xyz')
|
||||
cinder_instance = self.cinder_class(addon)
|
||||
cinder_instance = self.CinderClass(addon)
|
||||
with self.assertNumQueries(13):
|
||||
# - 1 Fetch Cinder Decision
|
||||
# - 2 Fetch Version
|
||||
|
@ -1174,7 +1175,7 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
guid=addon.guid, addon_version=other_version.version
|
||||
)
|
||||
report = CinderReport(abuse_report)
|
||||
cinder_instance = self.cinder_class(addon, other_version)
|
||||
cinder_instance = self.CinderClass(addon, other_version)
|
||||
assert cinder_instance.report(report=report, reporter=None)
|
||||
job = CinderJob.objects.create(job_id='1234-xyz')
|
||||
assert not addon.current_version.needshumanreview_set.exists()
|
||||
|
@ -1192,7 +1193,7 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
addon = self._create_dummy_target()
|
||||
addon.current_version.file.update(is_signed=True)
|
||||
self._test_appeal(
|
||||
CinderUnauthenticatedReporter('itsme', 'm@r.io'), self.cinder_class(addon)
|
||||
CinderUnauthenticatedReporter('itsme', 'm@r.io'), self.CinderClass(addon)
|
||||
)
|
||||
assert (
|
||||
addon.current_version.needshumanreview_set.get().reason
|
||||
|
@ -1202,7 +1203,7 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
def test_appeal_logged_in(self):
|
||||
addon = self._create_dummy_target()
|
||||
addon.current_version.file.update(is_signed=True)
|
||||
self._test_appeal(CinderUser(user_factory()), self.cinder_class(addon))
|
||||
self._test_appeal(CinderUser(user_factory()), self.CinderClass(addon))
|
||||
assert (
|
||||
addon.current_version.needshumanreview_set.get().reason
|
||||
== NeedsHumanReview.REASONS.ADDON_REVIEW_APPEAL
|
||||
|
@ -1216,7 +1217,7 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
# etc since the waffle switch is off. So we're back to the same number of
|
||||
# queries made by the reports that go to Cinder.
|
||||
self.expected_queries_for_report = TestCinderAddon.expected_queries_for_report
|
||||
self._test_appeal(CinderUser(user_factory()), self.cinder_class(addon))
|
||||
self._test_appeal(CinderUser(user_factory()), self.CinderClass(addon))
|
||||
assert addon.current_version.needshumanreview_set.count() == 0
|
||||
|
||||
def test_report_with_ongoing_appeal(self):
|
||||
|
@ -1234,15 +1235,16 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
# count more predictable.
|
||||
waffle.switch_is_active('dsa-abuse-reports-review')
|
||||
self._test_report(addon)
|
||||
cinder_instance = self.cinder_class(addon)
|
||||
cinder_instance = self.CinderClass(addon)
|
||||
cinder_instance.post_report(job)
|
||||
# The add-on does not get flagged again while the appeal is ongoing.
|
||||
assert addon.current_version.needshumanreview_set.count() == 0
|
||||
|
||||
def test_report_with_ongoing_escalated_appeal(self):
|
||||
def test_report_with_ongoing_forwarded_appeal(self):
|
||||
addon = self._create_dummy_target()
|
||||
addon.current_version.file.update(is_signed=True)
|
||||
job = CinderJob.objects.create(job_id='1234-xyz')
|
||||
CinderJob.objects.create(forwarded_to_job=job)
|
||||
job.appealed_decisions.add(
|
||||
CinderDecision.objects.create(
|
||||
addon=addon,
|
||||
|
@ -1250,20 +1252,11 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
action=DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON,
|
||||
)
|
||||
)
|
||||
# Job we're attaching the report does have a decision but it's an
|
||||
# escalation so it's still considered ongoing/open.
|
||||
job.update(
|
||||
decision=CinderDecision.objects.create(
|
||||
addon=addon,
|
||||
cinder_id='1234-escalation',
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
|
||||
)
|
||||
)
|
||||
# Trigger switch_is_active to ensure it's cached to make db query
|
||||
# count more predictable.
|
||||
waffle.switch_is_active('dsa-abuse-reports-review')
|
||||
self._test_report(addon)
|
||||
cinder_instance = self.cinder_class(addon)
|
||||
cinder_instance = self.CinderClass(addon)
|
||||
cinder_instance.post_report(job)
|
||||
# The add-on does not get flagged again while the appeal is ongoing.
|
||||
assert addon.current_version.needshumanreview_set.count() == 0
|
||||
|
@ -1284,7 +1277,7 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
json={'error': 'reason'},
|
||||
status=400,
|
||||
)
|
||||
cinder_instance = self.cinder_class(target)
|
||||
cinder_instance = self.CinderClass(target)
|
||||
assert (
|
||||
cinder_instance.create_decision(
|
||||
action=DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON.api_value,
|
||||
|
@ -1326,7 +1319,7 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
json={'error': 'reason'},
|
||||
status=400,
|
||||
)
|
||||
cinder_instance = self.cinder_class(target)
|
||||
cinder_instance = self.CinderClass(target)
|
||||
assert (
|
||||
cinder_instance.create_job_decision(
|
||||
job_id=job.job_id,
|
||||
|
@ -1368,12 +1361,165 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
|
|||
json={'error': 'reason'},
|
||||
status=400,
|
||||
)
|
||||
cinder_instance = self.cinder_class(target)
|
||||
cinder_instance = self.CinderClass(target)
|
||||
assert cinder_instance.close_job(job_id=job_id) == job_id
|
||||
|
||||
def test_workflow_recreate(self):
|
||||
addon = self._create_dummy_target()
|
||||
listed_version = addon.current_version
|
||||
listed_version.file.update(is_signed=True)
|
||||
unlisted_version = version_factory(
|
||||
addon=addon, channel=amo.CHANNEL_UNLISTED, file_kw={'is_signed': True}
|
||||
)
|
||||
ActivityLog.objects.all().delete()
|
||||
cinder_instance = self.CinderClass(addon)
|
||||
cinder_job = CinderJob.objects.create(target_addon=addon, job_id='1')
|
||||
AbuseReport.objects.create(
|
||||
reason=AbuseReport.REASONS.HATEFUL_VIOLENT_DECEPTIVE,
|
||||
guid=addon.guid,
|
||||
cinder_job=cinder_job,
|
||||
reporter_email='email@domain.com',
|
||||
)
|
||||
AbuseReport.objects.create(
|
||||
reason=AbuseReport.REASONS.HATEFUL_VIOLENT_DECEPTIVE,
|
||||
guid=addon.guid,
|
||||
cinder_job=cinder_job,
|
||||
reporter=user_factory(),
|
||||
)
|
||||
responses.add(
|
||||
responses.POST,
|
||||
f'{settings.CINDER_SERVER_URL}create_report',
|
||||
json={'job_id': '2'},
|
||||
status=201,
|
||||
)
|
||||
|
||||
assert cinder_instance.workflow_recreate(job=cinder_job) == '2'
|
||||
|
||||
assert addon.reload().status == amo.STATUS_APPROVED
|
||||
assert (
|
||||
listed_version.reload().needshumanreview_set.get().reason
|
||||
== NeedsHumanReview.REASONS.CINDER_ESCALATION
|
||||
)
|
||||
assert (
|
||||
unlisted_version.reload().needshumanreview_set.get().reason
|
||||
== NeedsHumanReview.REASONS.CINDER_ESCALATION
|
||||
)
|
||||
assert ActivityLog.objects.count() == 1
|
||||
activity = ActivityLog.objects.filter(
|
||||
action=amo.LOG.NEEDS_HUMAN_REVIEW_CINDER.id
|
||||
).get()
|
||||
assert activity.arguments == [listed_version, unlisted_version]
|
||||
assert activity.user == self.task_user
|
||||
|
||||
# but if we have a version specified, we flag that version
|
||||
NeedsHumanReview.objects.all().delete()
|
||||
other_version = version_factory(
|
||||
addon=addon, file_kw={'status': amo.STATUS_DISABLED, 'is_signed': True}
|
||||
)
|
||||
assert not other_version.due_date
|
||||
ActivityLog.objects.all().delete()
|
||||
cinder_job.abusereport_set.update(addon_version=other_version.version)
|
||||
cinder_instance.workflow_recreate(job=cinder_job)
|
||||
assert not listed_version.reload().needshumanreview_set.exists()
|
||||
assert not unlisted_version.reload().needshumanreview_set.exists()
|
||||
other_version.reload()
|
||||
assert other_version.due_date
|
||||
assert (
|
||||
other_version.needshumanreview_set.get().reason
|
||||
== NeedsHumanReview.REASONS.CINDER_ESCALATION
|
||||
)
|
||||
assert ActivityLog.objects.count() == 1
|
||||
activity = ActivityLog.objects.get(action=amo.LOG.NEEDS_HUMAN_REVIEW_CINDER.id)
|
||||
assert activity.arguments == [other_version]
|
||||
assert activity.user == self.task_user
|
||||
|
||||
def test_workflow_recreate_no_versions_to_flag(self):
|
||||
addon = self._create_dummy_target()
|
||||
listed_version = addon.current_version
|
||||
listed_version.file.update(is_signed=True)
|
||||
unlisted_version = version_factory(
|
||||
addon=addon, channel=amo.CHANNEL_UNLISTED, file_kw={'is_signed': True}
|
||||
)
|
||||
NeedsHumanReview.objects.create(
|
||||
reason=NeedsHumanReview.REASONS.CINDER_ESCALATION, version=listed_version
|
||||
)
|
||||
NeedsHumanReview.objects.create(
|
||||
reason=NeedsHumanReview.REASONS.CINDER_ESCALATION, version=unlisted_version
|
||||
)
|
||||
assert NeedsHumanReview.objects.count() == 2
|
||||
ActivityLog.objects.all().delete()
|
||||
responses.add(
|
||||
responses.POST,
|
||||
f'{settings.CINDER_SERVER_URL}create_report',
|
||||
json={'job_id': '2'},
|
||||
status=201,
|
||||
)
|
||||
|
||||
cinder_instance = self.CinderClass(addon)
|
||||
cinder_job = CinderJob.objects.create(target_addon=addon, job_id='1')
|
||||
|
||||
assert cinder_instance.workflow_recreate(job=cinder_job) == '2'
|
||||
assert NeedsHumanReview.objects.count() == 2
|
||||
assert ActivityLog.objects.count() == 0
|
||||
|
||||
@override_switch('dsa-cinder-forwarded-review', active=False)
|
||||
def test_workflow_recreate_waffle_switch_off(self):
|
||||
# Escalation when the waffle switch is off is essentially a no-op on
|
||||
# AMO side.
|
||||
addon = self._create_dummy_target()
|
||||
listed_version = addon.current_version
|
||||
listed_version.file.update(is_signed=True)
|
||||
unlisted_version = version_factory(
|
||||
addon=addon, channel=amo.CHANNEL_UNLISTED, file_kw={'is_signed': True}
|
||||
)
|
||||
ActivityLog.objects.all().delete()
|
||||
cinder_instance = self.CinderClass(addon)
|
||||
cinder_job = CinderJob.objects.create(target_addon=addon)
|
||||
AbuseReport.objects.create(
|
||||
reason=AbuseReport.REASONS.HATEFUL_VIOLENT_DECEPTIVE,
|
||||
guid=addon.guid,
|
||||
cinder_job=cinder_job,
|
||||
reporter_email='email@domain.com',
|
||||
)
|
||||
AbuseReport.objects.create(
|
||||
reason=AbuseReport.REASONS.HATEFUL_VIOLENT_DECEPTIVE,
|
||||
guid=addon.guid,
|
||||
cinder_job=cinder_job,
|
||||
reporter=user_factory(),
|
||||
)
|
||||
responses.add(
|
||||
responses.POST,
|
||||
f'{settings.CINDER_SERVER_URL}create_report',
|
||||
json={'job_id': '2'},
|
||||
status=201,
|
||||
)
|
||||
|
||||
assert cinder_instance.workflow_recreate(job=cinder_job) == '2'
|
||||
|
||||
assert addon.reload().status == amo.STATUS_APPROVED
|
||||
assert not listed_version.reload().needshumanreview_set.exists()
|
||||
assert not listed_version.due_date
|
||||
assert not unlisted_version.reload().needshumanreview_set.exists()
|
||||
assert not unlisted_version.due_date
|
||||
assert ActivityLog.objects.count() == 0
|
||||
|
||||
other_version = version_factory(
|
||||
addon=addon, file_kw={'status': amo.STATUS_DISABLED, 'is_signed': True}
|
||||
)
|
||||
assert not other_version.due_date
|
||||
ActivityLog.objects.all().delete()
|
||||
cinder_job.abusereport_set.update(addon_version=other_version.version)
|
||||
cinder_instance.workflow_recreate(job=cinder_job)
|
||||
assert not listed_version.reload().needshumanreview_set.exists()
|
||||
assert not unlisted_version.reload().needshumanreview_set.exists()
|
||||
other_version.reload()
|
||||
assert not other_version.due_date
|
||||
assert not listed_version.reload().needshumanreview_set.exists()
|
||||
assert not unlisted_version.reload().needshumanreview_set.exists()
|
||||
|
||||
|
||||
class TestCinderUser(BaseTestCinderCase, TestCase):
|
||||
cinder_class = CinderUser
|
||||
CinderClass = CinderUser
|
||||
# 2 queries expected:
|
||||
# - Related add-ons
|
||||
# - Number of listed add-ons
|
||||
|
@ -1391,7 +1537,7 @@ class TestCinderUser(BaseTestCinderCase, TestCase):
|
|||
homepage='http://home.example.com',
|
||||
)
|
||||
message = ' bad person!'
|
||||
cinder_user = self.cinder_class(user)
|
||||
cinder_user = self.CinderClass(user)
|
||||
encoded_message = cinder_user.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(user=user, message=message)
|
||||
|
||||
|
@ -1544,7 +1690,7 @@ class TestCinderUser(BaseTestCinderCase, TestCase):
|
|||
def test_build_report_payload_with_author_and_reporter_being_the_same(self):
|
||||
user = self._create_dummy_target()
|
||||
addon = addon_factory(users=[user])
|
||||
cinder_user = self.cinder_class(user)
|
||||
cinder_user = self.CinderClass(user)
|
||||
message = 'I dont like this guy'
|
||||
encoded_message = cinder_user.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(user=user, message=message)
|
||||
|
@ -1620,7 +1766,7 @@ class TestCinderUser(BaseTestCinderCase, TestCase):
|
|||
def test_build_report_payload_addon_author(self):
|
||||
user = self._create_dummy_target()
|
||||
addon = addon_factory(users=[user])
|
||||
cinder_user = self.cinder_class(user)
|
||||
cinder_user = self.CinderClass(user)
|
||||
message = '@bad person!'
|
||||
encoded_message = cinder_user.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(user=user, message=message)
|
||||
|
@ -1764,7 +1910,7 @@ class TestCinderUser(BaseTestCinderCase, TestCase):
|
|||
user.update(picture_type='image/png')
|
||||
|
||||
message = '=bad person!'
|
||||
cinder_user = self.cinder_class(user)
|
||||
cinder_user = self.CinderClass(user)
|
||||
encoded_message = cinder_user.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(user=user, message=message)
|
||||
|
||||
|
@ -1834,7 +1980,7 @@ class TestCinderUser(BaseTestCinderCase, TestCase):
|
|||
user = self._create_dummy_target()
|
||||
for _ in range(0, 6):
|
||||
user.addons.add(addon_factory())
|
||||
cinder_user = self.cinder_class(user)
|
||||
cinder_user = self.CinderClass(user)
|
||||
message = 'report for lots of relationships'
|
||||
abuse_report = AbuseReport.objects.create(user=user, message=message)
|
||||
data = cinder_user.build_report_payload(
|
||||
|
@ -1920,7 +2066,7 @@ class TestCinderUser(BaseTestCinderCase, TestCase):
|
|||
user = self._create_dummy_target()
|
||||
for _ in range(0, 6):
|
||||
user.addons.add(addon_factory())
|
||||
cinder_user = self.cinder_class(user)
|
||||
cinder_user = self.CinderClass(user)
|
||||
|
||||
responses.add(
|
||||
responses.POST,
|
||||
|
@ -2040,7 +2186,7 @@ class TestCinderUser(BaseTestCinderCase, TestCase):
|
|||
user = self._create_dummy_target()
|
||||
for _ in range(0, 6):
|
||||
user.addons.add(addon_factory())
|
||||
cinder_user = self.cinder_class(user)
|
||||
cinder_user = self.CinderClass(user)
|
||||
|
||||
responses.add(
|
||||
responses.POST,
|
||||
|
@ -2053,7 +2199,7 @@ class TestCinderUser(BaseTestCinderCase, TestCase):
|
|||
|
||||
|
||||
class TestCinderRating(BaseTestCinderCase, TestCase):
|
||||
cinder_class = CinderRating
|
||||
CinderClass = CinderRating
|
||||
expected_queries_for_report = 1 # For the author
|
||||
expected_queue_suffix = 'ratings'
|
||||
|
||||
|
@ -2068,7 +2214,7 @@ class TestCinderRating(BaseTestCinderCase, TestCase):
|
|||
|
||||
def test_build_report_payload(self):
|
||||
rating = self._create_dummy_target()
|
||||
cinder_rating = self.cinder_class(rating)
|
||||
cinder_rating = self.CinderClass(rating)
|
||||
message = '-bad rating!'
|
||||
encoded_message = cinder_rating.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(rating=rating, message=message)
|
||||
|
@ -2134,7 +2280,7 @@ class TestCinderRating(BaseTestCinderCase, TestCase):
|
|||
def test_build_report_payload_with_author_and_reporter_being_the_same(self):
|
||||
rating = self._create_dummy_target()
|
||||
user = rating.user
|
||||
cinder_rating = self.cinder_class(rating)
|
||||
cinder_rating = self.CinderClass(rating)
|
||||
message = '@my own words!'
|
||||
encoded_message = cinder_rating.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(rating=rating, message=message)
|
||||
|
@ -2213,7 +2359,7 @@ class TestCinderRating(BaseTestCinderCase, TestCase):
|
|||
rating = Rating.objects.create(
|
||||
addon=self.addon, user=addon_author, reply_to=original_rating
|
||||
)
|
||||
cinder_rating = self.cinder_class(rating)
|
||||
cinder_rating = self.CinderClass(rating)
|
||||
message = '-bad reply!'
|
||||
encoded_message = cinder_rating.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(rating=rating, message=message)
|
||||
|
@ -2294,7 +2440,7 @@ class TestCinderRating(BaseTestCinderCase, TestCase):
|
|||
|
||||
|
||||
class TestCinderCollection(BaseTestCinderCase, TestCase):
|
||||
cinder_class = CinderCollection
|
||||
CinderClass = CinderCollection
|
||||
expected_queries_for_report = 1 # For the author
|
||||
expected_queue_suffix = 'collections'
|
||||
|
||||
|
@ -2318,7 +2464,7 @@ class TestCinderCollection(BaseTestCinderCase, TestCase):
|
|||
|
||||
def test_build_report_payload(self):
|
||||
collection = self._create_dummy_target()
|
||||
cinder_collection = self.cinder_class(collection)
|
||||
cinder_collection = self.CinderClass(collection)
|
||||
message = '@bad collection!'
|
||||
encoded_message = cinder_collection.get_str(message)
|
||||
abuse_report = AbuseReport.objects.create(
|
||||
|
@ -2388,7 +2534,7 @@ class TestCinderCollection(BaseTestCinderCase, TestCase):
|
|||
|
||||
def test_build_report_payload_with_author_and_reporter_being_the_same(self):
|
||||
collection = self._create_dummy_target()
|
||||
cinder_collection = self.cinder_class(collection)
|
||||
cinder_collection = self.CinderClass(collection)
|
||||
user = collection.author
|
||||
message = '=Collect me!'
|
||||
encoded_message = cinder_collection.get_str(message)
|
||||
|
@ -2466,14 +2612,14 @@ class TestCinderCollection(BaseTestCinderCase, TestCase):
|
|||
|
||||
|
||||
class TestCinderReport(TestCase):
|
||||
cinder_class = CinderReport
|
||||
CinderClass = CinderReport
|
||||
|
||||
def test_reason_in_attributes(self):
|
||||
abuse_report = AbuseReport.objects.create(
|
||||
guid=addon_factory().guid,
|
||||
reason=AbuseReport.REASONS.POLICY_VIOLATION,
|
||||
)
|
||||
assert self.cinder_class(abuse_report).get_attributes() == {
|
||||
assert self.CinderClass(abuse_report).get_attributes() == {
|
||||
'id': str(abuse_report.pk),
|
||||
'created': str(abuse_report.created),
|
||||
'locale': None,
|
||||
|
@ -2488,7 +2634,7 @@ class TestCinderReport(TestCase):
|
|||
abuse_report = AbuseReport.objects.create(
|
||||
guid=addon_factory().guid, application_locale='en_US'
|
||||
)
|
||||
assert self.cinder_class(abuse_report).get_attributes() == {
|
||||
assert self.CinderClass(abuse_report).get_attributes() == {
|
||||
'id': str(abuse_report.pk),
|
||||
'created': str(abuse_report.created),
|
||||
'locale': 'en_US',
|
||||
|
@ -2506,7 +2652,7 @@ class TestCinderReport(TestCase):
|
|||
illegal_category=ILLEGAL_CATEGORIES.ANIMAL_WELFARE,
|
||||
illegal_subcategory=ILLEGAL_SUBCATEGORIES.OTHER,
|
||||
)
|
||||
assert self.cinder_class(abuse_report).get_attributes() == {
|
||||
assert self.CinderClass(abuse_report).get_attributes() == {
|
||||
'id': str(abuse_report.pk),
|
||||
'created': str(abuse_report.created),
|
||||
'locale': None,
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
|
||||
from olympia.amo.tests import addon_factory
|
||||
from olympia.constants.abuse import DECISION_ACTIONS
|
||||
|
||||
from ..models import AbuseReport, CinderDecision, CinderJob
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_backfill_cinder_escalations():
|
||||
addon = addon_factory()
|
||||
job_with_reports = CinderJob.objects.create(
|
||||
job_id='1',
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon
|
||||
),
|
||||
)
|
||||
abuse = AbuseReport.objects.create(guid=addon.guid, cinder_job=job_with_reports)
|
||||
appeal_job = CinderJob.objects.create(
|
||||
job_id='2',
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon
|
||||
),
|
||||
)
|
||||
appealled_decision = CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=addon, appeal_job=appeal_job
|
||||
)
|
||||
|
||||
# And some jobs/decisions that should be skipped:
|
||||
# decision that wasn't an escalation (or isn't any longer)
|
||||
CinderJob.objects.create(
|
||||
job_id='3',
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon
|
||||
),
|
||||
)
|
||||
# decision without an associated cinder job (shouldn't occur, but its handled)
|
||||
CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon
|
||||
)
|
||||
# decision that already has a forwarded job created, so we don't need to backfill
|
||||
CinderJob.objects.create(
|
||||
job_id='4',
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon
|
||||
),
|
||||
forwarded_to_job=CinderJob.objects.create(job_id='5'),
|
||||
)
|
||||
assert CinderJob.objects.count() == 5
|
||||
assert CinderDecision.objects.count() == 6
|
||||
responses.add(
|
||||
responses.POST,
|
||||
f'{settings.CINDER_SERVER_URL}create_report',
|
||||
json={'job_id': '6'},
|
||||
status=201,
|
||||
)
|
||||
responses.add(
|
||||
responses.POST,
|
||||
f'{settings.CINDER_SERVER_URL}create_report',
|
||||
json={'job_id': '7'},
|
||||
status=201,
|
||||
)
|
||||
|
||||
call_command('backfill_cinder_escalations')
|
||||
assert CinderJob.objects.count() == 7
|
||||
assert CinderDecision.objects.count() == 6
|
||||
|
||||
new_job_with_reports = job_with_reports.reload().forwarded_to_job
|
||||
assert new_job_with_reports
|
||||
assert new_job_with_reports.resolvable_in_reviewer_tools is True
|
||||
assert abuse.reload().cinder_job == new_job_with_reports
|
||||
new_appeal_job = appeal_job.reload().forwarded_to_job
|
||||
assert new_appeal_job
|
||||
assert new_appeal_job.resolvable_in_reviewer_tools is True
|
||||
assert appealled_decision.reload().appeal_job == new_appeal_job
|
|
@ -591,15 +591,8 @@ class TestCinderJobManager(TestCase):
|
|||
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=addon
|
||||
),
|
||||
)
|
||||
escalated_job = CinderJob.objects.create(
|
||||
job_id='3',
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon
|
||||
),
|
||||
)
|
||||
qs = CinderJob.objects.unresolved()
|
||||
assert len(qs) == 2
|
||||
assert list(qs) == [job, escalated_job]
|
||||
assert list(qs) == [job]
|
||||
|
||||
def test_reviewer_handled(self):
|
||||
not_policy_report = AbuseReport.objects.create(
|
||||
|
@ -639,13 +632,8 @@ class TestCinderJobManager(TestCase):
|
|||
qs = CinderJob.objects.resolvable_in_reviewer_tools()
|
||||
assert list(qs) == [job, appeal_job]
|
||||
|
||||
not_policy_report.cinder_job.update(
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
|
||||
addon=not_policy_report.target,
|
||||
),
|
||||
resolvable_in_reviewer_tools=True,
|
||||
)
|
||||
not_policy_report.cinder_job.update(resolvable_in_reviewer_tools=True)
|
||||
CinderJob.objects.create(forwarded_to_job=not_policy_report.cinder_job)
|
||||
qs = CinderJob.objects.resolvable_in_reviewer_tools()
|
||||
assert list(qs) == [not_policy_report.cinder_job, job, appeal_job]
|
||||
|
||||
|
@ -696,7 +684,7 @@ class TestCinderJob(TestCase):
|
|||
assert isinstance(helper, CinderAddon)
|
||||
assert not isinstance(helper, CinderAddonHandledByReviewers)
|
||||
assert helper.addon == addon
|
||||
assert helper.version is None
|
||||
assert helper.version_string is None
|
||||
|
||||
helper = CinderJob.get_entity_helper(
|
||||
addon,
|
||||
|
@ -707,14 +695,14 @@ class TestCinderJob(TestCase):
|
|||
assert isinstance(helper, CinderAddon)
|
||||
assert not isinstance(helper, CinderAddonHandledByReviewers)
|
||||
assert helper.addon == addon
|
||||
assert helper.version == addon.current_version
|
||||
assert helper.version_string == addon.current_version
|
||||
|
||||
helper = CinderJob.get_entity_helper(addon, resolved_in_reviewer_tools=True)
|
||||
# if now reason is in REVIEWER_HANDLED it will be reported differently
|
||||
assert isinstance(helper, CinderAddon)
|
||||
assert isinstance(helper, CinderAddonHandledByReviewers)
|
||||
assert helper.addon == addon
|
||||
assert helper.version is None
|
||||
assert helper.version_string is None
|
||||
|
||||
helper = CinderJob.get_entity_helper(
|
||||
addon,
|
||||
|
@ -725,7 +713,7 @@ class TestCinderJob(TestCase):
|
|||
assert isinstance(helper, CinderAddon)
|
||||
assert isinstance(helper, CinderAddonHandledByReviewers)
|
||||
assert helper.addon == addon
|
||||
assert helper.version == addon.current_version
|
||||
assert helper.version_string == addon.current_version.version
|
||||
|
||||
helper = CinderJob.get_entity_helper(user, resolved_in_reviewer_tools=False)
|
||||
assert isinstance(helper, CinderUser)
|
||||
|
@ -901,6 +889,89 @@ class TestCinderJob(TestCase):
|
|||
assert cinder_job.target_addon == abuse_report.target
|
||||
assert cinder_job.resolvable_in_reviewer_tools
|
||||
|
||||
def test_handle_job_recreated(self):
|
||||
addon = addon_factory()
|
||||
decision = CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon, notes='blah'
|
||||
)
|
||||
job = CinderJob.objects.create(
|
||||
job_id='1234', target_addon=addon, decision=decision
|
||||
)
|
||||
report = AbuseReport.objects.create(guid=addon.guid, cinder_job=job)
|
||||
assert not job.resolvable_in_reviewer_tools
|
||||
|
||||
job.handle_job_recreated(new_job_id='5678')
|
||||
|
||||
job.reload()
|
||||
new_job = job.forwarded_to_job
|
||||
assert new_job.job_id == '5678'
|
||||
assert list(new_job.forwarded_from_jobs.all()) == [job]
|
||||
assert new_job.resolvable_in_reviewer_tools
|
||||
assert new_job.target_addon == addon
|
||||
assert report.reload().cinder_job == new_job
|
||||
|
||||
def test_handle_job_recreated_existing_job(self):
|
||||
addon = addon_factory()
|
||||
exisiting_escalation_job = CinderJob.objects.create(
|
||||
job_id='5678', target_addon=addon
|
||||
)
|
||||
other_forwarded_job = CinderJob.objects.create(
|
||||
job_id='9999', target_addon=addon, forwarded_to_job=exisiting_escalation_job
|
||||
)
|
||||
|
||||
decision = CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon, notes='blah'
|
||||
)
|
||||
old_job = CinderJob.objects.create(
|
||||
job_id='1234', target_addon=addon, decision=decision
|
||||
)
|
||||
report = AbuseReport.objects.create(guid=addon.guid, cinder_job=old_job)
|
||||
|
||||
old_job.handle_job_recreated(new_job_id='5678')
|
||||
|
||||
old_job.reload()
|
||||
exisiting_escalation_job.reload()
|
||||
assert old_job.forwarded_to_job == exisiting_escalation_job
|
||||
assert list(exisiting_escalation_job.forwarded_from_jobs.all()) == [
|
||||
other_forwarded_job,
|
||||
old_job,
|
||||
]
|
||||
assert report.reload().cinder_job == exisiting_escalation_job
|
||||
|
||||
def test_handle_job_recreated_appeal(self):
|
||||
addon = addon_factory()
|
||||
decision = CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon, notes='blah'
|
||||
)
|
||||
appeal_job = CinderJob.objects.create(
|
||||
job_id='1234', target_addon=addon, decision=decision
|
||||
)
|
||||
original_job = CinderJob.objects.create(
|
||||
job_id='0000',
|
||||
target_addon=addon,
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||
addon=addon,
|
||||
notes='its okay',
|
||||
appeal_job=appeal_job,
|
||||
),
|
||||
)
|
||||
report = AbuseReport.objects.create(
|
||||
guid=addon.guid, cinder_job=original_job, appellant_job=appeal_job
|
||||
)
|
||||
assert not appeal_job.resolvable_in_reviewer_tools
|
||||
|
||||
appeal_job.handle_job_recreated(new_job_id='5678')
|
||||
|
||||
appeal_job.reload()
|
||||
new_job = appeal_job.forwarded_to_job
|
||||
assert new_job.job_id == '5678'
|
||||
assert list(new_job.forwarded_from_jobs.all()) == [appeal_job]
|
||||
assert new_job.resolvable_in_reviewer_tools
|
||||
assert new_job.target_addon == addon
|
||||
assert original_job.decision.reload().appeal_job == new_job
|
||||
assert report.reload().appellant_job == new_job
|
||||
|
||||
def test_process_decision(self):
|
||||
cinder_job = CinderJob.objects.create(job_id='1234')
|
||||
target = user_factory()
|
||||
|
@ -965,12 +1036,19 @@ class TestCinderJob(TestCase):
|
|||
assert notify_mock.call_count == 1
|
||||
assert list(cinder_job.decision.policies.all()) == [policy]
|
||||
|
||||
def test_process_decision_escalate_addon(self):
|
||||
def test_process_decision_escalate_addon_action(self):
|
||||
addon = addon_factory()
|
||||
cinder_job = CinderJob.objects.create(job_id='1234', target_addon=addon)
|
||||
AbuseReport.objects.create(guid=addon.guid, cinder_job=cinder_job)
|
||||
report = AbuseReport.objects.create(guid=addon.guid, cinder_job=cinder_job)
|
||||
assert not cinder_job.resolvable_in_reviewer_tools
|
||||
new_date = datetime(2024, 1, 1)
|
||||
responses.add(
|
||||
responses.POST,
|
||||
f'{settings.CINDER_SERVER_URL}create_report',
|
||||
json={'job_id': '5678'},
|
||||
status=201,
|
||||
)
|
||||
|
||||
cinder_job.process_decision(
|
||||
decision_cinder_id='12345',
|
||||
decision_date=new_date,
|
||||
|
@ -978,13 +1056,18 @@ class TestCinderJob(TestCase):
|
|||
decision_notes='blah',
|
||||
policy_ids=[],
|
||||
)
|
||||
assert cinder_job.decision.cinder_id == '12345'
|
||||
assert cinder_job.decision.date == new_date
|
||||
cinder_job.reload()
|
||||
assert cinder_job.decision
|
||||
assert cinder_job.decision.action == DECISION_ACTIONS.AMO_ESCALATE_ADDON
|
||||
assert cinder_job.decision.notes == 'blah'
|
||||
assert cinder_job.decision.addon == addon
|
||||
assert cinder_job.resolvable_in_reviewer_tools
|
||||
assert cinder_job.target_addon == addon
|
||||
|
||||
new_job = cinder_job.forwarded_to_job
|
||||
assert new_job
|
||||
assert new_job.job_id == '5678'
|
||||
assert list(new_job.forwarded_from_jobs.all()) == [cinder_job]
|
||||
assert new_job.resolvable_in_reviewer_tools
|
||||
assert new_job.target_addon == addon
|
||||
assert report.reload().cinder_job == new_job
|
||||
|
||||
def _test_resolve_job(self, activity_action, cinder_action, *, expect_target_email):
|
||||
addon_developer = user_factory()
|
||||
|
@ -1271,15 +1354,11 @@ class TestCinderJob(TestCase):
|
|||
).exists()
|
||||
assert NeedsHumanReview.objects.filter(is_active=True).count() == 2
|
||||
|
||||
def test_resolve_job_escalation(self):
|
||||
def test_resolve_job_forwarded(self):
|
||||
addon_developer = user_factory()
|
||||
addon = addon_factory(users=[addon_developer])
|
||||
cinder_job = CinderJob.objects.create(
|
||||
job_id='999',
|
||||
decision=CinderDecision.objects.create(
|
||||
addon=addon, action=DECISION_ACTIONS.AMO_ESCALATE_ADDON
|
||||
),
|
||||
)
|
||||
cinder_job = CinderJob.objects.create(job_id='999')
|
||||
CinderJob.objects.create(forwarded_to_job=cinder_job)
|
||||
NeedsHumanReview.objects.create(
|
||||
version=addon.current_version,
|
||||
reason=NeedsHumanReview.REASONS.CINDER_ESCALATION,
|
||||
|
@ -1301,7 +1380,7 @@ class TestCinderJob(TestCase):
|
|||
)
|
||||
responses.add(
|
||||
responses.POST,
|
||||
f'{settings.CINDER_SERVER_URL}create_decision',
|
||||
f'{settings.CINDER_SERVER_URL}jobs/{cinder_job.job_id}/decision',
|
||||
json={'uuid': uuid.uuid4().hex},
|
||||
status=201,
|
||||
)
|
||||
|
@ -1405,6 +1484,113 @@ class TestCinderJob(TestCase):
|
|||
assert not job.is_appeal
|
||||
assert appeal.is_appeal
|
||||
|
||||
def test_clear_needs_human_review_flags(self):
|
||||
def nhr_exists(reason):
|
||||
return NeedsHumanReview.objects.filter(
|
||||
reason=reason, is_active=True
|
||||
).exists()
|
||||
|
||||
addon = addon_factory()
|
||||
job = CinderJob.objects.create(
|
||||
job_id='1',
|
||||
target_addon=addon,
|
||||
resolvable_in_reviewer_tools=True,
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon
|
||||
),
|
||||
)
|
||||
NeedsHumanReview.objects.create(
|
||||
version=addon.current_version,
|
||||
reason=NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION,
|
||||
)
|
||||
NeedsHumanReview.objects.create(
|
||||
version=addon.current_version,
|
||||
reason=NeedsHumanReview.REASONS.CINDER_ESCALATION,
|
||||
)
|
||||
NeedsHumanReview.objects.create(
|
||||
version=addon.current_version,
|
||||
reason=NeedsHumanReview.REASONS.ADDON_REVIEW_APPEAL,
|
||||
)
|
||||
|
||||
# for a non-forwarded or appealed job, this should clear the abuse NHR only
|
||||
job.clear_needs_human_review_flags()
|
||||
assert not nhr_exists(NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION)
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.CINDER_ESCALATION)
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.ADDON_REVIEW_APPEAL)
|
||||
|
||||
NeedsHumanReview.objects.create(
|
||||
version=addon.current_version,
|
||||
reason=NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION,
|
||||
)
|
||||
# if the job is forwarded, we make sure that there are no other forwarded jobs
|
||||
CinderJob.objects.create(job_id='2', target_addon=addon, forwarded_to_job=job)
|
||||
other_forward = CinderJob.objects.create(
|
||||
job_id='3',
|
||||
target_addon=addon,
|
||||
resolvable_in_reviewer_tools=True,
|
||||
)
|
||||
CinderJob.objects.create(
|
||||
job_id='4', target_addon=addon, forwarded_to_job=other_forward
|
||||
)
|
||||
job.clear_needs_human_review_flags()
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION)
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.CINDER_ESCALATION)
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.ADDON_REVIEW_APPEAL)
|
||||
|
||||
# unless the other job is closed too
|
||||
other_forward.update(
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon
|
||||
)
|
||||
)
|
||||
job.clear_needs_human_review_flags()
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION)
|
||||
assert not nhr_exists(NeedsHumanReview.REASONS.CINDER_ESCALATION)
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.ADDON_REVIEW_APPEAL)
|
||||
|
||||
NeedsHumanReview.objects.create(
|
||||
version=addon.current_version,
|
||||
reason=NeedsHumanReview.REASONS.CINDER_ESCALATION,
|
||||
)
|
||||
# similarly if the job is an appeal we make sure that there are no other appeals
|
||||
CinderJob.objects.create(
|
||||
job_id='5',
|
||||
target_addon=addon,
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon, appeal_job=job
|
||||
),
|
||||
)
|
||||
job.forwarded_from_jobs.get().delete()
|
||||
other_appeal = CinderJob.objects.create(
|
||||
job_id='6',
|
||||
target_addon=addon,
|
||||
resolvable_in_reviewer_tools=True,
|
||||
)
|
||||
CinderJob.objects.create(
|
||||
job_id='7',
|
||||
target_addon=addon,
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||
addon=addon,
|
||||
appeal_job=other_appeal,
|
||||
),
|
||||
)
|
||||
job.clear_needs_human_review_flags()
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION)
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.CINDER_ESCALATION)
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.ADDON_REVIEW_APPEAL)
|
||||
|
||||
# unless the other job is closed too
|
||||
other_appeal.update(
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon
|
||||
)
|
||||
)
|
||||
job.clear_needs_human_review_flags()
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION)
|
||||
assert nhr_exists(NeedsHumanReview.REASONS.CINDER_ESCALATION)
|
||||
assert not nhr_exists(NeedsHumanReview.REASONS.ADDON_REVIEW_APPEAL)
|
||||
|
||||
|
||||
class TestCinderDecisionCanBeAppealed(TestCase):
|
||||
def setUp(self):
|
||||
|
@ -1625,13 +1811,9 @@ class TestCinderDecisionCanBeAppealed(TestCase):
|
|||
)
|
||||
self.decision.can_be_appealed(is_reporter=False)
|
||||
|
||||
def test_author_cant_appeal_approve_or_escalation_decision(self):
|
||||
for decision_action in (
|
||||
DECISION_ACTIONS.AMO_ESCALATE_ADDON,
|
||||
DECISION_ACTIONS.AMO_APPROVE,
|
||||
):
|
||||
self.decision.update(action=decision_action)
|
||||
assert not self.decision.can_be_appealed(is_reporter=False)
|
||||
def test_author_cant_appeal_approve_decision(self):
|
||||
self.decision.update(action=DECISION_ACTIONS.AMO_APPROVE)
|
||||
assert not self.decision.can_be_appealed(is_reporter=False)
|
||||
|
||||
def test_author_cant_appeal_disable_decision_already_appealed(self):
|
||||
self.decision.update(action=DECISION_ACTIONS.AMO_DISABLE_ADDON)
|
||||
|
|
|
@ -28,6 +28,7 @@ from olympia.zadmin.models import set_config
|
|||
from ..models import AbuseReport, CinderDecision, CinderJob, CinderPolicy
|
||||
from ..tasks import (
|
||||
appeal_to_cinder,
|
||||
handle_escalate_action,
|
||||
notify_addon_decision_to_cinder,
|
||||
report_to_cinder,
|
||||
resolve_job_in_cinder,
|
||||
|
@ -985,3 +986,30 @@ class TestSyncCinderPolicies(TestCase):
|
|||
sync_cinder_policies()
|
||||
assert CinderPolicy.objects.count() == 6
|
||||
assert CinderPolicy.objects.filter(text='ADDED').count() == 6
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_handle_escalate_action():
|
||||
addon = addon_factory()
|
||||
decision = CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon, notes='blah'
|
||||
)
|
||||
job = CinderJob.objects.create(job_id='1234', target_addon=addon, decision=decision)
|
||||
report = AbuseReport.objects.create(guid=addon.guid, cinder_job=job)
|
||||
assert not job.resolvable_in_reviewer_tools
|
||||
responses.add(
|
||||
responses.POST,
|
||||
f'{settings.CINDER_SERVER_URL}create_report',
|
||||
json={'job_id': '5678'},
|
||||
status=201,
|
||||
)
|
||||
|
||||
handle_escalate_action(job_pk=job.pk)
|
||||
|
||||
job.reload()
|
||||
new_job = job.forwarded_to_job
|
||||
assert new_job.job_id == '5678'
|
||||
assert list(new_job.forwarded_from_jobs.all()) == [job]
|
||||
assert new_job.resolvable_in_reviewer_tools
|
||||
assert new_job.target_addon == addon
|
||||
assert report.reload().cinder_job == new_job
|
||||
|
|
|
@ -7,17 +7,10 @@ from waffle.testutils import override_switch
|
|||
from olympia import amo
|
||||
from olympia.activity.models import ActivityLog, ActivityLogToken
|
||||
from olympia.addons.models import Addon
|
||||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
collection_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
from olympia.amo.tests import TestCase, addon_factory, collection_factory, user_factory
|
||||
from olympia.constants.abuse import DECISION_ACTIONS
|
||||
from olympia.core import set_user
|
||||
from olympia.ratings.models import Rating
|
||||
from olympia.reviewers.models import NeedsHumanReview
|
||||
|
||||
from ..models import AbuseReport, CinderDecision, CinderJob, CinderPolicy
|
||||
from ..utils import (
|
||||
|
@ -27,7 +20,6 @@ from ..utils import (
|
|||
CinderActionDeleteCollection,
|
||||
CinderActionDeleteRating,
|
||||
CinderActionDisableAddon,
|
||||
CinderActionEscalateAddon,
|
||||
CinderActionIgnore,
|
||||
CinderActionOverrideApprove,
|
||||
CinderActionRejectVersion,
|
||||
|
@ -427,7 +419,7 @@ class TestCinderActionUser(BaseTestCinderAction, TestCase):
|
|||
self._test_owner_affirmation_email(f'Mozilla Add-ons: {self.user.name}')
|
||||
|
||||
|
||||
@override_switch('dsa-cinder-escalations-review', active=True)
|
||||
@override_switch('dsa-cinder-forwarded-review', active=True)
|
||||
@override_switch('dsa-appeals-review', active=True)
|
||||
class TestCinderActionAddon(BaseTestCinderAction, TestCase):
|
||||
ActionClass = CinderActionDisableAddon
|
||||
|
@ -514,111 +506,6 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
|
|||
action.notify_owners()
|
||||
return f'Mozilla Add-ons: {self.addon.name}'
|
||||
|
||||
def test_escalate_addon(self):
|
||||
listed_version = self.addon.current_version
|
||||
listed_version.file.update(is_signed=True)
|
||||
unlisted_version = version_factory(
|
||||
addon=self.addon, channel=amo.CHANNEL_UNLISTED, file_kw={'is_signed': True}
|
||||
)
|
||||
ActivityLog.objects.all().delete()
|
||||
action = CinderActionEscalateAddon(self.decision)
|
||||
assert action.process_action() is None
|
||||
|
||||
assert self.addon.reload().status == amo.STATUS_APPROVED
|
||||
assert (
|
||||
listed_version.reload().needshumanreview_set.get().reason
|
||||
== NeedsHumanReview.REASONS.CINDER_ESCALATION
|
||||
)
|
||||
assert (
|
||||
unlisted_version.reload().needshumanreview_set.get().reason
|
||||
== NeedsHumanReview.REASONS.CINDER_ESCALATION
|
||||
)
|
||||
assert ActivityLog.objects.count() == 1
|
||||
activity = ActivityLog.objects.filter(
|
||||
action=amo.LOG.NEEDS_HUMAN_REVIEW_CINDER.id
|
||||
).get()
|
||||
assert activity.arguments == [listed_version, unlisted_version]
|
||||
assert activity.user == self.task_user
|
||||
|
||||
# but if we have a version specified, we flag that version
|
||||
NeedsHumanReview.objects.all().delete()
|
||||
other_version = version_factory(
|
||||
addon=self.addon, file_kw={'status': amo.STATUS_DISABLED, 'is_signed': True}
|
||||
)
|
||||
assert not other_version.due_date
|
||||
ActivityLog.objects.all().delete()
|
||||
self.cinder_job.abusereport_set.update(addon_version=other_version.version)
|
||||
assert action.process_action() is None
|
||||
assert not listed_version.reload().needshumanreview_set.exists()
|
||||
assert not unlisted_version.reload().needshumanreview_set.exists()
|
||||
other_version.reload()
|
||||
assert other_version.due_date
|
||||
assert (
|
||||
other_version.needshumanreview_set.get().reason
|
||||
== NeedsHumanReview.REASONS.CINDER_ESCALATION
|
||||
)
|
||||
assert ActivityLog.objects.count() == 1
|
||||
activity = ActivityLog.objects.get(action=amo.LOG.NEEDS_HUMAN_REVIEW_CINDER.id)
|
||||
assert activity.arguments == [other_version]
|
||||
assert activity.user == self.task_user
|
||||
self.cinder_job.notify_reporters(action)
|
||||
assert len(mail.outbox) == 0
|
||||
|
||||
def test_escalate_addon_no_versions_to_flag(self):
|
||||
listed_version = self.addon.current_version
|
||||
listed_version.file.update(is_signed=True)
|
||||
unlisted_version = version_factory(
|
||||
addon=self.addon, channel=amo.CHANNEL_UNLISTED, file_kw={'is_signed': True}
|
||||
)
|
||||
NeedsHumanReview.objects.create(
|
||||
reason=NeedsHumanReview.REASONS.CINDER_ESCALATION, version=listed_version
|
||||
)
|
||||
NeedsHumanReview.objects.create(
|
||||
reason=NeedsHumanReview.REASONS.CINDER_ESCALATION, version=unlisted_version
|
||||
)
|
||||
assert NeedsHumanReview.objects.count() == 2
|
||||
ActivityLog.objects.all().delete()
|
||||
|
||||
action = CinderActionEscalateAddon(self.decision)
|
||||
assert action.process_action() is None
|
||||
assert NeedsHumanReview.objects.count() == 2
|
||||
assert ActivityLog.objects.count() == 0
|
||||
assert len(mail.outbox) == 0
|
||||
|
||||
@override_switch('dsa-cinder-escalations-review', active=False)
|
||||
def test_escalate_addon_waffle_switch_off(self):
|
||||
# Escalation when the waffle switch is off is essentially a no-op on
|
||||
# AMO side.
|
||||
listed_version = self.addon.current_version
|
||||
listed_version.file.update(is_signed=True)
|
||||
unlisted_version = version_factory(
|
||||
addon=self.addon, channel=amo.CHANNEL_UNLISTED, file_kw={'is_signed': True}
|
||||
)
|
||||
ActivityLog.objects.all().delete()
|
||||
action = CinderActionEscalateAddon(self.decision)
|
||||
assert action.process_action() is None
|
||||
|
||||
assert self.addon.reload().status == amo.STATUS_APPROVED
|
||||
assert not listed_version.reload().needshumanreview_set.exists()
|
||||
assert not listed_version.due_date
|
||||
assert not unlisted_version.reload().needshumanreview_set.exists()
|
||||
assert not unlisted_version.due_date
|
||||
assert ActivityLog.objects.count() == 0
|
||||
|
||||
other_version = version_factory(
|
||||
addon=self.addon, file_kw={'status': amo.STATUS_DISABLED, 'is_signed': True}
|
||||
)
|
||||
assert not other_version.due_date
|
||||
ActivityLog.objects.all().delete()
|
||||
self.cinder_job.abusereport_set.update(addon_version=other_version.version)
|
||||
assert action.process_action() is None
|
||||
assert not listed_version.reload().needshumanreview_set.exists()
|
||||
assert not unlisted_version.reload().needshumanreview_set.exists()
|
||||
other_version.reload()
|
||||
assert not other_version.due_date
|
||||
assert not listed_version.reload().needshumanreview_set.exists()
|
||||
assert not unlisted_version.reload().needshumanreview_set.exists()
|
||||
|
||||
def test_target_appeal_decline(self):
|
||||
self.addon.update(status=amo.STATUS_DISABLED)
|
||||
ActivityLog.objects.all().delete()
|
||||
|
|
|
@ -11,7 +11,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
import waffle
|
||||
|
||||
import olympia
|
||||
from olympia import activity, amo
|
||||
from olympia import amo
|
||||
from olympia.activity import log_create
|
||||
from olympia.addons.models import Addon
|
||||
from olympia.amo.templatetags.jinja_helpers import absolutify
|
||||
|
@ -284,81 +284,9 @@ class CinderActionEscalateAddon(CinderAction):
|
|||
valid_targets = (Addon,)
|
||||
|
||||
def process_action(self):
|
||||
"""This will return always return a falsey value because we've not taken any
|
||||
action at this point, just flagging for human review."""
|
||||
return self.flag_for_human_review()
|
||||
from olympia.abuse.tasks import handle_escalate_action
|
||||
|
||||
def flag_for_human_review(self):
|
||||
from olympia.reviewers.models import NeedsHumanReview
|
||||
|
||||
if not waffle.switch_is_active('dsa-cinder-escalations-review'):
|
||||
log.info(
|
||||
'Not adding %s to review queue despite %s because waffle switch is off',
|
||||
self.target,
|
||||
'escalation',
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(self.target, Addon) and self.decision.is_third_party_initiated:
|
||||
reason = NeedsHumanReview.REASONS.CINDER_ESCALATION
|
||||
reported_versions = set(
|
||||
self.decision.cinder_job.abusereport_set.values_list(
|
||||
'addon_version', flat=True
|
||||
)
|
||||
)
|
||||
version_objs = (
|
||||
set(
|
||||
self.target.versions(manager='unfiltered_for_relations')
|
||||
.filter(version__in=reported_versions)
|
||||
.exclude(
|
||||
needshumanreview__reason=reason,
|
||||
needshumanreview__is_active=True,
|
||||
)
|
||||
.no_transforms()
|
||||
)
|
||||
if reported_versions
|
||||
else set()
|
||||
)
|
||||
|
||||
nhr_object = None
|
||||
# We need custom save() and post_save to be triggered, so we can't
|
||||
# optimize this via bulk_create().
|
||||
for version in version_objs:
|
||||
nhr_object = NeedsHumanReview(
|
||||
version=version, reason=reason, is_active=True
|
||||
)
|
||||
nhr_object.save(_no_automatic_activity_log=True)
|
||||
# If we have more versions specified than versions we flagged, flag latest
|
||||
# to be safe. (Either because there was an unknown version, or a None)
|
||||
if (
|
||||
len(version_objs) != len(reported_versions)
|
||||
or len(reported_versions) == 0
|
||||
):
|
||||
version_objs = version_objs.union(
|
||||
self.target.set_needs_human_review_on_latest_versions(
|
||||
reason=reason,
|
||||
ignore_reviewed=False,
|
||||
unique_reason=True,
|
||||
skip_activity_log=True,
|
||||
)
|
||||
)
|
||||
if version_objs:
|
||||
version_objs = sorted(version_objs, key=lambda v: v.id)
|
||||
# we just need this to exact to do get_reason_display
|
||||
nhr_object = nhr_object or NeedsHumanReview(
|
||||
version=version_objs[-1],
|
||||
reason=reason,
|
||||
is_active=True,
|
||||
)
|
||||
activity.log_create(
|
||||
amo.LOG.NEEDS_HUMAN_REVIEW_CINDER,
|
||||
*version_objs,
|
||||
details={'comments': nhr_object.get_reason_display()},
|
||||
)
|
||||
|
||||
def get_owners(self):
|
||||
# we don't send any emails for escalations
|
||||
return ()
|
||||
handle_escalate_action.delay(job_pk=self.decision.cinder_job.pk)
|
||||
|
||||
|
||||
class CinderActionDeleteCollection(CinderAction):
|
||||
|
|
|
@ -7,7 +7,8 @@ REPORTED_MEDIA_BACKUP_EXPIRATION_DAYS = 31 + APPEAL_EXPIRATION_DAYS
|
|||
DECISION_ACTIONS = APIChoicesWithDash(
|
||||
('AMO_BAN_USER', 1, 'User ban'),
|
||||
('AMO_DISABLE_ADDON', 2, 'Add-on disable'),
|
||||
('AMO_ESCALATE_ADDON', 3, 'Escalate add-on to reviewers'),
|
||||
# Used to indicate the job has been forwarded to AMO
|
||||
('AMO_ESCALATE_ADDON', 3, 'Forward add-on to reviewers'),
|
||||
# 4 is unused
|
||||
('AMO_DELETE_RATING', 5, 'Rating delete'),
|
||||
('AMO_DELETE_COLLECTION', 6, 'Collection delete'),
|
||||
|
@ -33,12 +34,7 @@ DECISION_ACTIONS.add_subset(
|
|||
),
|
||||
)
|
||||
DECISION_ACTIONS.add_subset(
|
||||
'APPEALABLE_BY_REPORTER',
|
||||
('AMO_APPROVE', 'AMO_APPROVE_VERSION'),
|
||||
)
|
||||
DECISION_ACTIONS.add_subset(
|
||||
'UNRESOLVED',
|
||||
('AMO_ESCALATE_ADDON',),
|
||||
'APPEALABLE_BY_REPORTER', ('AMO_APPROVE', 'AMO_APPROVE_VERSION')
|
||||
)
|
||||
DECISION_ACTIONS.add_subset(
|
||||
'REMOVING',
|
||||
|
@ -50,10 +46,7 @@ DECISION_ACTIONS.add_subset(
|
|||
'AMO_REJECT_VERSION_ADDON',
|
||||
),
|
||||
)
|
||||
DECISION_ACTIONS.add_subset(
|
||||
'APPROVING',
|
||||
('AMO_APPROVE', 'AMO_APPROVE_VERSION'),
|
||||
)
|
||||
DECISION_ACTIONS.add_subset('APPROVING', ('AMO_APPROVE', 'AMO_APPROVE_VERSION'))
|
||||
|
||||
# Illegal categories, only used when the reason is `illegal`. The constants
|
||||
# are derived from the "spec" but without the `STATEMENT_CATEGORY_` prefix.
|
||||
|
|
|
@ -17,6 +17,14 @@ def create_waffle_switch(name):
|
|||
return inner
|
||||
|
||||
|
||||
def rename_waffle_switch(old_name, new_name):
|
||||
def inner(apps, schema_editor):
|
||||
Switch = apps.get_model('waffle', 'Switch')
|
||||
Switch.objects.update_or_create(name=old_name, defaults={'name': new_name})
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
class DeleteWaffleSwitch(migrations.RunPython):
|
||||
def __init__(self, name, **kwargs):
|
||||
super().__init__(
|
||||
|
@ -39,3 +47,15 @@ class CreateWaffleSwitch(migrations.RunPython):
|
|||
|
||||
def describe(self):
|
||||
return 'Create Waffle Switch (Python operation)'
|
||||
|
||||
|
||||
class RenameWaffleSwitch(migrations.RunPython):
|
||||
def __init__(self, old_name, new_name, **kwargs):
|
||||
super().__init__(
|
||||
rename_waffle_switch(old_name, new_name),
|
||||
reverse_code=rename_waffle_switch(new_name, old_name),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def describe(self):
|
||||
return 'Rename Waffle Switch, safely (Python operation)'
|
||||
|
|
|
@ -7,7 +7,11 @@ from django.apps.registry import apps
|
|||
import pytest
|
||||
from waffle.models import Switch
|
||||
|
||||
from olympia.core.db.migrations import CreateWaffleSwitch, DeleteWaffleSwitch
|
||||
from olympia.core.db.migrations import (
|
||||
CreateWaffleSwitch,
|
||||
DeleteWaffleSwitch,
|
||||
RenameWaffleSwitch,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
@ -78,3 +82,41 @@ def test_create_waffle_switch_reverse():
|
|||
'fake_app', schema_editor, from_state, to_state
|
||||
)
|
||||
assert not Switch.objects.filter(name='foo').exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rename_waffle_switch_forward():
|
||||
schema_editor = mock.Mock(connection=mock.Mock(alias='default'))
|
||||
from_state = mock.Mock(apps=apps)
|
||||
to_state = mock.Mock()
|
||||
RenameWaffleSwitch('foo', 'baa').database_forwards(
|
||||
'fake_app', schema_editor, from_state, to_state
|
||||
)
|
||||
assert not Switch.objects.filter(name='foo').exists()
|
||||
assert Switch.objects.filter(name='baa').exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rename_waffle_switch_forward_already_exists():
|
||||
Switch.objects.create(name='foo')
|
||||
schema_editor = mock.Mock(connection=mock.Mock(alias='default'))
|
||||
from_state = mock.Mock(apps=apps)
|
||||
to_state = mock.Mock()
|
||||
RenameWaffleSwitch('foo', 'baa').database_forwards(
|
||||
'fake_app', schema_editor, from_state, to_state
|
||||
)
|
||||
assert not Switch.objects.filter(name='foo').exists()
|
||||
assert Switch.objects.filter(name='baa').exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rename_waffle_switch_reverse():
|
||||
Switch.objects.create(name='baa')
|
||||
schema_editor = mock.Mock(connection=mock.Mock(alias='default'))
|
||||
from_state = mock.Mock(apps=apps)
|
||||
to_state = mock.Mock()
|
||||
RenameWaffleSwitch('foo', 'baa').database_backwards(
|
||||
'fake_app', schema_editor, from_state, to_state
|
||||
)
|
||||
assert Switch.objects.filter(name='foo').exists()
|
||||
assert not Switch.objects.filter(name='baa').exists()
|
||||
|
|
|
@ -848,6 +848,7 @@ CELERY_TASK_ROUTES = {
|
|||
# Misc AMO tasks.
|
||||
'olympia.blocklist.tasks.monitor_remote_settings': {'queue': 'amo'},
|
||||
'olympia.abuse.tasks.appeal_to_cinder': {'queue': 'amo'},
|
||||
'olympia.abuse.tasks.handle_escalate_action': {'queue': 'amo'},
|
||||
'olympia.abuse.tasks.report_to_cinder': {'queue': 'amo'},
|
||||
'olympia.abuse.tasks.notify_addon_decision_to_cinder': {'queue': 'amo'},
|
||||
'olympia.abuse.tasks.resolve_job_in_cinder': {'queue': 'amo'},
|
||||
|
|
|
@ -18,7 +18,6 @@ from olympia import amo, ratings
|
|||
from olympia.abuse.models import CinderJob, CinderPolicy
|
||||
from olympia.access import acl
|
||||
from olympia.amo.forms import AMOModelForm
|
||||
from olympia.constants.abuse import DECISION_ACTIONS
|
||||
from olympia.constants.reviewers import REVIEWER_DELAYED_REJECTION_PERIOD_DAYS_DEFAULT
|
||||
from olympia.ratings.models import Rating
|
||||
from olympia.ratings.permissions import user_can_delete_rating
|
||||
|
@ -229,8 +228,8 @@ class CinderJobsWidget(forms.CheckboxSelectMultiple):
|
|||
# label_from_instance() on WidgetRenderedModelMultipleChoiceField returns the
|
||||
# full object, not a label, this is what makes this work.
|
||||
obj = label
|
||||
is_escalation = (
|
||||
obj.decision and obj.decision.action == DECISION_ACTIONS.AMO_ESCALATE_ADDON
|
||||
forwarded_notes = obj.forwarded_from_jobs.all().values_list(
|
||||
'decision__notes', flat=True
|
||||
)
|
||||
is_appeal = obj.is_appeal
|
||||
reports = obj.all_abuse_reports
|
||||
|
@ -244,14 +243,18 @@ class CinderJobsWidget(forms.CheckboxSelectMultiple):
|
|||
)
|
||||
for report in reports
|
||||
)
|
||||
escalation = ((f'Reasoning: {obj.decision.notes}',),) if is_escalation else ()
|
||||
forwarded = (
|
||||
((f'Reasoning: {"; ".join(notes for notes in forwarded_notes)}',),)
|
||||
if forwarded_notes
|
||||
else ()
|
||||
)
|
||||
appeals = (
|
||||
(appeal_text_obj.text, appeal_text_obj.reporter_report is not None)
|
||||
for appealed_decision in obj.appealed_decisions.all()
|
||||
for appeal_text_obj in appealed_decision.appeals.all()
|
||||
)
|
||||
subtexts_gen = [
|
||||
*escalation,
|
||||
*forwarded,
|
||||
*(
|
||||
(f'{"Reporter" if is_reporter else "Developer"} Appeal: {text}',)
|
||||
for text, is_reporter in appeals
|
||||
|
@ -263,7 +266,7 @@ class CinderJobsWidget(forms.CheckboxSelectMultiple):
|
|||
'{}{}{}<details><summary>Show detail on {} reports</summary>'
|
||||
'<span>{}</span><ul>{}</ul></details>',
|
||||
'[Appeal] ' if is_appeal else '',
|
||||
'[Escalation] ' if is_escalation else '',
|
||||
'[Forwarded] ' if forwarded else '',
|
||||
format_html_join(', ', '"{}"', reasons_set),
|
||||
len(reports),
|
||||
format_html_join('', '{}<br/>', subtexts_gen),
|
||||
|
|
|
@ -1034,20 +1034,24 @@ class TestReviewForm(TestCase):
|
|||
reporter_report=appealed_abuse_report,
|
||||
)
|
||||
|
||||
cinder_job_escalated = CinderJob.objects.create(
|
||||
job_id='escalated',
|
||||
cinder_job_forwarded = CinderJob.objects.create(
|
||||
job_id='forwarded',
|
||||
resolvable_in_reviewer_tools=True,
|
||||
target_addon=self.addon,
|
||||
)
|
||||
CinderJob.objects.create(
|
||||
job_id='forwarded_from',
|
||||
forwarded_to_job=cinder_job_forwarded,
|
||||
decision=CinderDecision.objects.create(
|
||||
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
|
||||
notes='Why o why',
|
||||
addon=self.addon,
|
||||
),
|
||||
resolvable_in_reviewer_tools=True,
|
||||
target_addon=self.addon,
|
||||
)
|
||||
AbuseReport.objects.create(
|
||||
**{**abuse_kw, 'location': AbuseReport.LOCATION.AMO},
|
||||
message='ddd',
|
||||
cinder_job=cinder_job_escalated,
|
||||
cinder_job=cinder_job_forwarded,
|
||||
addon_version='<script>alert()</script>',
|
||||
)
|
||||
|
||||
|
@ -1078,7 +1082,7 @@ class TestReviewForm(TestCase):
|
|||
qs_list = list(choices.queryset)
|
||||
assert qs_list == [
|
||||
# Only unresolved, reviewer handled, jobs are shown
|
||||
cinder_job_escalated,
|
||||
cinder_job_forwarded,
|
||||
cinder_job_appeal,
|
||||
cinder_job_2_reports,
|
||||
]
|
||||
|
@ -1087,7 +1091,7 @@ class TestReviewForm(TestCase):
|
|||
doc = pq(content)
|
||||
label_0 = doc('label[for="id_cinder_jobs_to_resolve_0"]')
|
||||
assert label_0.text() == (
|
||||
'[Escalation] "DSA: It violates Mozilla\'s Add-on Policies"\n'
|
||||
'[Forwarded] "DSA: It violates Mozilla\'s Add-on Policies"\n'
|
||||
'Show detail on 1 reports\n'
|
||||
'Reasoning: Why o why\n\n'
|
||||
'v[<script>alert()</script>]: ddd'
|
||||
|
|
Загрузка…
Ссылка в новой задаче