Skip to content

Commit 2b7fef9

Browse files
Merge pull request #7685 from freedomofpress/api2-no-pending
Don't include pending and deleted sources in APIv2
2 parents cf288a6 + 2ea5c5d commit 2b7fef9

File tree

2 files changed

+35
-6
lines changed

2 files changed

+35
-6
lines changed

securedrop/models.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,20 @@ def eager_query(model: str) -> Union[EagerQuery, Query]:
4545
Falls back to a plain ``Query`` if no options are registered. A caller that
4646
requires eager loading can annotate the return value with ``EagerQuery`` to
4747
enforce it during type-checking.
48+
49+
This is intended for user-facing data access, e.g. we filter out pending sources.
4850
"""
4951
cls = globals()[model]
5052
try:
51-
return cls.query.options(*cls.query_options())
53+
query = cls.query.options(*cls.query_options())
5254
except AttributeError:
53-
return cls.query
55+
query = cls.query
56+
57+
# Filter out pending and deleted sources
58+
if model == "Source":
59+
query = query.filter_by(pending=False, deleted_at=None)
60+
61+
return query
5462

5563

5664
def get_one_or_else(

securedrop/tests/test_journalist_api2.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22
from contextlib import contextmanager
33
from copy import deepcopy
44
from dataclasses import asdict
5+
from datetime import UTC, datetime
56

67
import pytest
78
from flask import url_for
89
from flask_sqlalchemy import get_debug_queries
910
from journalist_app import api2
1011
from journalist_app.api2 import json_version
1112
from journalist_app.api2.types import Event, EventType, ItemTarget, SourceTarget
12-
from models import Reply, Submission
13+
from models import Reply, Submission, db
1314
from sqlalchemy.orm.exc import MultipleResultsFound
1415
from tests.utils import ascii_armor, decrypt_as_journalist
1516
from tests.utils.api_helper import get_api_headers
17+
from tests.utils.db_helper import init_source, submit
1618

1719

1820
def filtered_queries():
@@ -73,10 +75,26 @@ def test_auth_required(journalist_app, endpoint, kwargs):
7375
assert response.status_code == 403
7476

7577

76-
def test_index(journalist_app, test_files, journalist_api_token):
78+
def test_index(journalist_app, test_files, journalist_api_token, app_storage):
7779
"""
78-
Verify GET /index response and HTTP 304 behavior
80+
Verify GET /index response and HTTP 304 behavior.
7981
"""
82+
# Create a pending source and a deleted source to verify they're excluded
83+
with journalist_app.app_context():
84+
# Create a pending source (no submissions)
85+
pending_source, _ = init_source(app_storage)
86+
pending_uuid = pending_source.uuid
87+
assert pending_source.pending is True
88+
89+
# Create source that is queued for deletion but not yet deleted
90+
deleted_source, _ = init_source(app_storage)
91+
submit(app_storage, deleted_source, 1)
92+
deleted_uuid = deleted_source.uuid
93+
assert deleted_source.pending is False
94+
# Mark it as deleted
95+
deleted_source.deleted_at = datetime.now(UTC)
96+
db.session.commit()
97+
8098
with journalist_app.test_client() as app:
8199
uuid = test_files["source"].uuid
82100
with assert_query_count(2):
@@ -85,11 +103,14 @@ def test_index(journalist_app, test_files, journalist_api_token):
85103
headers=get_api_headers(journalist_api_token),
86104
)
87105

88-
# Verify the source is in the response
106+
# Verify the active source is in the response
89107
assert response.status_code == 200
90108
assert uuid in response.json["sources"]
91109
# test_files generates 2 submissions and 1 reply, so 3 items total
92110
assert len(response.json["items"]) == 3
111+
# Verify pending and deleted sources are NOT in the response
112+
assert pending_uuid not in response.json["sources"]
113+
assert deleted_uuid not in response.json["sources"]
93114

94115
with assert_query_count(2):
95116
response2 = app.get(

0 commit comments

Comments
 (0)