Skip to content

Commit 7c416ae

Browse files
refactor: extract lock spy fixture and parametrize FOR UPDATE tests
1 parent 2715a32 commit 7c416ae

1 file changed

Lines changed: 55 additions & 187 deletions

File tree

‎tests/unittests/sessions/test_session_service.py‎

Lines changed: 55 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,202 +1155,70 @@ async def test_prepare_tables_idempotent_after_creation():
11551155
await service.close()
11561156

11571157

1158-
@pytest.mark.asyncio
1159-
async def test_append_event_skips_for_update_when_no_app_or_user_delta():
1160-
"""FOR UPDATE locks on app_states/user_states should be skipped when the
1161-
event carries no delta for those scopes."""
1162-
service = DatabaseSessionService('sqlite+aiosqlite:///:memory:')
1163-
try:
1164-
session = await service.create_session(
1165-
app_name='my_app', user_id='user', session_id='s1'
1166-
)
1167-
1168-
# Patch _supports_row_level_locking to simulate PostgreSQL behavior
1169-
with mock.patch.object(
1170-
service, '_supports_row_level_locking', return_value=True
1171-
):
1172-
original_fn = database_session_service._select_required_state
1173-
calls = []
1174-
1175-
async def spy(**kwargs):
1176-
calls.append(kwargs.get('use_row_level_locking'))
1177-
return await original_fn(**kwargs)
1178-
1179-
with mock.patch.object(
1180-
database_session_service,
1181-
'_select_required_state',
1182-
side_effect=spy,
1183-
):
1184-
# Event with only session-scoped state (no app: or user: prefix)
1185-
event_no_scoped_delta = Event(
1186-
invocation_id='inv1',
1187-
author='user',
1188-
actions=EventActions(state_delta={'session_key': 'v1'}),
1189-
)
1190-
await service.append_event(session, event_no_scoped_delta)
1191-
1192-
# Two calls: one for app_states, one for user_states
1193-
assert len(calls) == 2
1194-
# Both should be False since there's no app: or user: delta
1195-
assert calls[0] is False
1196-
assert calls[1] is False
1197-
finally:
1198-
await service.close()
1199-
1200-
1201-
@pytest.mark.asyncio
1202-
async def test_append_event_skips_for_update_when_no_state_delta():
1203-
"""FOR UPDATE locks should be skipped when event has no state_delta at all."""
1204-
service = DatabaseSessionService('sqlite+aiosqlite:///:memory:')
1205-
try:
1206-
session = await service.create_session(
1207-
app_name='my_app', user_id='user', session_id='s1'
1208-
)
1209-
1210-
with mock.patch.object(
1211-
service, '_supports_row_level_locking', return_value=True
1212-
):
1213-
original_fn = database_session_service._select_required_state
1214-
calls = []
1215-
1216-
async def spy(**kwargs):
1217-
calls.append(kwargs.get('use_row_level_locking'))
1218-
return await original_fn(**kwargs)
1219-
1220-
with mock.patch.object(
1221-
database_session_service,
1222-
'_select_required_state',
1223-
side_effect=spy,
1224-
):
1225-
event_no_delta = Event(
1226-
invocation_id='inv2',
1227-
author='user',
1228-
)
1229-
await service.append_event(session, event_no_delta)
1230-
1231-
assert len(calls) == 2
1232-
assert calls[0] is False
1233-
assert calls[1] is False
1234-
finally:
1235-
await service.close()
1236-
1237-
1238-
@pytest.mark.asyncio
1239-
async def test_append_event_acquires_for_update_when_app_delta_present():
1240-
"""FOR UPDATE lock on app_states should be acquired when event has app:
1241-
prefixed state delta."""
1158+
@pytest.fixture
1159+
async def lock_spy_harness():
1160+
"""Sets up a DatabaseSessionService with a spy on _select_required_state."""
12421161
service = DatabaseSessionService('sqlite+aiosqlite:///:memory:')
12431162
try:
12441163
session = await service.create_session(
12451164
app_name='my_app', user_id='user', session_id='s1'
12461165
)
12471166

1248-
with mock.patch.object(
1249-
service, '_supports_row_level_locking', return_value=True
1167+
calls = []
1168+
original_fn = database_session_service._select_required_state
1169+
1170+
async def spy(**kwargs):
1171+
calls.append(kwargs.get('use_row_level_locking'))
1172+
return await original_fn(**kwargs)
1173+
1174+
with (
1175+
mock.patch.object(
1176+
service, '_supports_row_level_locking', return_value=True
1177+
),
1178+
mock.patch.object(
1179+
database_session_service,
1180+
'_select_required_state',
1181+
side_effect=spy,
1182+
),
12501183
):
1251-
original_fn = database_session_service._select_required_state
1252-
calls = []
1253-
1254-
async def spy(**kwargs):
1255-
calls.append(kwargs.get('use_row_level_locking'))
1256-
return await original_fn(**kwargs)
1257-
1258-
with mock.patch.object(
1259-
database_session_service,
1260-
'_select_required_state',
1261-
side_effect=spy,
1262-
):
1263-
event_app_delta = Event(
1264-
invocation_id='inv3',
1265-
author='user',
1266-
actions=EventActions(state_delta={'app:key1': 'v1'}),
1267-
)
1268-
await service.append_event(session, event_app_delta)
1269-
1270-
assert len(calls) == 2
1271-
# app_states lock: True (has app: delta)
1272-
assert calls[0] is True
1273-
# user_states lock: False (no user: delta)
1274-
assert calls[1] is False
1184+
yield service, session, calls
12751185
finally:
12761186
await service.close()
12771187

12781188

12791189
@pytest.mark.asyncio
1280-
async def test_append_event_acquires_for_update_when_user_delta_present():
1281-
"""FOR UPDATE lock on user_states should be acquired when event has user:
1282-
prefixed state delta."""
1283-
service = DatabaseSessionService('sqlite+aiosqlite:///:memory:')
1284-
try:
1285-
session = await service.create_session(
1286-
app_name='my_app', user_id='user', session_id='s1'
1287-
)
1288-
1289-
with mock.patch.object(
1290-
service, '_supports_row_level_locking', return_value=True
1291-
):
1292-
original_fn = database_session_service._select_required_state
1293-
calls = []
1294-
1295-
async def spy(**kwargs):
1296-
calls.append(kwargs.get('use_row_level_locking'))
1297-
return await original_fn(**kwargs)
1298-
1299-
with mock.patch.object(
1300-
database_session_service,
1301-
'_select_required_state',
1302-
side_effect=spy,
1303-
):
1304-
event_user_delta = Event(
1305-
invocation_id='inv4',
1306-
author='user',
1307-
actions=EventActions(state_delta={'user:key1': 'v1'}),
1308-
)
1309-
await service.append_event(session, event_user_delta)
1310-
1311-
assert len(calls) == 2
1312-
# app_states lock: False (no app: delta)
1313-
assert calls[0] is False
1314-
# user_states lock: True (has user: delta)
1315-
assert calls[1] is True
1316-
finally:
1317-
await service.close()
1318-
1319-
1320-
@pytest.mark.asyncio
1321-
async def test_append_event_acquires_for_update_when_both_deltas_present():
1322-
"""FOR UPDATE locks on both app_states and user_states should be acquired
1323-
when the event has both app: and user: prefixed state deltas."""
1324-
service = DatabaseSessionService('sqlite+aiosqlite:///:memory:')
1325-
try:
1326-
session = await service.create_session(
1327-
app_name='my_app', user_id='user', session_id='s1'
1328-
)
1329-
1330-
with mock.patch.object(
1331-
service, '_supports_row_level_locking', return_value=True
1332-
):
1333-
original_fn = database_session_service._select_required_state
1334-
calls = []
1335-
1336-
async def spy(**kwargs):
1337-
calls.append(kwargs.get('use_row_level_locking'))
1338-
return await original_fn(**kwargs)
1339-
1340-
with mock.patch.object(
1341-
database_session_service,
1342-
'_select_required_state',
1343-
side_effect=spy,
1344-
):
1345-
event_both = Event(
1346-
invocation_id='inv5',
1347-
author='user',
1348-
actions=EventActions(state_delta={'app:ak': 'av', 'user:uk': 'uv'}),
1349-
)
1350-
await service.append_event(session, event_both)
1351-
1352-
assert len(calls) == 2
1353-
assert calls[0] is True
1354-
assert calls[1] is True
1355-
finally:
1356-
await service.close()
1190+
@pytest.mark.parametrize(
1191+
'state_delta, expected_app_lock, expected_user_lock',
1192+
[
1193+
pytest.param(
1194+
{'session_key': 'v1'},
1195+
False,
1196+
False,
1197+
id='session_only_delta',
1198+
),
1199+
pytest.param(None, False, False, id='no_state_delta'),
1200+
pytest.param({'app:key1': 'v1'}, True, False, id='app_delta_only'),
1201+
pytest.param({'user:key1': 'v1'}, False, True, id='user_delta_only'),
1202+
pytest.param(
1203+
{'app:ak': 'av', 'user:uk': 'uv'},
1204+
True,
1205+
True,
1206+
id='both_app_and_user_delta',
1207+
),
1208+
],
1209+
)
1210+
async def test_append_event_conditional_for_update_locking(
1211+
lock_spy_harness, state_delta, expected_app_lock, expected_user_lock
1212+
):
1213+
"""FOR UPDATE locks should only be acquired for scopes that have deltas."""
1214+
service, session, calls = lock_spy_harness
1215+
1216+
kwargs = {'invocation_id': 'inv', 'author': 'user'}
1217+
if state_delta is not None:
1218+
kwargs['actions'] = EventActions(state_delta=state_delta)
1219+
event = Event(**kwargs)
1220+
await service.append_event(session, event)
1221+
1222+
assert len(calls) == 2
1223+
assert calls[0] is expected_app_lock
1224+
assert calls[1] is expected_user_lock

0 commit comments

Comments
 (0)