@@ -1302,3 +1302,103 @@ def test_api2_source_conversation_truncated(
13021302 )
13031303 assert res2 .status_code == 200
13041304 assert res2 .json ["events" ][event .id ][0 ] == 208
1305+
1306+
1307+ def test_api2_source_conversation_seen (
1308+ journalist_app ,
1309+ journalist_api_token ,
1310+ test_files ,
1311+ ):
1312+ """
1313+ Test processing of the "source_conversation_seen" event.
1314+ Items with interaction_count <= upper_bound must be marked as seen.
1315+ Items with interaction_count > upper_bound must remain unseen.
1316+ """
1317+ with journalist_app .test_client () as app :
1318+ source = test_files ["source" ]
1319+
1320+ assert len (test_files ["submissions" ]) >= 1
1321+ assert len (test_files ["replies" ]) >= 1
1322+
1323+ # Fetch index to get current versions
1324+ index = app .get (
1325+ url_for ("api2.index" ),
1326+ headers = get_api_headers (journalist_api_token ),
1327+ )
1328+ assert index .status_code == 200
1329+
1330+ # Build a map of item_uuid -> interaction_count
1331+ item_uuids = [item .uuid for item in (test_files ["submissions" ] + test_files ["replies" ])]
1332+
1333+ batch_resp = app .post (
1334+ url_for ("api2.data" ),
1335+ json = {"items" : item_uuids },
1336+ headers = get_api_headers (journalist_api_token ),
1337+ )
1338+ assert batch_resp .status_code == 200
1339+ data = batch_resp .json
1340+
1341+ initial_counts = {
1342+ item_uuid : item ["interaction_count" ] for item_uuid , item in data ["items" ].items ()
1343+ }
1344+
1345+ # Choose a bound that marks some but not all items as seen
1346+ sorted_counts = sorted (initial_counts .values ())
1347+ upper_bound = sorted_counts [len (sorted_counts ) // 2 ]
1348+
1349+ source_version = index .json ["sources" ][source .uuid ]
1350+
1351+ event = Event (
1352+ id = "888001" ,
1353+ target = SourceTarget (source_uuid = source .uuid , version = source_version ),
1354+ type = EventType .SOURCE_CONVERSATION_SEEN ,
1355+ data = {"upper_bound" : upper_bound },
1356+ )
1357+
1358+ response = app .post (
1359+ url_for ("api2.data" ),
1360+ json = {"events" : [asdict (event )]},
1361+ headers = get_api_headers (journalist_api_token ),
1362+ )
1363+ assert response .status_code == 200
1364+ assert response .json ["events" ][event .id ] == [200 , None ]
1365+
1366+ # Items within bound must be marked seen; items outside must not be
1367+ for item_uuid , count in initial_counts .items ():
1368+ submission = Submission .query .filter (Submission .uuid == item_uuid ).one_or_none ()
1369+ reply = Reply .query .filter (Reply .uuid == item_uuid ).one_or_none ()
1370+
1371+ if count <= upper_bound :
1372+ assert item_uuid in response .json ["items" ]
1373+ if submission is not None :
1374+ assert submission .seen is True
1375+ else :
1376+ assert len (reply .seen_replies ) > 0
1377+ elif submission is not None :
1378+ assert submission .seen is False
1379+ else :
1380+ assert len (reply .seen_replies ) == 0
1381+
1382+ # Resubmission must yield "Already Reported" (208)
1383+ res2 = app .post (
1384+ url_for ("api2.data" ),
1385+ json = {"events" : [asdict (event )]},
1386+ headers = get_api_headers (journalist_api_token ),
1387+ )
1388+ assert res2 .status_code == 200
1389+ assert res2 .json ["events" ][event .id ][0 ] == 208
1390+
1391+ # Non-existent source must yield Gone (410)
1392+ gone_event = Event (
1393+ id = "888002" ,
1394+ target = SourceTarget (source_uuid = str (uuid4 ()), version = source_version ),
1395+ type = EventType .SOURCE_CONVERSATION_SEEN ,
1396+ data = {"upper_bound" : upper_bound },
1397+ )
1398+ res3 = app .post (
1399+ url_for ("api2.data" ),
1400+ json = {"events" : [asdict (gone_event )]},
1401+ headers = get_api_headers (journalist_api_token ),
1402+ )
1403+ assert res3 .status_code == 200
1404+ assert res3 .json ["events" ][gone_event .id ][0 ] == 410
0 commit comments