|
4 | 4 | and failure modes, ensuring the V1Channel behaves correctly in various scenarios. |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import gzip |
7 | 8 | import json |
8 | 9 | import logging |
9 | 10 | from collections.abc import Iterator |
@@ -163,6 +164,12 @@ def setup_map_rpc_channel(v1_channel: V1Channel) -> V1RpcChannel: |
163 | 164 | return v1_channel.map_rpc_channel |
164 | 165 |
|
165 | 166 |
|
| 167 | +@pytest.fixture(name="blob_rpc_channel") |
| 168 | +def setup_blob_rpc_channel(v1_channel: V1Channel) -> V1RpcChannel: |
| 169 | + """Fixture to set up the Blob RPC channel for tests.""" |
| 170 | + return v1_channel.blob_rpc_channel |
| 171 | + |
| 172 | + |
166 | 173 | @pytest.fixture(name="warning_caplog") |
167 | 174 | def setup_warning_caplog(caplog: pytest.LogCaptureFixture) -> pytest.LogCaptureFixture: |
168 | 175 | """Fixture to capture warning messages.""" |
@@ -632,6 +639,75 @@ async def test_v1_channel_send_map_command( |
632 | 639 | assert result == decompressed_map_data |
633 | 640 |
|
634 | 641 |
|
| 642 | +async def test_v1_channel_send_blob_command( |
| 643 | + blob_rpc_channel: V1RpcChannel, |
| 644 | + mock_mqtt_channel: FakeChannel, |
| 645 | +) -> None: |
| 646 | + """Test that the blob channel adds security parameters and decodes the response.""" |
| 647 | + public_key = {"n": "abc", "e": "010001"} |
| 648 | + mock_mqtt_channel.response_queue.append( |
| 649 | + RoborockMessage( |
| 650 | + protocol=RoborockMessageProtocol.RPC_RESPONSE, |
| 651 | + payload=json.dumps({"dps": {"102": json.dumps({"id": 12345, "result": {"pub_key": public_key}})}}).encode(), |
| 652 | + ) |
| 653 | + ) |
| 654 | + |
| 655 | + blob_data = b"blob response" |
| 656 | + compressed = gzip.compress(blob_data) |
| 657 | + header_size = 24 |
| 658 | + payload = bytearray(header_size + len(compressed)) |
| 659 | + payload[:8] = b"ROBOROCK" |
| 660 | + payload[8:12] = (12346).to_bytes(4, "little") |
| 661 | + payload[16:18] = header_size.to_bytes(2, "little") |
| 662 | + payload[20:24] = len(compressed).to_bytes(4, "little") |
| 663 | + payload[header_size:] = compressed |
| 664 | + mock_mqtt_channel.response_queue.append( |
| 665 | + RoborockMessage(protocol=RoborockMessageProtocol.MAP_RESPONSE, payload=bytes(payload)) |
| 666 | + ) |
| 667 | + |
| 668 | + result = await blob_rpc_channel.send_command( |
| 669 | + RoborockCommand.GET_PHOTO, |
| 670 | + params={"img_id": "photo-id", "type": 1}, |
| 671 | + ) |
| 672 | + |
| 673 | + assert result == blob_data |
| 674 | + sent_payload = mock_mqtt_channel.published_messages[-1].payload |
| 675 | + assert sent_payload is not None |
| 676 | + request_payload = json.loads(sent_payload) |
| 677 | + request = json.loads(request_payload["dps"]["101"]) |
| 678 | + assert request["method"] == RoborockCommand.GET_PHOTO |
| 679 | + assert request["params"] == { |
| 680 | + "security": { |
| 681 | + "pub_key": public_key, |
| 682 | + "cipher_suite": 0, |
| 683 | + }, |
| 684 | + "endpoint": TEST_SECURITY_DATA.endpoint, |
| 685 | + "nonce": TEST_SECURITY_DATA.nonce.hex(), |
| 686 | + "data_filter": {"img_id": "photo-id", "type": 1}, |
| 687 | + } |
| 688 | + |
| 689 | + |
| 690 | +async def test_v1_channel_send_blob_command_rejects_invalid_public_key( |
| 691 | + blob_rpc_channel: V1RpcChannel, |
| 692 | + mock_mqtt_channel: FakeChannel, |
| 693 | +) -> None: |
| 694 | + """Test that the blob channel rejects an invalid public key response.""" |
| 695 | + mock_mqtt_channel.response_queue.append( |
| 696 | + RoborockMessage( |
| 697 | + protocol=RoborockMessageProtocol.RPC_RESPONSE, |
| 698 | + payload=json.dumps({"dps": {"102": json.dumps({"id": 12345, "result": {}})}}).encode(), |
| 699 | + ) |
| 700 | + ) |
| 701 | + |
| 702 | + with pytest.raises(RoborockException, match="did not contain a public key"): |
| 703 | + await blob_rpc_channel.send_command( |
| 704 | + RoborockCommand.GET_PHOTO, |
| 705 | + params={"img_id": "photo-id", "type": 1}, |
| 706 | + ) |
| 707 | + |
| 708 | + assert len(mock_mqtt_channel.published_messages) == 1 |
| 709 | + |
| 710 | + |
635 | 711 | async def test_v1_channel_add_dps_listener( |
636 | 712 | v1_channel: V1Channel, |
637 | 713 | mock_mqtt_channel: FakeChannel, |
|
0 commit comments