Repository: freedomofpress/securedrop

Issue:
journalist API: incorrect `is_starred` reported when starring and unstarring a source
## Description

Bug found by @kushaldas over in #3619:

> In my test, I removed a start from a source, I got back this reply: {'message': 'Star removed'}. After this when I am trying to get all the sources or that particular source again, I can still see 'is_starred': True for that source.

## Steps to Reproduce

Note: you'll need to put in the UUID of a source in your dev env in place of the one shown here

1. Get a source via the API: GET `127.0.0.1:8081/api/v1/sources/917c3b91-29f9-46a0-8e3b-0ba6af5fc9f5`
2. Star the source: POST `127.0.0.1:8081/api/v1/sources/917c3b91-29f9-46a0-8e3b-0ba6af5fc9f5/add_star`
3. Unstar the source: DELETE `127.0.0.1:8081/api/v1/sources/917c3b91-29f9-46a0-8e3b-0ba6af5fc9f5/remove_star`
4. Get the source once more: GET `127.0.0.1:8081/api/v1/sources/917c3b91-29f9-46a0-8e3b-0ba6af5fc9f5`

## Expected Behavior

`is_starred: False`

## Actual Behavior

`is_starred: True`

## Comments

simple fix, needs at minimum an additional assert in tests to cover this case


Discussion:

Resolving patch:
diff --git a/securedrop/models.py b/securedrop/models.py
--- a/securedrop/models.py
+++ b/securedrop/models.py
@@ -123,12 +123,17 @@ def to_json(self):
         else:
             last_updated = datetime.datetime.utcnow().isoformat() + 'Z'
 
+        if self.star and self.star.starred:
+            starred = True
+        else:
+            starred = False
+
         json_source = {
             'uuid': self.uuid,
             'url': url_for('api.single_source', source_uuid=self.uuid),
             'journalist_designation': self.journalist_designation,
             'is_flagged': self.flagged,
-            'is_starred': True if self.star else False,
+            'is_starred': starred,
             'last_updated': last_updated,
             'interaction_count': self.interaction_count,
             'key': {


Test patch:
diff --git a/securedrop/tests/test_journalist_api.py b/securedrop/tests/test_journalist_api.py
--- a/securedrop/tests/test_journalist_api.py
+++ b/securedrop/tests/test_journalist_api.py
@@ -306,6 +306,12 @@ def test_authorized_user_can_star_a_source(journalist_app, test_source,
         assert SourceStar.query.filter(
             SourceStar.source_id == source_id).one().starred
 
+        # API should also report is_starred is true
+        response = app.get(url_for('api.single_source', source_uuid=uuid),
+                           headers=get_api_headers(journalist_api_token))
+        json_response = json.loads(response.data)
+        assert json_response['is_starred'] is True
+
 
 def test_authorized_user_can_unstar_a_source(journalist_app, test_source,
                                              journalist_api_token):
@@ -324,6 +330,12 @@ def test_authorized_user_can_unstar_a_source(journalist_app, test_source,
         assert SourceStar.query.filter(
             SourceStar.source_id == source_id).one().starred is False
 
+        # API should also report is_starred is false
+        response = app.get(url_for('api.single_source', source_uuid=uuid),
+                           headers=get_api_headers(journalist_api_token))
+        json_response = json.loads(response.data)
+        assert json_response['is_starred'] is False
+
 
 def test_disallowed_methods_produces_405(journalist_app, test_source,
                                          journalist_api_token):