@@ -1602,53 +1602,45 @@ async def test_get_friends_balance_summary_success(expense_service):
16021602 },
16031603 ]
16041604
1605- # Mocking settlement aggregations for each friend in each group
1605+ # Mocking the OPTIMIZED settlement aggregation
1606+ # The new optimized version makes ONE aggregation call that returns all friends' balances
16061607 # Friend 1:
1607- # Group Alpha: Main owes Friend1 50 (net -50 for Main)
1608- # Group Beta: Friend1 owes Main 30 (net +30 for Main)
1609- # Total for Friend1: Main is owed 50, owes 30. Net: Main is owed 20 by Friend1.
1608+ # Group Alpha: Main owes Friend1 50 (balance: -50 for Main)
1609+ # Group Beta: Friend1 owes Main 30 (balance: +30 for Main)
1610+ # Total for Friend1: -50 + 30 = -20 ( Main owes Friend1 20)
16101611 # Friend 2:
1611- # Group Beta: Main owes Friend2 70 (net -70 for Main)
1612- # Total for Friend2: Main owes 70 to Friend2.
1612+ # Group Beta: Main owes Friend2 70 (balance: -70 for Main)
1613+ # Total for Friend2: -70 ( Main owes Friend2 70)
16131614
1614- # This is the side_effect for the .aggregate() call. It must be a sync function
1615- # that returns a cursor mock (AsyncMock).
16161615 def sync_mock_settlements_aggregate_cursor_factory (pipeline , * args , ** kwargs ):
1617- match_clause = pipeline [0 ]["$match" ]
1618- group_id_pipeline = match_clause ["groupId" ]
1619- or_conditions = match_clause ["$or" ]
1620-
1621- # Determine which friend is being processed based on payer/payee in OR condition
1622- # This is a simplification; real queries are more complex
1623- pipeline_friend_id = None
1624- for cond in or_conditions :
1625- if cond ["payerId" ] == user_id_str and cond ["payeeId" ] != user_id_str :
1626- pipeline_friend_id = cond ["payeeId" ]
1627- break
1628- elif cond ["payeeId" ] == user_id_str and cond ["payerId" ] != user_id_str :
1629- pipeline_friend_id = cond ["payerId" ]
1630- break
1631-
1616+ # The optimized version returns aggregated results for all friends in one go
16321617 mock_agg_cursor = AsyncMock ()
1633- if group_id_pipeline == group1_id and pipeline_friend_id == friend1_id_str :
1634- # Main owes Friend1 50 in Group Alpha
1635- mock_agg_cursor .to_list .return_value = [
1636- {"_id" : None , "userOwes" : 50.0 , "friendOwes" : 0.0 }
1637- ]
1638- elif group_id_pipeline == group2_id and pipeline_friend_id == friend1_id_str :
1639- # Friend1 owes Main 30 in Group Beta
1640- mock_agg_cursor .to_list .return_value = [
1641- {"_id" : None , "userOwes" : 0.0 , "friendOwes" : 30.0 }
1642- ]
1643- elif group_id_pipeline == group2_id and pipeline_friend_id == friend2_id_str :
1644- # Main owes Friend2 70 in Group Beta
1645- mock_agg_cursor .to_list .return_value = [
1646- {"_id" : None , "userOwes" : 70.0 , "friendOwes" : 0.0 }
1647- ]
1648- else :
1649- mock_agg_cursor .to_list .return_value = [
1650- {"_id" : None , "userOwes" : 0.0 , "friendOwes" : 0.0 }
1651- ] # Default empty
1618+ mock_agg_cursor .to_list .return_value = [
1619+ {
1620+ "_id" : friend1_id_str , # Friend 1
1621+ "totalBalance" : - 20.0 , # Main owes Friend1 20 (net: -50 from G1, +30 from G2)
1622+ "groups" : [
1623+ {
1624+ "groupId" : group1_id ,
1625+ "balance" : - 50.0 ,
1626+ }, # Main owes 50 in Group Alpha
1627+ {
1628+ "groupId" : group2_id ,
1629+ "balance" : 30.0 ,
1630+ }, # Friend1 owes 30 in Group Beta
1631+ ],
1632+ },
1633+ {
1634+ "_id" : friend2_id_str , # Friend 2
1635+ "totalBalance" : - 70.0 , # Main owes Friend2 70
1636+ "groups" : [
1637+ {
1638+ "groupId" : group2_id ,
1639+ "balance" : - 70.0 ,
1640+ }, # Main owes 70 in Group Beta
1641+ ],
1642+ },
1643+ ]
16521644 return mock_agg_cursor
16531645
16541646 with patch ("app.expenses.service.mongodb" ) as mock_mongodb :
@@ -1678,7 +1670,7 @@ def mock_user_find_cursor_side_effect(query, *args, **kwargs):
16781670
16791671 mock_db .users .find = MagicMock (side_effect = mock_user_find_cursor_side_effect )
16801672
1681- # Mock settlement aggregation logic
1673+ # Mock the optimized settlement aggregation logic
16821674 # .aggregate() is sync, returns an async cursor.
16831675 mock_db .settlements .aggregate = MagicMock (
16841676 side_effect = sync_mock_settlements_aggregate_cursor_factory
@@ -1739,9 +1731,9 @@ def mock_user_find_cursor_side_effect(query, *args, **kwargs):
17391731
17401732 # Verify mocks
17411733 mock_db .groups .find .assert_called_once_with ({"members.userId" : user_id_str })
1742- # settlements.aggregate is called for each friend in each group they share with user_id_str
1743- # Friend1 is in 2 groups with user_id_str, Friend2 is in 1 group with user_id_str. Total 3 calls.
1744- assert mock_db .settlements .aggregate .call_count == 3
1734+ # OPTIMIZED: settlements.aggregate is called ONCE (not per friend/ group)
1735+ # The optimized version uses a single aggregation pipeline to get all friends' balances
1736+ assert mock_db .settlements .aggregate .call_count == 1
17451737
17461738
17471739@pytest .mark .asyncio
0 commit comments