test_recorder.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. """Tests for the recording infrastructure (Recorder + RecordedSession)."""
  2. import asyncio
  3. import json
  4. import pytest
  5. from pathlib import Path
  6. from cq2_sdk.testing import Recorder, RecordedSession
  7. from cq2_sdk.transport.tcp import TcpClient
  8. from cq2_sdk.transport.framing import WIRE_HEADER_SIZE, decode_header, encode_frame
  9. async def _scripted_echo_server(reader, writer, scripted_sc):
  10. """Echo back SC frames from a scripted list, in order."""
  11. try:
  12. idx = 0
  13. while True:
  14. try:
  15. hdr_raw = await reader.readexactly(WIRE_HEADER_SIZE)
  16. except asyncio.IncompleteReadError:
  17. break
  18. header = decode_header(hdr_raw)
  19. body = await reader.readexactly(header.body_len) if header.body_len else b""
  20. # Echo the CS frame back as an SC
  21. sc = scripted_sc[idx] if idx < len(scripted_sc) else None
  22. idx += 1
  23. if sc is None:
  24. break
  25. sc_msg_id, sc_recog, sc_body = sc
  26. resp = encode_frame(sc_msg_id, sc_recog, 0, 0, 0, sc_body)
  27. writer.write(resp)
  28. await writer.drain()
  29. finally:
  30. writer.close()
  31. @pytest.fixture
  32. async def echo_server():
  33. scripted_sc = [
  34. (529, 1, b"lk/uid"),
  35. (530, 999, b"ok"),
  36. (520, 0, b'{"list":[{"chr":1,"name":"a"}]}'),
  37. ]
  38. state = {"server": None}
  39. async def handler(reader, writer):
  40. await _scripted_echo_server(reader, writer, scripted_sc)
  41. server = await asyncio.start_server(handler, "127.0.0.1", 0)
  42. state["server"] = server
  43. port = server.sockets[0].getsockname()[1]
  44. yield port
  45. server.close()
  46. await server.wait_closed()
  47. @pytest.mark.asyncio
  48. async def test_recorder_logs_send_and_recv(echo_server, tmp_path):
  49. tcp = TcpClient("127.0.0.1", echo_server)
  50. await tcp.connect()
  51. out = tmp_path / "session.jsonl"
  52. recorder = Recorder(tcp, out)
  53. recorder.install()
  54. try:
  55. await tcp.send(2001, recog=900000, param1=0, param2=0, param3=0, body=b"attach1")
  56. hdr, body = await tcp.recv(timeout=1.0)
  57. assert hdr.msg_id == 529
  58. finally:
  59. recorder.close()
  60. await tcp.close()
  61. # Verify file
  62. lines = out.read_text(encoding="utf-8").strip().splitlines()
  63. assert len(lines) == 2
  64. send_event = json.loads(lines[0])
  65. assert send_event["dir"] == "cs"
  66. assert send_event["msg_id"] == 2001
  67. assert send_event["recog"] == 900000
  68. assert send_event["body"] == b"attach1".hex()
  69. recv_event = json.loads(lines[1])
  70. assert recv_event["dir"] == "sc"
  71. assert recv_event["msg_id"] == 529
  72. assert recv_event["body"] == b"lk/uid".hex()
  73. @pytest.mark.asyncio
  74. async def test_recorder_handles_empty_body(echo_server, tmp_path):
  75. tcp = TcpClient("127.0.0.1", echo_server)
  76. await tcp.connect()
  77. out = tmp_path / "session.jsonl"
  78. recorder = Recorder(tcp, out)
  79. recorder.install()
  80. try:
  81. await tcp.send(60000, recog=0, param1=0, param2=0, param3=0, body=b"")
  82. finally:
  83. recorder.close()
  84. await tcp.close()
  85. line = out.read_text(encoding="utf-8").strip()
  86. event = json.loads(line)
  87. assert event["body"] == ""
  88. def test_recorded_session_reads_events(tmp_path):
  89. p = tmp_path / "session.jsonl"
  90. events = [
  91. {"dir": "cs", "msg_id": 2001, "recog": 0, "p1": 0, "p2": 0, "p3": 0, "body": ""},
  92. {"dir": "sc", "msg_id": 529, "recog": 1, "p1": 0, "p2": 0, "p3": 0, "body": "ab"},
  93. {"dir": "cs", "msg_id": 100, "recog": 0, "p1": 0, "p2": 0, "p3": 0, "body": ""},
  94. ]
  95. p.write_text("\n".join(json.dumps(e) for e in events) + "\n", encoding="utf-8")
  96. session = RecordedSession(p)
  97. assert len(session.events) == 3
  98. assert len(session.sent()) == 2
  99. assert len(session.received()) == 1
  100. assert session.received()[0]["msg_id"] == 529
  101. def test_recorded_session_empty_file(tmp_path):
  102. p = tmp_path / "empty.jsonl"
  103. p.write_text("", encoding="utf-8")
  104. session = RecordedSession(p)
  105. assert session.events == []
  106. def test_recorded_session_skips_blank_lines(tmp_path):
  107. p = tmp_path / "blanks.jsonl"
  108. content = (
  109. json.dumps({"dir": "cs", "msg_id": 1, "recog": 0, "p1": 0, "p2": 0, "p3": 0, "body": ""})
  110. + "\n\n"
  111. + json.dumps({"dir": "sc", "msg_id": 2, "recog": 0, "p1": 0, "p2": 0, "p3": 0, "body": ""})
  112. + "\n"
  113. )
  114. p.write_text(content, encoding="utf-8")
  115. session = RecordedSession(p)
  116. assert len(session.events) == 2