Repository: getsentry/sentry

Issue:
Test `_archived_row` in sentry/replays/post_process.py to make sure that all requested fields are returned, no more KeyError
We saw this "KeyError" in the backend recently: https://sentry.sentry.io/issues/4326971348/?alert_rule_id=14460873&alert_type=issue&project=1&referrer=slack

> KeyError /api/0/organizations/{organization_slug}/replays/ 'count_dead_clicks'

The problem is that we are asking for more fields to be returned in the list query, but when a deleted row exists we call `_archived_row()` and return that object instead. That object doesn't really care about what fields the api client was asking for, therefore we got a key error.
We did a quick fix to update the server so it matches what we're asking for: https://github.com/getsentry/sentry/pull/53225/files

Updating `_archived_row` like this is hard to maintain. 
There already exists a `VALID_FIELD_SET` inside ReplayValidator.py. We could write a test to guarantee that `_archived_row` knows about all fields, if a new field is added _archived_row will be updated to match.

Alternatively, we could change the implementation of _archived_row so that it returns an object which implements the `__getitem__()` method, so it returns None for any field requested. Then we don't need to update `_archived_row` each time a new field is added to VALID_FIELD_SET
https://stackoverflow.com/questions/43627405/understanding-getitem-method-in-python


Discussion:

Resolving patch:
diff --git a/src/sentry/replays/post_process.py b/src/sentry/replays/post_process.py
--- a/src/sentry/replays/post_process.py
+++ b/src/sentry/replays/post_process.py
@@ -4,6 +4,8 @@
 from itertools import zip_longest
 from typing import Any, Generator, Iterable, Iterator, MutableMapping
 
+from sentry.replays.validators import VALID_FIELD_SET
+
 
 def process_raw_response(response: list[dict[str, Any]], fields: list[str]) -> list[dict[str, Any]]:
     """Process the response further into the expected output."""
@@ -116,7 +118,7 @@ def dict_unique_list(items: Iterable[tuple[str, str]]) -> dict[str, list[str]]:
 
 
 def _archived_row(replay_id: str, project_id: int) -> dict[str, Any]:
-    return {
+    archived_replay_response = {
         "id": _strip_dashes(replay_id),
         "project_id": str(project_id),
         "trace_ids": [],
@@ -137,7 +139,18 @@ def _archived_row(replay_id: str, project_id: int) -> dict[str, Any]:
         "finished_at": None,
         "started_at": None,
         "is_archived": True,
+        "count_segments": None,
+        "count_urls": None,
+        "dist": None,
+        "platform": None,
+        "releases": None,
+        "clicks": None,
     }
+    for field in VALID_FIELD_SET:
+        if field not in archived_replay_response:
+            archived_replay_response[field] = None
+
+    return archived_replay_response
 
 
 CLICK_FIELD_MAP = {


Test patch:
diff --git a/tests/sentry/replays/test_organization_replay_index.py b/tests/sentry/replays/test_organization_replay_index.py
--- a/tests/sentry/replays/test_organization_replay_index.py
+++ b/tests/sentry/replays/test_organization_replay_index.py
@@ -912,6 +912,12 @@ def test_archived_records_are_null_fields(self):
                     "finished_at": None,
                     "duration": None,
                     "is_archived": True,
+                    "releases": None,
+                    "platform": None,
+                    "dist": None,
+                    "count_segments": None,
+                    "count_urls": None,
+                    "clicks": None,
                 }
             ]