mocks.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. # mocks.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
  2. # This copyright was auto-generated on Thu Mar 20 21:57:17 UTC 2025
  3. from __future__ import annotations
  4. import asyncio
  5. import re
  6. from dataclasses import dataclass, field
  7. from operator import is_
  8. from pathlib import Path
  9. from typing import Any, Generic, Optional, Pattern, TypeVar
  10. import requests
  11. from open_gopro import WiredGoPro, WirelessGoPro
  12. from open_gopro.api import (
  13. BleCommands,
  14. BleSettings,
  15. BleStatuses,
  16. HttpCommands,
  17. HttpSettings,
  18. WirelessApi,
  19. )
  20. from open_gopro.domain.communicator_interface import (
  21. BleMessage,
  22. GoProBle,
  23. GoProWifi,
  24. HttpMessage,
  25. MessageRules,
  26. )
  27. from open_gopro.domain.exceptions import ConnectFailed, FailedToFindDevice
  28. from open_gopro.features.base_feature import BaseFeature
  29. from open_gopro.gopro_base import GoProBase
  30. from open_gopro.models import GoProResp
  31. from open_gopro.models.constants import CmdId, GoProUUID, StatusId
  32. from open_gopro.models.constants.constants import ErrorCode
  33. from open_gopro.models.proto.cohn_pb2 import EnumCOHNStatus, NotifyCOHNStatus
  34. from open_gopro.models.types import CameraState, ResponseType, UpdateCb, UpdateType
  35. from open_gopro.network.ble import (
  36. BLEController,
  37. BleDevice,
  38. BleHandle,
  39. BleUUID,
  40. DisconnectHandlerType,
  41. NotiHandlerType,
  42. )
  43. from open_gopro.network.wifi import SsidState, WifiController
  44. from tests import mock_good_response, versions
  45. api_versions = {"2.0": WirelessApi}
  46. T = TypeVar("T")
  47. T2 = TypeVar("T2")
  48. @dataclass
  49. class MockGattTable:
  50. def handle2uuid(self, *args):
  51. return GoProUUID.CQ_QUERY_RESP
  52. class MockBleController(BLEController, Generic[BleHandle, BleDevice]):
  53. # pylint: disable=signature-differs
  54. def __init__(self, *args, **kwargs) -> None:
  55. self.gatt_db = MockGattTable()
  56. async def scan(self, token: Pattern, timeout: int, service_uuids: list[BleUUID] = None) -> str:
  57. if token == re.compile(".*device") or token == re.compile("device"):
  58. return "scanned_device"
  59. raise FailedToFindDevice
  60. async def read(self, handle: BleHandle, uuid: str) -> bytearray:
  61. return bytearray()
  62. async def write(self, handle: BleHandle, uuid: str, data: bytearray) -> None:
  63. return
  64. async def connect(self, disconnect_cb: DisconnectHandlerType, device: BleDevice, timeout: int) -> str:
  65. if disconnect_cb is None:
  66. raise ConnectFailed("forced connect fail from test", timeout, 1)
  67. return "connected_device"
  68. async def pair(self, handle: BleHandle) -> None:
  69. return
  70. async def enable_notifications(self, handle: BleHandle, handler: NotiHandlerType) -> None:
  71. return
  72. async def discover_chars(self, handle: BleHandle, service_uuids: list[BleUUID] = None) -> MockGattTable:
  73. return self.gatt_db
  74. async def disconnect(self, handle: BleHandle) -> None:
  75. return
  76. def disconnection_handler(_) -> None:
  77. print("Entered test disconnect callback")
  78. def notification_handler(handle: int, data: bytearray) -> None:
  79. print("Entered test notification callback")
  80. class MockBleCommunicator(GoProBle):
  81. # pylint: disable=signature-differs
  82. def __init__(self, test_version: str) -> None:
  83. super().__init__(
  84. MockBleController(),
  85. disconnection_handler,
  86. notification_handler,
  87. "target",
  88. )
  89. self._api = api_versions[test_version](self)
  90. self._ble_message_response: GoProResp = None
  91. self.spy: dict = {}
  92. def set_ble_message_response(self, response: MockGoproResp) -> None:
  93. self._ble_message_response = response
  94. def identifier(self) -> str:
  95. return "identifier"
  96. def _register_update(self, callback: UpdateCb, update: GoProBle._CompositeRegisterType | UpdateType) -> None:
  97. return
  98. def _unregister_update(
  99. self, callback: UpdateCb, update: GoProBle._CompositeRegisterType | UpdateType | None = None
  100. ) -> None:
  101. return
  102. def register_update(self, callback: UpdateCb, update: UpdateType) -> None:
  103. return
  104. def unregister_update(self, callback: UpdateCb, update: UpdateType | None = None) -> None:
  105. return
  106. async def _send_ble_message(
  107. self, message: BleMessage, rules: MessageRules = MessageRules(), **kwargs: Any
  108. ) -> GoProResp:
  109. self.spy = dict(uuid=message._uuid, packet=message._build_data(**kwargs))
  110. return self._ble_message_response
  111. async def _read_ble_characteristic(
  112. self, message: BleMessage, rules: MessageRules = MessageRules(), **kwargs: Any
  113. ) -> GoProResp:
  114. self.spy = dict(uuid=message._uuid)
  115. return self._ble_message_response
  116. @property
  117. def ble_command(self) -> BleCommands:
  118. return self._api.ble_command
  119. @property
  120. def ble_setting(self) -> BleSettings:
  121. return self._api.ble_setting
  122. @property
  123. def ble_status(self) -> BleStatuses:
  124. return self._api.ble_status
  125. class MockWifiController(WifiController):
  126. # pylint: disable=signature-differs
  127. def __init__(self, interface: Optional[str] = None, password: Optional[str] = None) -> None: ...
  128. async def connect(self, ssid: str, password: str, timeout: float) -> bool:
  129. return True if password == "password" else False
  130. async def disconnect(self) -> bool:
  131. return True
  132. def current(self) -> tuple[Optional[str], SsidState]:
  133. return "current_ssid", SsidState.CONNECTED
  134. def available_interfaces(self) -> list[str]:
  135. return ["interface1", "interface2"]
  136. def power(self, power: bool) -> None:
  137. return
  138. def is_on(self) -> bool:
  139. return True
  140. @dataclass
  141. class MockWifiResponse:
  142. url: str
  143. body: dict[str, Any] = field(default_factory=dict)
  144. class MockWifiCommunicator(GoProWifi):
  145. # pylint: disable=signature-differs
  146. def __init__(self, test_version: str):
  147. super().__init__(MockWifiController())
  148. self._api = api_versions[test_version](self)
  149. @property
  150. def identifier(self) -> str:
  151. return "identifier"
  152. async def _get_json(
  153. self, message: HttpMessage, *, timeout: int = 0, rules: MessageRules = MessageRules(), **kwargs
  154. ) -> GoProResp:
  155. return MockWifiResponse(message.build_url(**kwargs), message.build_body(**kwargs))
  156. async def _get_stream(
  157. self, message: HttpMessage, *, timeout: int = 0, rules: MessageRules = MessageRules(), **kwargs
  158. ) -> GoProResp:
  159. return MockWifiResponse(message.build_url(path=kwargs["camera_file"])), kwargs["local_file"]
  160. async def _put_json(
  161. self, message: HttpMessage, *, timeout: int = 0, rules: MessageRules = MessageRules(), **kwargs
  162. ) -> GoProResp:
  163. return MockWifiResponse(message.build_url(**kwargs), message.build_body(**kwargs))
  164. async def _stream_to_file(self, url: str, file: Path):
  165. return url, file
  166. def register_update(self, callback: UpdateCb, update: UpdateType) -> None:
  167. return
  168. def unregister_update(self, callback: UpdateCb, update: UpdateType = None) -> None:
  169. return
  170. def _register_update(self, callback: UpdateCb, update: GoProBle._CompositeRegisterType | UpdateType) -> None:
  171. return
  172. def _unregister_update(
  173. self, callback: UpdateCb, update: GoProBle._CompositeRegisterType | UpdateType | None = None
  174. ) -> None:
  175. return
  176. @property
  177. def http_command(self) -> HttpCommands:
  178. return self._api.http_command
  179. @property
  180. def http_setting(self) -> HttpSettings:
  181. return self._api.http_setting
  182. @dataclass
  183. class MockGoproResp:
  184. value: Any | None = None
  185. status: ErrorCode | None = None
  186. identifier: ResponseType | None = None
  187. @property
  188. def data(self) -> Any:
  189. return self.value
  190. @property
  191. def ok(self) -> bool:
  192. return self.status is ErrorCode.SUCCESS if self.status else True
  193. # Create a context manager class to mock GoproObserverDistinctInitial
  194. class MockObserver(Generic[T, T2]):
  195. initial_response: Any = None
  196. first_response: Any = None
  197. def __init__(self, *args, **kwargs) -> None:
  198. return
  199. async def __aenter__(self) -> Any:
  200. return self
  201. async def __aexit__(self, *args: Any) -> None:
  202. pass
  203. def observe(self) -> Any:
  204. class ObservableMock:
  205. def __init__(self, first_response: Any) -> None:
  206. self.first_response = first_response
  207. async def first(self, *args: Any, **kwargs: Any) -> Any:
  208. return self.first_response
  209. return ObservableMock(self.first_response)
  210. class MockWiredGoPro(WiredGoPro):
  211. def __init__(self, test_version: str) -> None:
  212. super().__init__(serial=None, poll_period=0.5)
  213. self.http_command.wired_usb_control = self._mock_empty_return
  214. self.http_command.get_open_gopro_api_version = self._mock_get_version
  215. self.http_command.get_camera_state = self._mock_get_state
  216. self.http_command.set_third_party_client_info = self._mock_empty_return
  217. self.state_response: CameraState = {}
  218. async def _mock_get_state(self, *args, **kwargs):
  219. return MockGoproResp(self.state_response)
  220. def set_state_response(self, response: CameraState):
  221. self.state_response = response
  222. async def _mock_empty_return(self, *args, **kwargs):
  223. return
  224. async def _mock_get_version(self, *args, **kwargs):
  225. return MockGoproResp("2.0")
  226. class MockFeature(BaseFeature):
  227. def __init__(self, *args, **kwargs) -> None:
  228. return
  229. async def wait_until_ready(self) -> None:
  230. return
  231. @property
  232. def is_ready(self) -> bool:
  233. return True
  234. @property
  235. def is_supported(self) -> bool:
  236. return True
  237. async def close(self) -> None:
  238. return
  239. class MockEvent:
  240. async def wait(self) -> None:
  241. return
  242. def set(self) -> None:
  243. return
  244. class MockWirelessGoPro(WirelessGoPro):
  245. def __init__(self, test_version: str) -> None:
  246. super().__init__(
  247. target="device",
  248. ble_adapter=MockBleController,
  249. wifi_adapter=MockWifiController,
  250. enable_wifi=True,
  251. maintain_state=False,
  252. )
  253. self._test_version = test_version
  254. # TODO we need to find a way to inject these from individual tests
  255. self._api.ble_command.get_open_gopro_api_version = self._mock_version
  256. self._api.http_command.get_open_gopro_api_version = self._mock_version
  257. self._api.ble_command.cohn_get_certificate = self._mock_get_cohn_cohn_cert
  258. self._api.ble_command.get_ap_entries = self._mock_get_ap_entries
  259. self._api.ble_command.cohn_get_status = self._mock_get_cohn_status
  260. self.http_command.set_third_party_client_info = self._mock_empty_return
  261. self.ble_command.set_third_party_client_info = self._mock_empty_return
  262. self.ble_command.set_date_time_tz_dst = self._mock_empty_return
  263. self.ble_command.set_pairing_complete = self._mock_empty_return
  264. self._ble.write = self._mock_write
  265. self._ble._gatt_table = MockGattTable()
  266. self._test_response_uuid = GoProUUID.CQ_COMMAND
  267. self._test_response_data = bytearray()
  268. self.ble_status.ap_mode.get_value = self._mock_wifi_check
  269. self._ble_disconnect_event = MockEvent()
  270. def set_requests_session(self, session: requests.Session) -> None:
  271. self._mock_requests_session = session
  272. @property
  273. def _requests_session(self) -> requests.Session:
  274. return self._mock_requests_session
  275. async def mock_gopro_resp(self, value: T) -> GoProResp[T]:
  276. return MockGoproResp(value)
  277. async def _open_wifi(self, timeout: int = 15, retries: int = 5) -> None:
  278. self._api.ble_command.get_wifi_password = self._mock_password
  279. self._api.ble_command.get_wifi_ssid = self._mock_ssid
  280. self._api.ble_command.enable_wifi_ap = self._mock_empty_return
  281. await super()._open_wifi(timeout, retries)
  282. async def _close_ble(self) -> None:
  283. self._ble_disconnect_event.set()
  284. await super()._close_ble()
  285. async def _open_ble(self, timeout: int, retries: int) -> None:
  286. await super()._open_ble(timeout=timeout, retries=retries)
  287. self._ble._gatt_table.handle2uuid = self._mock_uuid
  288. async def _read_ble_characteristic(
  289. self, message: BleMessage, rules: MessageRules = MessageRules(), **kwargs: Any
  290. ) -> GoProResp:
  291. raise NotImplementedError
  292. async def _mock_get_cohn_status(self, *args, **kwargs) -> MockGoproResp:
  293. return MockGoproResp(NotifyCOHNStatus(status=EnumCOHNStatus.COHN_UNPROVISIONED))
  294. async def _mock_version(self) -> MockGoproResp:
  295. return MockGoproResp("2.0")
  296. async def _mock_password(self) -> MockGoproResp:
  297. return MockGoproResp("password")
  298. async def _mock_wifi_check(self) -> MockGoproResp:
  299. return MockGoproResp(True)
  300. async def _mock_ssid(self) -> MockGoproResp:
  301. return MockGoproResp("ssid")
  302. async def _mock_empty_return(self, *args, **kwargs) -> MockGoproResp:
  303. return MockGoproResp()
  304. def _mock_uuid(self, _) -> BleUUID:
  305. return self._test_response_uuid
  306. async def _mock_write(self, uuid: str, data: bytearray) -> None:
  307. assert self._test_response_data is not None
  308. self._notification_handler(0, self._test_response_data)
  309. # for packet in self._test_response_data:
  310. # self._notification_handler(0, packet)
  311. async def _mock_get_cohn_cohn_cert(self) -> MockGoproResp:
  312. @dataclass
  313. class MockCert:
  314. cert: str
  315. return MockGoproResp(MockCert("cert"))
  316. async def _mock_get_ap_entries(self) -> MockGoproResp:
  317. return MockGoproResp("ap_entries")
  318. @property
  319. def is_ble_connected(self) -> bool:
  320. return True
  321. @property
  322. def is_http_connected(self) -> bool:
  323. return True
  324. class MockGoProMaintainBle(WirelessGoPro):
  325. def __init__(self) -> None:
  326. super().__init__(
  327. target="device",
  328. ble_adapter=MockBleController,
  329. wifi_adapter=MockWifiController,
  330. enable_wifi=True,
  331. maintain_ble=True,
  332. keep_alive_interval=1,
  333. )
  334. self._test_version = "2.0"
  335. self._api.ble_command.get_open_gopro_api_version = self._mock_get_version
  336. self.ble_status.encoding.register_value_update = self._mock_register_encoding
  337. self.ble_status.busy.register_value_update = self._mock_register_busy
  338. self.ble_setting.led.set = self._mock_led_set
  339. self._open_wifi = self._mock_open_wifi
  340. self.ble_command.set_pairing_complete = self._mock_pairing_complete
  341. self._sync_resp_ready_q.get = self._mock_q_get
  342. self.generic_spy: asyncio.Queue[Any] = asyncio.Queue()
  343. async def _mock_q_get(self, *args, **kwargs):
  344. return mock_good_response
  345. async def _mock_led_set(self, *args):
  346. await self.generic_spy.put(args)
  347. return mock_good_response
  348. async def _mock_open_wifi(self, *args):
  349. return None
  350. async def _mock_pairing_complete(self) -> MockGoproResp:
  351. return MockGoproResp()
  352. async def _mock_register_encoding(self, *args):
  353. return MockGoproResp({StatusId.ENCODING: 1})
  354. async def _mock_register_busy(self, *args):
  355. return MockGoproResp({StatusId.BUSY: 1})
  356. async def mock_handle2uuid(self, *args):
  357. return GoProUUID.CQ_QUERY_RESP
  358. async def _open_ble(self, timeout: int, retries: int) -> None:
  359. await super()._open_ble(timeout=timeout, retries=retries)
  360. async def _mock_get_version(self) -> MockGoproResp:
  361. return MockGoproResp("2.0")