|
24 | 24 | ExecRequest, |
25 | 25 | ExecSqlRequest, |
26 | 26 | FailureResponse, |
| 27 | + FilesResponse, |
27 | 28 | FinalizeRequest, |
| 29 | + HeartbeatRequest, |
| 30 | + InterruptRequest, |
28 | 31 | LeaderRequest, |
29 | 32 | LeaderResponse, |
30 | 33 | MetadataResponse, |
@@ -118,6 +121,36 @@ def test_finalize_request(self) -> None: |
118 | 121 | assert msg.db_id == 1 |
119 | 122 | assert msg.stmt_id == 2 |
120 | 123 |
|
| 124 | + def test_heartbeat_request(self) -> None: |
| 125 | + """HeartbeatRequest(timestamp=1_700_000_000): type=2, body=uint64. |
| 126 | +
|
| 127 | + The C server currently does not dispatch HEARTBEAT, but the wire |
| 128 | + layout is still shipped as part of the public message surface; |
| 129 | + pin it here so an encoder refactor cannot drift the byte shape |
| 130 | + symmetrically with the decoder. |
| 131 | + """ |
| 132 | + ts = 1_700_000_000 |
| 133 | + expected = _header(1, 2) + _u64(ts) |
| 134 | + assert encode_message(HeartbeatRequest(timestamp=ts)) == expected |
| 135 | + |
| 136 | + msg = decode_message(expected, is_request=True) |
| 137 | + assert isinstance(msg, HeartbeatRequest) |
| 138 | + assert msg.timestamp == ts |
| 139 | + |
| 140 | + def test_interrupt_request(self) -> None: |
| 141 | + """InterruptRequest(db_id=0x1234567890ABCDEF): type=10. |
| 142 | +
|
| 143 | + Body: uint64(db_id). Single-field message, easy to misencode |
| 144 | + LE-vs-BE on a refactor; golden pin catches it immediately. |
| 145 | + """ |
| 146 | + db_id = 0x1234567890ABCDEF |
| 147 | + expected = _header(1, 10) + _u64(db_id) |
| 148 | + assert encode_message(InterruptRequest(db_id=db_id)) == expected |
| 149 | + |
| 150 | + msg = decode_message(expected, is_request=True) |
| 151 | + assert isinstance(msg, InterruptRequest) |
| 152 | + assert msg.db_id == db_id |
| 153 | + |
121 | 154 | def test_exec_request_with_params(self) -> None: |
122 | 155 | """ExecRequest(db_id=1, stmt_id=2, params=[42, "hello"]): type=5, schema=0. |
123 | 156 |
|
@@ -267,6 +300,36 @@ def test_rows_response_part_marker(self) -> None: |
267 | 300 | # Last 8 bytes should be the PART marker |
268 | 301 | assert body[-8:] == part_marker |
269 | 302 |
|
| 303 | + def test_files_response(self) -> None: |
| 304 | + """FilesResponse with two files: type=9. |
| 305 | +
|
| 306 | + Body layout: uint64(count), then for each file |
| 307 | + text(name) — null-terminated, padded to 8-byte boundary |
| 308 | + uint64(size) — must be 8-byte aligned (no per-file padding) |
| 309 | + raw content — ``size`` bytes, 8-byte aligned |
| 310 | +
|
| 311 | + File 1: name="db" → b"db\\0\\0\\0\\0\\0\\0" (8 bytes), 8 bytes of content. |
| 312 | + File 2: name="main.db" → b"main.db\\0" (exact word), 16 bytes of content. |
| 313 | +
|
| 314 | + Total body = 8 (count) + 8+8+8 (f1) + 8+8+16 (f2) = 64 bytes = 8 words. |
| 315 | + """ |
| 316 | + count = _u64(2) |
| 317 | + f1_name = _text("db") # 8 bytes |
| 318 | + f1_size = _u64(8) |
| 319 | + f1_content = b"pageonep" # exactly 8 bytes |
| 320 | + f2_name = _text("main.db") # 8 bytes |
| 321 | + f2_size = _u64(16) |
| 322 | + f2_content = b"pageone_pagetwo_" # exactly 16 bytes |
| 323 | + expected_body = count + f1_name + f1_size + f1_content + f2_name + f2_size + f2_content |
| 324 | + expected = _header(len(expected_body) // 8, 9) + expected_body |
| 325 | + |
| 326 | + msg = FilesResponse(files={"db": f1_content, "main.db": f2_content}) |
| 327 | + assert encode_message(msg) == expected |
| 328 | + |
| 329 | + decoded = decode_message(expected, is_request=False) |
| 330 | + assert isinstance(decoded, FilesResponse) |
| 331 | + assert decoded.files == {"db": f1_content, "main.db": f2_content} |
| 332 | + |
270 | 333 |
|
271 | 334 | class TestGoldenHandshake: |
272 | 335 | """Verify handshake encoding.""" |
|
0 commit comments