Skip to content

Commit e74ebb2

Browse files
authored
Merge pull request #7705 from freedomofpress/sdc-2769-208-on-failure
fix(`mark_progress`): clear progress for events in error states
2 parents 97917e0 + e7b30a6 commit e74ebb2

File tree

2 files changed

+95
-5
lines changed

2 files changed

+95
-5
lines changed

securedrop/journalist_app/api2/events.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,21 @@ def has_progress(self, event: Event) -> EventStatusCode:
8686
def mark_progress(
8787
self, event: Event, status: EventStatusCode = EventStatusCode.Processing
8888
) -> None:
89-
self._redis.set(
90-
self.idempotence_key(event),
91-
status,
92-
ex=IDEMPOTENCE_PERIOD,
93-
)
89+
"""
90+
If `status` is a non-error code, mark it as the progress of `event`, to
91+
be returned later as "Already Reported".
92+
93+
If `status` is an error code, clear it, since `event` MAY be resubmitted
94+
later.
95+
"""
96+
if status >= EventStatusCode.BadRequest:
97+
self._redis.delete(self.idempotence_key(event))
98+
else:
99+
self._redis.set(
100+
self.idempotence_key(event),
101+
status,
102+
ex=IDEMPOTENCE_PERIOD,
103+
)
94104

95105
@staticmethod
96106
def handle_item_deleted(event: Event) -> EventResult:

securedrop/tests/test_journalist_api2.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,3 +869,83 @@ def test_api2_event_ordering(journalist_app, journalist_api_token, test_files):
869869

870870
# Event "2" (sent first, processed second) finds it missing.
871871
assert resp.json["events"]["3419026047977394171"][0] == 410
872+
873+
874+
def test_api2_source_conversation_deleted_resubmission(
875+
journalist_app,
876+
journalist_api_token,
877+
test_files,
878+
):
879+
"""
880+
A rejected event (409) MUST be handled (200) if corrected and resubmitted.
881+
An accepted (i.e., corrected) event MUST be acknowledged (208) if
882+
resubmitted.
883+
"""
884+
with journalist_app.test_client() as app:
885+
source = test_files["source"]
886+
source_uuid = source.uuid
887+
888+
# 1. Submit with the wrong version --> Conflict (409).
889+
event = Event(
890+
id="600100",
891+
target=SourceTarget(source_uuid=source_uuid, version="wrong-version"),
892+
type=EventType.SOURCE_CONVERSATION_DELETED,
893+
)
894+
res1 = app.post(
895+
url_for("api2.data"),
896+
json={"events": [asdict(event)]},
897+
headers=get_api_headers(journalist_api_token),
898+
)
899+
assert res1.status_code == 200
900+
assert res1.json["events"][event.id][0] == 409
901+
902+
# Confirm that nothing has been deleted.
903+
for submission in test_files["submissions"]:
904+
assert (
905+
Submission.query.filter(Submission.uuid == submission.uuid).one_or_none()
906+
is not None
907+
)
908+
for reply in test_files["replies"]:
909+
assert Reply.query.filter(Reply.uuid == reply.uuid).one_or_none() is not None
910+
911+
expected_item_uuids = {item.uuid for item in test_files["submissions"]}
912+
expected_item_uuids.update({item.uuid for item in test_files["replies"]})
913+
914+
# 2. Resubmit the same event with the correct version --> OK (200).
915+
index = app.get(
916+
url_for("api2.index"),
917+
headers=get_api_headers(journalist_api_token),
918+
)
919+
assert index.status_code == 200
920+
correct_version = index.json["sources"][source_uuid]
921+
922+
event.target = SourceTarget(source_uuid=source_uuid, version=correct_version)
923+
res2 = app.post(
924+
url_for("api2.data"),
925+
json={"events": [asdict(event)]},
926+
headers=get_api_headers(journalist_api_token),
927+
)
928+
assert res2.status_code == 200
929+
assert res2.json["events"][event.id] == [200, None]
930+
931+
# Confirm that items are returned as deleted.
932+
assert res2.json["sources"][source_uuid] is not None
933+
for item_uuid in expected_item_uuids:
934+
assert item_uuid in res2.json["items"]
935+
assert res2.json["items"][item_uuid] is None
936+
937+
# Confirm that items have actually been deleted.
938+
for submission in test_files["submissions"]:
939+
assert Submission.query.filter(Submission.uuid == submission.uuid).one_or_none() is None
940+
for reply in test_files["replies"]:
941+
assert Reply.query.filter(Reply.uuid == reply.uuid).one_or_none() is None
942+
assert Source.query.filter(Source.uuid == source_uuid).one_or_none() is not None
943+
944+
# 3. Resubmit the same event again --> Already Reported (208).
945+
res3 = app.post(
946+
url_for("api2.data"),
947+
json={"events": [asdict(event)]},
948+
headers=get_api_headers(journalist_api_token),
949+
)
950+
assert res3.status_code == 200
951+
assert res3.json["events"][event.id][0] == 208

0 commit comments

Comments
 (0)