1
0

test_art_frame.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. from unittest.mock import AsyncMock, MagicMock, patch
  2. import pytest
  3. from bleak.backends.device import BLEDevice
  4. from switchbot import SwitchBotAdvertisement
  5. from switchbot.devices.art_frame import COMMAND_SET_IMAGE, SwitchbotArtFrame
  6. from switchbot.devices.device import SwitchbotEncryptedDevice
  7. from . import ART_FRAME_INFO
  8. from .test_adv_parser import AdvTestCase, generate_ble_device
  9. def create_device_for_command_testing(
  10. adv_info: AdvTestCase,
  11. init_data: dict | None = None,
  12. ) -> SwitchbotArtFrame:
  13. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  14. device = SwitchbotArtFrame(
  15. ble_device, "ff", "ffffffffffffffffffffffffffffffff", model=adv_info.modelName
  16. )
  17. device.update_from_advertisement(
  18. make_advertisement_data(ble_device, adv_info, init_data)
  19. )
  20. device._send_command = AsyncMock()
  21. device._check_command_result = MagicMock()
  22. device.update = AsyncMock()
  23. return device
  24. def make_advertisement_data(
  25. ble_device: BLEDevice, adv_info: AdvTestCase, init_data: dict | None = None
  26. ) -> SwitchBotAdvertisement:
  27. """Set advertisement data with defaults."""
  28. if init_data is None:
  29. init_data = {}
  30. return SwitchBotAdvertisement(
  31. address="aa:bb:cc:dd:ee:ff",
  32. data={
  33. "rawAdvData": adv_info.service_data,
  34. "data": adv_info.data | init_data,
  35. "isEncrypted": False,
  36. "model": adv_info.model,
  37. "modelFriendlyName": adv_info.modelFriendlyName,
  38. "modelName": adv_info.modelName,
  39. }
  40. | init_data,
  41. device=ble_device,
  42. rssi=-80,
  43. active=True,
  44. )
  45. @pytest.mark.asyncio
  46. async def test_get_basic_info_none() -> None:
  47. device = create_device_for_command_testing(ART_FRAME_INFO)
  48. device._get_basic_info = AsyncMock(return_value=None)
  49. assert await device.get_basic_info() is None
  50. with pytest.raises(
  51. RuntimeError, match=r"Failed to retrieve basic info for current image index."
  52. ):
  53. await device._get_current_image_index()
  54. @pytest.mark.asyncio
  55. @pytest.mark.parametrize(
  56. ("basic_info", "result"),
  57. [
  58. (
  59. b"\x016\x07\x01\x00\x00\x04\x00\xde\x18\xa5\x00\x00\x00\x00\x00\x00",
  60. [
  61. False,
  62. 54,
  63. 0.7,
  64. 1,
  65. 0,
  66. 0,
  67. 0,
  68. 0,
  69. 4,
  70. [0, 222, 24, 165],
  71. ],
  72. ),
  73. ],
  74. )
  75. async def test_get_basic_info_parsing(
  76. basic_info: str, result: list[bool | int | float | list[int]]
  77. ) -> None:
  78. device = create_device_for_command_testing(ART_FRAME_INFO)
  79. device._get_basic_info = AsyncMock(return_value=basic_info)
  80. info = await device.get_basic_info()
  81. assert info["battery_charging"] == result[0]
  82. assert info["battery"] == result[1]
  83. assert info["firmware"] == result[2]
  84. assert info["hardware"] == result[3]
  85. assert info["display_size"] == result[4]
  86. assert info["display_mode"] == result[5]
  87. assert info["last_network_status"] == result[6]
  88. assert info["current_image_index"] == result[7]
  89. assert info["total_num_of_images"] == result[8]
  90. assert info["all_images_index"] == result[9]
  91. device._update_parsed_data(info)
  92. assert device.get_all_images_index() == result[9]
  93. assert device.get_total_images() == result[8]
  94. assert device.get_current_image_index() == result[7]
  95. @pytest.mark.asyncio
  96. async def test_select_image_with_single_image() -> None:
  97. device = create_device_for_command_testing(ART_FRAME_INFO)
  98. with (
  99. patch.object(device, "get_all_images_index", return_value=[1]),
  100. pytest.raises(RuntimeError, match=r"No images available to select from."),
  101. ):
  102. device._select_image_index(1)
  103. @pytest.mark.asyncio
  104. @pytest.mark.parametrize(
  105. ("current_index", "all_images_index", "expected_cmd"),
  106. [
  107. (100, [1, 100, 150], "150"),
  108. (150, [1, 100, 150], "1"),
  109. (1, [1, 100, 150], "100"),
  110. ],
  111. )
  112. async def test_next_image(
  113. current_index: int, all_images_index: list[int], expected_cmd: str
  114. ) -> None:
  115. device = create_device_for_command_testing(ART_FRAME_INFO)
  116. with (
  117. patch.object(device, "get_current_image_index", return_value=current_index),
  118. patch.object(device, "get_all_images_index", return_value=all_images_index),
  119. ):
  120. await device.next_image()
  121. device._send_command.assert_awaited_with(
  122. COMMAND_SET_IMAGE.format(f"{int(expected_cmd):02X}")
  123. )
  124. @pytest.mark.asyncio
  125. @pytest.mark.parametrize(
  126. ("current_index", "all_images_index", "expected_cmd"),
  127. [
  128. (100, [1, 100, 150], "1"),
  129. (150, [1, 100, 150], "100"),
  130. (1, [1, 100, 150], "150"),
  131. ],
  132. )
  133. async def test_prev_image(
  134. current_index: int, all_images_index: list[int], expected_cmd: str
  135. ) -> None:
  136. device = create_device_for_command_testing(ART_FRAME_INFO)
  137. with (
  138. patch.object(device, "get_current_image_index", return_value=current_index),
  139. patch.object(device, "get_all_images_index", return_value=all_images_index),
  140. ):
  141. await device.prev_image()
  142. device._send_command.assert_awaited_with(
  143. COMMAND_SET_IMAGE.format(f"{int(expected_cmd):02X}")
  144. )
  145. @pytest.mark.asyncio
  146. async def test_set_image_with_invalid_index() -> None:
  147. device = create_device_for_command_testing(ART_FRAME_INFO)
  148. with (
  149. patch.object(device, "get_total_images", return_value=3),
  150. patch.object(device, "get_all_images_index", return_value=[1, 2, 3]),
  151. pytest.raises(
  152. ValueError, match=r"Image index 5 is out of range. Total images: 3."
  153. ),
  154. ):
  155. await device.set_image(5)
  156. @pytest.mark.asyncio
  157. async def test_set_image_with_valid_index() -> None:
  158. device = create_device_for_command_testing(ART_FRAME_INFO)
  159. with (
  160. patch.object(device, "get_total_images", return_value=3),
  161. patch.object(device, "get_all_images_index", return_value=[10, 20, 30]),
  162. ):
  163. await device.set_image(1)
  164. device._send_command.assert_awaited_with(COMMAND_SET_IMAGE.format("14"))
  165. @pytest.mark.asyncio
  166. @patch.object(SwitchbotEncryptedDevice, "verify_encryption_key", new_callable=AsyncMock)
  167. async def test_verify_encryption_key(mock_parent_verify: AsyncMock) -> None:
  168. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  169. key_id = "ff"
  170. encryption_key = "ffffffffffffffffffffffffffffffff"
  171. mock_parent_verify.return_value = True
  172. result = await SwitchbotArtFrame.verify_encryption_key(
  173. device=ble_device,
  174. key_id=key_id,
  175. encryption_key=encryption_key,
  176. model=ART_FRAME_INFO.modelName,
  177. )
  178. mock_parent_verify.assert_awaited_once_with(
  179. ble_device,
  180. key_id,
  181. encryption_key,
  182. ART_FRAME_INFO.modelName,
  183. )
  184. assert result is True