Skip to content

Commit 559af89

Browse files
authored
Merge pull request #123 from blues/alex-add-heartbeat
feat: handle heartbeat responses in Notecard transaction
2 parents 09a4dea + 96db8bd commit 559af89

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

notecard/notecard.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,15 @@ def Transaction(self, req, lock=True):
361361

362362
error = True
363363
break
364+
elif '{heartbeat}' in rsp_json['err']:
365+
if self._debug:
366+
try:
367+
print(f'[DEBUG] {rsp_json["status"]}')
368+
except:
369+
pass
370+
371+
error = False
372+
continue
364373

365374
error = False
366375
break

test/test_notecard.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,71 @@ def test_transaction_does_not_retry_on_bad_bin_error_in_response(
317317

318318
assert card._transact.call_count == 1
319319

320+
@pytest.mark.parametrize('num_heartbeats,debug_enabled', [
321+
(1, False),
322+
(4, False),
323+
(notecard.CARD_TRANSACTION_RETRIES + 1, False),
324+
(1, True),
325+
(4, True),
326+
(notecard.CARD_TRANSACTION_RETRIES + 1, True),
327+
])
328+
def test_transaction_continues_after_heartbeat_to_get_valid_response(
329+
self, arrange_transaction_test, num_heartbeats, debug_enabled):
330+
card = arrange_transaction_test()
331+
card._debug = debug_enabled
332+
req = {"req": "note.add"}
333+
334+
# num_heartbeats of heartbeat responses followed by valid response
335+
heartbeat_response = b'{"err":"{heartbeat}","status":"testing"}\r\n'
336+
json_responses = [{'err': '{heartbeat}', 'status': 'testing'}] * num_heartbeats + [{'total': 42}]
337+
338+
valid_response = b'{"total":42}\r\n'
339+
card._transact.side_effect = [heartbeat_response] * num_heartbeats + [valid_response]
340+
341+
with patch('notecard.notecard.json.loads') as mock_loads:
342+
mock_loads.side_effect = json_responses
343+
if debug_enabled:
344+
with patch('builtins.print') as mock_print:
345+
result = card.Transaction(req)
346+
# Verify debug messages were printed for each heartbeat
347+
assert mock_print.call_count >= num_heartbeats
348+
else:
349+
result = card.Transaction(req)
350+
351+
assert card._transact.call_count == num_heartbeats + 1
352+
assert result == {'total': 42}
353+
354+
def test_transaction_debug_heartbeat_with_and_without_status(
355+
self, arrange_transaction_test):
356+
card = arrange_transaction_test()
357+
card._debug = True
358+
req = {"req": "note.add"}
359+
360+
# First heartbeat has a status field, second doesn't, then valid response
361+
heartbeat_with_status = b'{"err":"{heartbeat}","status":"testing stsafe"}\r\n'
362+
heartbeat_without_status = b'{"err":"{heartbeat}"}\r\n'
363+
valid_response = b'{"total":42}\r\n'
364+
card._transact.side_effect = [heartbeat_with_status, heartbeat_without_status, valid_response]
365+
366+
json_responses = [
367+
{'err': '{heartbeat}', 'status': 'testing stsafe'},
368+
{'err': '{heartbeat}'},
369+
{'total': 42}
370+
]
371+
372+
with patch('notecard.notecard.json.loads') as mock_loads:
373+
mock_loads.side_effect = json_responses
374+
with patch('builtins.print') as mock_print:
375+
result = card.Transaction(req)
376+
377+
# Verify the debug message was printed for first heartbeat (has status)
378+
mock_print.assert_any_call('[DEBUG] testing stsafe')
379+
# For second heartbeat (no status), exception is silently ignored (pass)
380+
# So we should only see one debug print call
381+
debug_calls = [call for call in mock_print.call_args_list if '[DEBUG]' in str(call)]
382+
assert len(debug_calls) == 1
383+
assert result == {'total': 42}
384+
320385
@pytest.mark.parametrize(
321386
'rsp_expected,return_type',
322387
[

0 commit comments

Comments
 (0)