test_event_warnings_errors.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. from openleadr import OpenADRClient, OpenADRServer, enable_default_logging, utils, messaging
  2. import pytest
  3. from functools import partial
  4. import asyncio
  5. from datetime import datetime, timedelta, timezone
  6. import logging
  7. enable_default_logging()
  8. async def on_create_party_registration(ven_name):
  9. return 'venid', 'regid'
  10. async def on_event_accepted(ven_id, event_id, opt_type, future=None):
  11. if future and future.done() is False:
  12. future.set_result(opt_type)
  13. async def good_on_event(event):
  14. return 'optIn'
  15. async def faulty_on_event(event):
  16. return None
  17. async def broken_on_event(event):
  18. raise KeyError("BOOM")
  19. @pytest.mark.asyncio
  20. async def test_client_no_event_handler(caplog):
  21. caplog.set_level(logging.WARNING)
  22. logger = logging.getLogger('openleadr')
  23. logger.setLevel(logging.DEBUG)
  24. client = OpenADRClient(ven_name='myven',
  25. vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b')
  26. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  27. server.add_handler('on_create_party_registration', on_create_party_registration)
  28. print("Running server")
  29. await server.run_async()
  30. # await asyncio.sleep(0.1)
  31. print("Running client")
  32. await client.run()
  33. event_confirm_future = asyncio.get_event_loop().create_future()
  34. print("Adding event")
  35. server.add_event(ven_id='venid',
  36. event_id='test_client_no_event_handler',
  37. signal_name='simple',
  38. signal_type='level',
  39. intervals=[{'dtstart': datetime.now(timezone.utc),
  40. 'duration': timedelta(seconds=1),
  41. 'signal_payload': 1.1}],
  42. target={'ven_id': 'venid'},
  43. callback=partial(on_event_accepted, future=event_confirm_future))
  44. print("Waiting for a response to the event")
  45. result = await event_confirm_future
  46. assert result == 'optOut'
  47. assert ("You should implement your own on_event handler. This handler receives "
  48. "an Event dict and should return either 'optIn' or 'optOut' based on your "
  49. "choice. Will opt out of the event for now.") in [rec.message for rec in caplog.records]
  50. await client.stop()
  51. await server.stop()
  52. await asyncio.gather(*[t for t in asyncio.all_tasks()][1:])
  53. @pytest.mark.asyncio
  54. async def test_client_faulty_event_handler(caplog):
  55. caplog.set_level(logging.WARNING)
  56. logger = logging.getLogger('openleadr')
  57. logger.setLevel(logging.DEBUG)
  58. client = OpenADRClient(ven_name='myven',
  59. vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b')
  60. client.add_handler('on_event', faulty_on_event)
  61. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  62. server.add_handler('on_create_party_registration', on_create_party_registration)
  63. print("Running server")
  64. await server.run_async()
  65. # await asyncio.sleep(0.1)
  66. print("Running client")
  67. await client.run()
  68. event_confirm_future = asyncio.get_event_loop().create_future()
  69. print("Adding event")
  70. server.add_event(ven_id='venid',
  71. event_id='test_client_faulty_event_handler',
  72. signal_name='simple',
  73. signal_type='level',
  74. intervals=[{'dtstart': datetime.now(timezone.utc),
  75. 'duration': timedelta(seconds=1),
  76. 'signal_payload': 1.1}],
  77. target={'ven_id': 'venid'},
  78. callback=partial(on_event_accepted, future=event_confirm_future))
  79. print("Waiting for a response to the event")
  80. result = await event_confirm_future
  81. assert result == 'optOut'
  82. assert ("Your on_event or on_update_event handler must return 'optIn' or 'optOut'; "
  83. f"you supplied {None}. Please fix your on_event handler.") in [rec.message for rec in caplog.records]
  84. await client.stop()
  85. await server.stop()
  86. await asyncio.gather(*[t for t in asyncio.all_tasks()][1:])
  87. @pytest.mark.asyncio
  88. async def test_client_exception_event_handler(caplog):
  89. caplog.set_level(logging.WARNING)
  90. logger = logging.getLogger('openleadr')
  91. logger.setLevel(logging.DEBUG)
  92. client = OpenADRClient(ven_name='myven',
  93. vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b')
  94. client.add_handler('on_event', broken_on_event)
  95. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  96. server.add_handler('on_create_party_registration', on_create_party_registration)
  97. print("Running server")
  98. await server.run_async()
  99. # await asyncio.sleep(0.1)
  100. print("Running client")
  101. await client.run()
  102. event_confirm_future = asyncio.get_event_loop().create_future()
  103. print("Adding event")
  104. server.add_event(ven_id='venid',
  105. event_id='test_client_exception_event_handler',
  106. signal_name='simple',
  107. signal_type='level',
  108. intervals=[{'dtstart': datetime.now(timezone.utc),
  109. 'duration': timedelta(seconds=1),
  110. 'signal_payload': 1.1}],
  111. target={'ven_id': 'venid'},
  112. callback=partial(on_event_accepted, future=event_confirm_future))
  113. print("Waiting for a response to the event")
  114. result = await event_confirm_future
  115. assert result == 'optOut'
  116. err = KeyError("BOOM")
  117. assert ("Your on_event handler encountered an error. Will Opt Out of the event. "
  118. f"The error was {err.__class__.__name__}: {str(err)}") in [rec.message for rec in caplog.records]
  119. await client.stop()
  120. await server.stop()
  121. await asyncio.gather(*[t for t in asyncio.all_tasks()][1:])
  122. @pytest.mark.asyncio
  123. async def test_client_good_event_handler(caplog):
  124. caplog.set_level(logging.WARNING)
  125. logger = logging.getLogger('openleadr')
  126. logger.setLevel(logging.DEBUG)
  127. client = OpenADRClient(ven_name='myven',
  128. vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b')
  129. client.add_handler('on_event', good_on_event)
  130. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  131. server.add_handler('on_create_party_registration', on_create_party_registration)
  132. print("Running server")
  133. await server.run_async()
  134. # await asyncio.sleep(0.1)
  135. print("Running client")
  136. await client.run()
  137. event_confirm_future = asyncio.get_event_loop().create_future()
  138. print("Adding event")
  139. server.add_event(ven_id='venid',
  140. event_id='test_client_good_event_handler',
  141. signal_name='simple',
  142. signal_type='level',
  143. intervals=[{'dtstart': datetime.now(timezone.utc),
  144. 'duration': timedelta(seconds=1),
  145. 'signal_payload': 1.1}],
  146. target={'ven_id': 'venid'},
  147. callback=partial(on_event_accepted, future=event_confirm_future))
  148. print("Waiting for a response to the event")
  149. result = await event_confirm_future
  150. assert result == 'optIn'
  151. assert len(caplog.records) == 0
  152. await client.stop()
  153. await server.stop()
  154. # await asyncio.sleep(1)
  155. @pytest.mark.asyncio
  156. async def test_server_warning_conflicting_poll_methods(caplog):
  157. caplog.set_level(logging.WARNING)
  158. logger = logging.getLogger('openleadr')
  159. logger.setLevel(logging.DEBUG)
  160. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  161. server.add_handler('on_poll', print)
  162. server.add_event(ven_id='venid',
  163. event_id='test_server_warning_conflicting_poll_methods',
  164. signal_name='simple',
  165. signal_type='level',
  166. intervals=[{'dtstart': datetime.now(timezone.utc),
  167. 'duration': timedelta(seconds=1),
  168. 'signal_payload': 1.1}],
  169. target={'ven_id': 'venid'},
  170. callback=on_event_accepted)
  171. assert ("You cannot use the add_event method after you assign your own on_poll "
  172. "handler. If you use your own on_poll handler, you are responsible for "
  173. "delivering events from that handler. If you want to use OpenLEADRs "
  174. "message queuing system, you should not assign an on_poll handler. "
  175. "Your Event will NOT be added.") in [record.msg for record in caplog.records]
  176. @pytest.mark.asyncio
  177. async def test_server_warning_naive_datetimes_in_event(caplog):
  178. caplog.set_level(logging.WARNING)
  179. logger = logging.getLogger('openleadr')
  180. logger.setLevel(logging.DEBUG)
  181. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  182. server.add_event(ven_id='venid',
  183. event_id='test_server_warning_naive_datetimes_in_event',
  184. signal_name='simple',
  185. signal_type='level',
  186. intervals=[{'dtstart': datetime.now(),
  187. 'duration': timedelta(seconds=1),
  188. 'signal_payload': 1.1}],
  189. target={'ven_id': 'venid'},
  190. callback=on_event_accepted)
  191. assert ("You supplied a naive datetime object to your interval's dtstart. "
  192. "This will be interpreted as a timestamp in your local timezone "
  193. "and then converted to UTC before sending. Please supply timezone-"
  194. "aware timestamps like datetime.datetime.new(timezone.utc) or "
  195. "datetime.datetime(..., tzinfo=datetime.timezone.utc)") in [record.msg for record in caplog.records]
  196. def test_event_with_wrong_response_required(caplog):
  197. now = datetime.now(timezone.utc)
  198. event = {'active_period': {'dtstart': now, 'duration': timedelta(seconds=10)},
  199. 'event_descriptor': {'event_id': 'event123',
  200. 'modification_number': 1,
  201. 'priority': 0,
  202. 'event_status': 'far',
  203. 'created_date_time': now},
  204. 'event_signals': [{'signal_name': 'simple',
  205. 'signal_type': 'level',
  206. 'intervals': [{'dtstart': now,
  207. 'duration': timedelta(seconds=10),
  208. 'signal_payload': 1}]}],
  209. 'targets': [{'ven_id': 'ven123'}],
  210. 'response_required': 'blabla'}
  211. msg = messaging.create_message('oadrDistributeEvent', events=[event])
  212. assert ("The response_required property in an Event should be "
  213. "'never' or 'always', not blabla. Changing to 'always'.") in caplog.messages
  214. message_type, message_payload= messaging.parse_message(msg)
  215. assert message_payload['events'][0]['response_required'] == 'always'
  216. def test_event_missing_created_date_time(caplog):
  217. now = datetime.now(timezone.utc)
  218. event = {'active_period': {'dtstart': now, 'duration': timedelta(seconds=10)},
  219. 'event_descriptor': {'event_id': 'event123',
  220. 'modification_number': 1,
  221. 'priority': 0,
  222. 'event_status': 'far'},
  223. 'event_signals': [{'signal_name': 'simple',
  224. 'signal_type': 'level',
  225. 'intervals': [{'dtstart': now,
  226. 'duration': timedelta(seconds=10),
  227. 'signal_payload': 1}]}],
  228. 'targets': [{'ven_id': 'ven123'}],
  229. 'response_required': 'always'}
  230. msg = messaging.create_message('oadrDistributeEvent', events=[event])
  231. assert ("Your event descriptor did not contain a created_date_time. "
  232. "This will be automatically added.") in caplog.messages
  233. def test_event_incongruent_targets(caplog):
  234. now = datetime.now(timezone.utc)
  235. event = {'active_period': {'dtstart': now, 'duration': timedelta(seconds=10)},
  236. 'event_descriptor': {'event_id': 'event123',
  237. 'modification_number': 1,
  238. 'priority': 0,
  239. 'event_status': 'far',
  240. 'created_date_time': now},
  241. 'event_signals': [{'signal_name': 'simple',
  242. 'signal_type': 'level',
  243. 'intervals': [{'dtstart': now,
  244. 'duration': timedelta(seconds=10),
  245. 'signal_payload': 1}]}],
  246. 'targets': [{'ven_id': 'ven123'}],
  247. 'targets_by_type': {'ven_id': ['ven456']},
  248. 'response_required': 'always'}
  249. with pytest.raises(ValueError) as err:
  250. msg = messaging.create_message('oadrDistributeEvent', events=[event])
  251. assert str(err.value) == ("You assigned both 'targets' and 'targets_by_type' in your event, "
  252. "but the two were not consistent with each other. "
  253. f"You supplied 'targets' = {event['targets']} and "
  254. f"'targets_by_type' = {event['targets_by_type']}")
  255. def test_event_only_targets_by_type(caplog):
  256. now = datetime.now(timezone.utc)
  257. event = {'active_period': {'dtstart': now, 'duration': timedelta(seconds=10)},
  258. 'event_descriptor': {'event_id': 'event123',
  259. 'modification_number': 1,
  260. 'priority': 0,
  261. 'event_status': 'far',
  262. 'created_date_time': now},
  263. 'event_signals': [{'signal_name': 'simple',
  264. 'signal_type': 'level',
  265. 'intervals': [{'dtstart': now,
  266. 'duration': timedelta(seconds=10),
  267. 'signal_payload': 1}]}],
  268. 'targets_by_type': {'ven_id': ['ven456']},
  269. 'response_required': 'always'}
  270. msg = messaging.create_message('oadrDistributeEvent', events=[event])
  271. message_type, message_payload = messaging.parse_message(msg)
  272. assert message_payload['events'][0]['targets'] == [{'ven_id': 'ven456'}]
  273. @pytest.mark.asyncio
  274. async def test_client_warning_no_update_event_handler(caplog):
  275. caplog.set_level(logging.WARNING)
  276. logger = logging.getLogger('openleadr')
  277. logger.setLevel(logging.DEBUG)
  278. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  279. server.add_handler('on_create_party_registration', on_create_party_registration)
  280. event_accepted_future = asyncio.get_event_loop().create_future()
  281. server.add_event(ven_id='venid',
  282. event_id='test_client_warning_no_update_event_handler',
  283. signal_name='simple',
  284. signal_type='level',
  285. intervals=[{'dtstart': datetime.now(timezone.utc),
  286. 'duration': timedelta(seconds=1),
  287. 'signal_payload': 1.1}],
  288. target={'ven_id': 'venid'},
  289. callback=event_accepted_future)
  290. client = OpenADRClient(ven_name='myven',
  291. vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b')
  292. client.add_handler('on_event', good_on_event)
  293. print("Starting server")
  294. await server.run()
  295. await client.run()
  296. print("Waiting for first event to be accepted...")
  297. await event_accepted_future
  298. # Manually update the event
  299. server.events['venid'][0].event_descriptor.modification_number = 1
  300. server.events_updated['venid'] = True
  301. await asyncio.sleep(1)
  302. assert ("You should implement your own on_update_event handler. This handler receives "
  303. "an Event dict and should return either 'optIn' or 'optOut' based on your "
  304. "choice. Will re-use the previous opt status for this event_id for now") in [record.msg for record in caplog.records]
  305. await client.stop()
  306. await server.stop()
  307. @pytest.mark.asyncio
  308. async def test_server_add_event_with_wrong_callback_signature(caplog):
  309. def dummy_callback(some_param):
  310. pass
  311. caplog.set_level(logging.WARNING)
  312. logger = logging.getLogger('openleadr')
  313. logger.setLevel(logging.DEBUG)
  314. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  315. with pytest.raises(ValueError) as err:
  316. server.add_event(ven_id='venid',
  317. event_id='test_server_add_event_with_wrong_callback_signature',
  318. signal_name='simple',
  319. signal_type='level',
  320. intervals=[{'dtstart': datetime.now(timezone.utc),
  321. 'duration': timedelta(seconds=1),
  322. 'signal_payload': 1.1}],
  323. target={'ven_id': 'venid'},
  324. callback=dummy_callback)
  325. @pytest.mark.asyncio
  326. async def test_server_add_event_with_no_callback(caplog):
  327. def dummy_callback(some_param):
  328. pass
  329. caplog.set_level(logging.WARNING)
  330. logger = logging.getLogger('openleadr')
  331. logger.setLevel(logging.DEBUG)
  332. server = OpenADRServer(vtn_id='myvtn')
  333. server.add_event(ven_id='venid',
  334. event_id='test_server_add_event_with_no_callback',
  335. signal_name='simple',
  336. signal_type='level',
  337. intervals=[{'dtstart': datetime.now(timezone.utc),
  338. 'duration': timedelta(seconds=1),
  339. 'signal_payload': 1.1}],
  340. target={'ven_id': 'venid'})
  341. assert ("You did not provide a 'callback', which means you won't know if the "
  342. "VEN will opt in or opt out of your event. You should consider adding "
  343. "a callback for this.") in caplog.messages
  344. @pytest.mark.asyncio
  345. async def test_server_add_event_with_no_callback_response_never_required(caplog):
  346. caplog.set_level(logging.WARNING)
  347. logger = logging.getLogger('openleadr')
  348. logger.setLevel(logging.DEBUG)
  349. server = OpenADRServer(vtn_id='myvtn')
  350. server.add_event(ven_id='venid',
  351. event_id='test_server_add_event_with_no_callback_response_never_required',
  352. signal_name='simple',
  353. signal_type='level',
  354. intervals=[{'dtstart': datetime.now(timezone.utc),
  355. 'duration': timedelta(seconds=1),
  356. 'signal_payload': 1.1}],
  357. target={'ven_id': 'venid'},
  358. response_required='never')
  359. await server.run()
  360. await server.stop()
  361. assert ("You did not provide a 'callback', which means you won't know if the "
  362. "VEN will opt in or opt out of your event. You should consider adding "
  363. "a callback for this.") not in caplog.messages