1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
"""Tests for the BLE manufacturer data parser."""
from __future__ import annotations
from aioshelly.ble.manufacturer_data import (
ALLTERCO_MFID,
BLOCK_TYPE_FLAGS,
BLOCK_TYPE_MAC,
BLOCK_TYPE_MODEL,
has_rpc_over_ble,
parse_shelly_manufacturer_data,
)
def test_parse_empty_manufacturer_data() -> None:
"""Test parsing empty manufacturer data."""
assert parse_shelly_manufacturer_data({}) is None
def test_parse_wrong_manufacturer_id() -> None:
"""Test parsing manufacturer data with wrong ID."""
assert parse_shelly_manufacturer_data({0x1234: b"\x01\x02\x03"}) is None
def test_parse_too_short_data() -> None:
"""Test parsing manufacturer data that is too short."""
assert parse_shelly_manufacturer_data({ALLTERCO_MFID: b""}) is None
def test_parse_flags_only() -> None:
"""Test parsing manufacturer data with flags only."""
# Block type 0x01 (flags) + 2 bytes flags (0x0004 = RPC over BLE enabled)
data = bytes([BLOCK_TYPE_FLAGS, 0x04, 0x00])
result = parse_shelly_manufacturer_data({ALLTERCO_MFID: data})
assert result == {"flags": 0x0004}
def test_parse_mac_only() -> None:
"""Test parsing manufacturer data with MAC address only."""
# Block type 0x0A (MAC) + 6 bytes MAC address
data = bytes([BLOCK_TYPE_MAC, 0xC0, 0x49, 0xEF, 0x88, 0x73, 0xE8])
result = parse_shelly_manufacturer_data({ALLTERCO_MFID: data})
assert result == {"mac": "C0:49:EF:88:73:E8"}
def test_parse_model_only() -> None:
"""Test parsing manufacturer data with model ID only."""
# Block type 0x0B (model) + 2 bytes model ID (0x1234)
data = bytes([BLOCK_TYPE_MODEL, 0x34, 0x12])
result = parse_shelly_manufacturer_data({ALLTERCO_MFID: data})
assert result == {"model_id": 0x1234}
def test_parse_all_blocks() -> None:
"""Test parsing manufacturer data with all block types."""
# Flags + MAC + Model
data = bytes(
[
BLOCK_TYPE_FLAGS,
0x07,
0x00, # flags = 0x0007 (discoverable, auth, RPC over BLE)
BLOCK_TYPE_MAC,
0xC0,
0x49,
0xEF,
0x88,
0x73,
0xE8, # MAC
BLOCK_TYPE_MODEL,
0x34,
0x12, # model = 0x1234
]
)
result = parse_shelly_manufacturer_data({ALLTERCO_MFID: data})
assert result == {
"flags": 0x0007,
"mac": "C0:49:EF:88:73:E8",
"model_id": 0x1234,
}
def test_parse_truncated_flags() -> None:
"""Test parsing manufacturer data with truncated flags block."""
# Block type 0x01 (flags) but only 1 byte of data (needs 2)
data = bytes([BLOCK_TYPE_FLAGS, 0x04])
result = parse_shelly_manufacturer_data({ALLTERCO_MFID: data})
# Should stop parsing and return empty result
assert result is None
def test_parse_truncated_mac() -> None:
"""Test parsing manufacturer data with truncated MAC block."""
# Block type 0x0A (MAC) but only 3 bytes (needs 6)
data = bytes([BLOCK_TYPE_MAC, 0xC0, 0x49, 0xEF])
result = parse_shelly_manufacturer_data({ALLTERCO_MFID: data})
# Should stop parsing and return empty result
assert result is None
def test_parse_truncated_model() -> None:
"""Test parsing manufacturer data with truncated model block."""
# Block type 0x0B (model) but only 1 byte (needs 2)
data = bytes([BLOCK_TYPE_MODEL, 0x34])
result = parse_shelly_manufacturer_data({ALLTERCO_MFID: data})
# Should stop parsing and return empty result
assert result is None
def test_parse_partial_blocks() -> None:
"""Test parsing with valid block followed by incomplete block."""
# Valid flags block followed by incomplete MAC block
data = bytes([BLOCK_TYPE_FLAGS, 0x04, 0x00, BLOCK_TYPE_MAC, 0xC0, 0x49])
result = parse_shelly_manufacturer_data({ALLTERCO_MFID: data})
# Should parse flags but stop at incomplete MAC
assert result == {"flags": 0x0004}
def test_parse_lone_block_type() -> None:
"""Test parsing with lone block type byte at end (no data following)."""
# Valid flags block followed by MODEL block type with no data
data = bytes([BLOCK_TYPE_FLAGS, 0x04, 0x00, BLOCK_TYPE_MODEL])
result = parse_shelly_manufacturer_data({ALLTERCO_MFID: data})
# Should parse flags but stop at MODEL block that has no data
assert result == {"flags": 0x0004}
def test_parse_unknown_block_type() -> None:
"""Test parsing manufacturer data with unknown block type."""
# Flags + unknown block type 0xFF
data = bytes([BLOCK_TYPE_FLAGS, 0x04, 0x00, 0xFF, 0x01, 0x02])
result = parse_shelly_manufacturer_data({ALLTERCO_MFID: data})
# Should parse flags and stop at unknown block
assert result == {"flags": 0x0004}
def test_has_rpc_over_ble_enabled() -> None:
"""Test checking if RPC over BLE is enabled."""
# RPC over BLE enabled (bit 2 = 0x04)
data = bytes([BLOCK_TYPE_FLAGS, 0x04, 0x00])
assert has_rpc_over_ble({ALLTERCO_MFID: data}) is True
def test_has_rpc_over_ble_disabled() -> None:
"""Test checking if RPC over BLE is disabled."""
# RPC over BLE disabled (only bits 0 and 1 set)
data = bytes([BLOCK_TYPE_FLAGS, 0x03, 0x00])
assert has_rpc_over_ble({ALLTERCO_MFID: data}) is False
def test_has_rpc_over_ble_no_flags() -> None:
"""Test checking RPC over BLE with no flags block."""
# Only MAC, no flags
data = bytes([BLOCK_TYPE_MAC, 0xC0, 0x49, 0xEF, 0x88, 0x73, 0xE8])
assert has_rpc_over_ble({ALLTERCO_MFID: data}) is False
def test_has_rpc_over_ble_empty_data() -> None:
"""Test checking RPC over BLE with empty data."""
assert has_rpc_over_ble({}) is False
def test_has_rpc_over_ble_wrong_manufacturer() -> None:
"""Test checking RPC over BLE with wrong manufacturer ID."""
data = bytes([BLOCK_TYPE_FLAGS, 0x04, 0x00])
assert has_rpc_over_ble({0x1234: data}) is False
|