Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/olympia/abuse/cinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,10 @@ class WorkflowEventSendMixin:

workflow_name = None # Needs to be defined by subclasses

@property
def workflow_pretty_name(self):
return self.workflow_name

def change_to_v2_syntax(self, subgraph):
"""api/v2 syntax is a bit different, we need to change the format of the
subgraph a bit before sending it."""
Expand Down Expand Up @@ -697,6 +701,7 @@ def send_event(self):
class CinderAddonContentReview(WorkflowEventSendMixin, CinderAddon):
queue_suffix = 'listing-content'
workflow_name = 'amo_addon.contentreview'
workflow_pretty_name = 'Amo Addon Contentreview'

def appeal(self, *, decision_cinder_id, **kwargs):
# We don't flag for NHR for content review follow-ups
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.2.12 on 2026-04-02 16:04

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('abuse', '0063_create_content_review_in_cinder_switch'),
]

operations = [
migrations.AddField(
model_name='cinderjob',
name='content_review',
field=models.BooleanField(default=False, null=True),
),
migrations.AlterField(
model_name='contentdecision',
name='action',
field=models.PositiveSmallIntegerField(choices=[(1, 'User ban'), (2, 'Add-on disable'), (3, '(Obsolete) 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 moderated (no action)'), (13, 'Forward add-on to legal'), (14, 'Pending rejection date changed'), (15, 'No action - internal requeue'), (16, 'Add-on disable and block'), (17, 'Add-on listing content rejection')]),
),
]
8 changes: 7 additions & 1 deletion src/olympia/abuse/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class CinderJob(ModelBase):
to=Addon, blank=True, null=True, on_delete=models.deletion.SET_NULL
)
resolvable_in_reviewer_tools = models.BooleanField(default=None, null=True)
content_review = models.BooleanField(default=False, null=True)

objects = CinderJobManager()

Expand Down Expand Up @@ -1280,7 +1281,11 @@ def appeal(self, *, abuse_report, appeal_text, user, is_reporter):
)
if not self.can_be_appealed(is_reporter=is_reporter, abuse_report=abuse_report):
raise CantBeAppealed
is_content_review = self.action == DECISION_ACTIONS.AMO_REJECT_LISTING_CONTENT
is_content_review = (
self.cinder_job.content_review
if self.cinder_job
else self.action == DECISION_ACTIONS.AMO_REJECT_LISTING_CONTENT
)

entity_helper = CinderJob.get_entity_helper(
self.target,
Expand All @@ -1299,6 +1304,7 @@ def appeal(self, *, abuse_report, appeal_text, user, is_reporter):
defaults={
'target_addon': self.addon,
'resolvable_in_reviewer_tools': resolvable_in_reviewer_tools,
'content_review': is_content_review,
},
)
self.update(appeal_job=appeal_job)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"event": "job.actioned",
"payload": {
"action": "created",
"action_made_by": {
"workflow": {
"event_id": "019d4912-4851-7242-bd0b-f16c3ab57da5",
"name": "Amo Addon Contentreview"
}
},
"job": {
"created_at": "2026-04-01T12:43:50.782776Z",
"entity": {
"attributes": {
"average_daily_users": 0,
"created": "2026-04-01T12:19:58.646580",
"description": "en-US",
"guid": "{95a42dcb-a337-46a8-8292-d3bc8a48b647}",
"homepage": "https://addons-server.readthedocs.io/en/latest/topics/api/addons.html#create",
"id": "10000",
"last_updated": "2026-04-01T12:19:58.646580",
"name": "A string to be detected recommended",
"privacy_policy": "",
"promoted": "",
"release_notes": "EN-US Version notes added in API at addon creation time",
"requires_payment": false,
"slug": "a-string-to-be-detected-rec4",
"summary": "change summary from API from API333",
"support_email": "rusiczki.ioana@test.com",
"version": "3.0"
},
"entity_schema": "amo_addon"
},
"id": "0aa2e6b6-9899-4bd0-9f59-94e8762861ba",
"job_category": "standard",
"num_reports": 0,
"priority": 0,
"queue": {
"is_multi_review": false,
"slug": "amo-dev-listing-content"
},
"status": "open"
},
"notes": "",
"source": "workflow",
"timestamp": "2026-04-01T12:43:50.791551+00:00"
}
}
40 changes: 40 additions & 0 deletions src/olympia/abuse/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2658,6 +2658,46 @@ def test_appeal_as_target_from_resolved_in_amo(self):
addon = Addon.unfiltered.get()
assert addon in Addon.unfiltered.get_queryset_for_pending_queues()

def test_appeal_as_target_on_content_review_job(self):
addon = addon_factory(status=amo.STATUS_REJECTED)
cinder_job = CinderJob.objects.create(
target_addon=addon,
decision=ContentDecision.objects.create(
cinder_id='4815162342-lost',
action_date=self.days_ago(179),
action=DECISION_ACTIONS.AMO_REJECT_LISTING_CONTENT,
addon=addon,
),
content_review=True,
)
appeal_response = responses.add(
responses.POST,
f'{settings.CINDER_SERVER_URL}v1/appeal',
json={'external_id': '2432615184-tsol'},
status=201,
)

cinder_job.decision.appeal(
abuse_report=None,
appeal_text='appeal text',
user=user_factory(),
is_reporter=False,
)

cinder_job.reload()
assert cinder_job.decision.appeal_job.job_id == '2432615184-tsol'
assert cinder_job.decision.appeal_job.target_addon == addon
assert cinder_job.decision.appeal_job.content_review is True

assert appeal_response.call_count == 1
request = responses.calls[0].request
request_body = json.loads(request.body)
assert request_body['reasoning'] == 'appeal text'
assert request_body['decision_to_appeal_id'] == str(
cinder_job.decision.cinder_id
)
assert request_body['queue_slug'] == 'amo-env-listing-content'

def test_appeal_as_target_improperly_configured(self):
addon = addon_factory()
abuse_report = AbuseReport.objects.create(
Expand Down
67 changes: 58 additions & 9 deletions src/olympia/abuse/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1600,7 +1600,7 @@ def test_unknown_cinder_job_and_entity_prod(self):
with override_settings(CINDER_UNIQUE_IDS=True):
self._test_unkownn_cinder_job_and_entity(400)

def test_unkownn_cinder_job_but_known_entity(self):
def test_unknown_cinder_job_but_known_entity(self):
assert not CinderJob.objects.exists()
data = self.get_data()
addon = addon_factory(
Expand Down Expand Up @@ -1796,7 +1796,7 @@ def test_process_queue_move_called(self):
assert response.status_code == 201
assert response.data == {'amo': {'received': True, 'handled': True}}

def _test_process_queue_move_no_cinder_report(self, status_code):
def _test_process_queue_move_no_cinder_job(self, status_code):
req = self.get_request(
data=self.get_data('job_actioned_move_to_dev_infringement.json')
)
Expand All @@ -1812,20 +1812,69 @@ def _test_process_queue_move_no_cinder_report(self, status_code):
}
}

def test_process_queue_move_no_cinder_report_nonprod(self):
def test_process_queue_move_no_cinder_job_nonprod(self):
with override_settings(CINDER_UNIQUE_IDS=False):
self._test_process_queue_move_no_cinder_report(200)
self._test_process_queue_move_no_cinder_job(200)

def test_process_queue_move_no_cinder_report_prod(self):
def test_process_queue_move_no_cinder_job_prod(self):
with override_settings(CINDER_UNIQUE_IDS=True):
self._test_process_queue_move_no_cinder_report(400)
self._test_process_queue_move_no_cinder_job(400)

def test_process_queue_move_invalid_action(self):
def test_create_content_review_cinder_job_from_workflow(self):
data = self.get_data('job_actioned_workflow_created_job.json')
addon = addon_factory(
id=10000,
created=datetime.fromisoformat(
data['payload']['job']['entity']['attributes']['created']
),
)
req = self.get_request(data=data)
assert not CinderJob.objects.exists()

response = cinder_webhook(req)
assert (job := CinderJob.objects.get())
assert job.job_id == '0aa2e6b6-9899-4bd0-9f59-94e8762861ba'
assert job.target_addon == addon
assert job.content_review is True
assert response.status_code == 201
assert response.data == {'amo': {'received': True, 'handled': True}}

def test_proccess_webhook_payload_job_actioned_wrong_workflow(self):
data = self.get_data('job_actioned_workflow_created_job.json')
data['payload']['action_made_by']['workflow']['name'] = 'someother workflow'
response = cinder_webhook(self.get_request(data=data))
assert response.status_code == 200
assert response.data == {
'amo': {
'received': True,
'handled': False,
'not_handled_reason': (
'Unsupported workflow (someother workflow) for job.actioned:created'
),
}
}

def test_process_webhook_payload_job_actioned_wrong_source(self):
data = self.get_data('job_actioned_workflow_created_job.json')
data['payload']['source'] = 'not-workflow'
response = cinder_webhook(self.get_request(data=data))
assert response.status_code == 200
assert response.data == {
'amo': {
'received': True,
'handled': False,
'not_handled_reason': (
'Unsupported source (not-workflow) for job.actioned:created'
),
}
}

def test_process_webhook_payload_job_actioned_invalid_action(self):
data = self.get_data('job_actioned_move_to_dev_infringement.json')

data['payload']['action'] = 'something_else'
response = cinder_webhook(self.get_request(data=data))
assert response.status_code == 400
assert response.status_code == 200
assert response.data == {
'amo': {
'received': True,
Expand All @@ -1836,7 +1885,7 @@ def test_process_queue_move_invalid_action(self):
}
}

def test_process_queue_move_not_addon(self):
def test_process_webhook_payload_job_actioned_not_addon(self):
data = self.get_data('job_actioned_move_to_dev_infringement.json')

data['payload']['job']['entity']['entity_schema'] = 'amo_user'
Expand Down
Loading