test_event_warnings_errors.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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. signal_name='simple',
  37. signal_type='level',
  38. intervals=[{'dtstart': datetime.now(timezone.utc),
  39. 'duration': timedelta(seconds=1),
  40. 'signal_payload': 1.1}],
  41. target={'ven_id': 'venid'},
  42. callback=partial(on_event_accepted, future=event_confirm_future))
  43. print("Waiting for a response to the event")
  44. result = await event_confirm_future
  45. assert result == 'optOut'
  46. assert ("You should implement your own on_event handler. This handler receives "
  47. "an Event dict and should return either 'optIn' or 'optOut' based on your "
  48. "choice. Will opt out of the event for now.") in [rec.message for rec in caplog.records]
  49. await client.stop()
  50. await server.stop()
  51. await asyncio.gather(*[t for t in asyncio.all_tasks()][1:])
  52. @pytest.mark.asyncio
  53. async def test_client_faulty_event_handler(caplog):
  54. caplog.set_level(logging.WARNING)
  55. logger = logging.getLogger('openleadr')
  56. logger.setLevel(logging.DEBUG)
  57. client = OpenADRClient(ven_name='myven',
  58. vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b')
  59. client.add_handler('on_event', faulty_on_event)
  60. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  61. server.add_handler('on_create_party_registration', on_create_party_registration)
  62. print("Running server")
  63. await server.run_async()
  64. await asyncio.sleep(0.1)
  65. print("Running client")
  66. await client.run()
  67. event_confirm_future = asyncio.get_event_loop().create_future()
  68. print("Adding event")
  69. server.add_event(ven_id='venid',
  70. signal_name='simple',
  71. signal_type='level',
  72. intervals=[{'dtstart': datetime.now(timezone.utc),
  73. 'duration': timedelta(seconds=1),
  74. 'signal_payload': 1.1}],
  75. target={'ven_id': 'venid'},
  76. callback=partial(on_event_accepted, future=event_confirm_future))
  77. print("Waiting for a response to the event")
  78. result = await event_confirm_future
  79. assert result == 'optOut'
  80. assert ("Your on_event or on_update_event handler must return 'optIn' or 'optOut'; "
  81. f"you supplied {None}. Please fix your on_event handler.") in [rec.message for rec in caplog.records]
  82. await client.stop()
  83. await server.stop()
  84. await asyncio.gather(*[t for t in asyncio.all_tasks()][1:])
  85. @pytest.mark.asyncio
  86. async def test_client_exception_event_handler(caplog):
  87. caplog.set_level(logging.WARNING)
  88. logger = logging.getLogger('openleadr')
  89. logger.setLevel(logging.DEBUG)
  90. client = OpenADRClient(ven_name='myven',
  91. vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b')
  92. client.add_handler('on_event', broken_on_event)
  93. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  94. server.add_handler('on_create_party_registration', on_create_party_registration)
  95. print("Running server")
  96. await server.run_async()
  97. await asyncio.sleep(0.1)
  98. print("Running client")
  99. await client.run()
  100. event_confirm_future = asyncio.get_event_loop().create_future()
  101. print("Adding event")
  102. server.add_event(ven_id='venid',
  103. signal_name='simple',
  104. signal_type='level',
  105. intervals=[{'dtstart': datetime.now(timezone.utc),
  106. 'duration': timedelta(seconds=1),
  107. 'signal_payload': 1.1}],
  108. target={'ven_id': 'venid'},
  109. callback=partial(on_event_accepted, future=event_confirm_future))
  110. print("Waiting for a response to the event")
  111. result = await event_confirm_future
  112. assert result == 'optOut'
  113. err = KeyError("BOOM")
  114. assert ("Your on_event handler encountered an error. Will Opt Out of the event. "
  115. f"The error was {err.__class__.__name__}: {str(err)}") in [rec.message for rec in caplog.records]
  116. await client.stop()
  117. await server.stop()
  118. await asyncio.gather(*[t for t in asyncio.all_tasks()][1:])
  119. @pytest.mark.asyncio
  120. async def test_client_good_event_handler(caplog):
  121. caplog.set_level(logging.WARNING)
  122. logger = logging.getLogger('openleadr')
  123. logger.setLevel(logging.DEBUG)
  124. client = OpenADRClient(ven_name='myven',
  125. vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b')
  126. client.add_handler('on_event', good_on_event)
  127. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  128. server.add_handler('on_create_party_registration', on_create_party_registration)
  129. print("Running server")
  130. await server.run_async()
  131. await asyncio.sleep(0.1)
  132. print("Running client")
  133. await client.run()
  134. event_confirm_future = asyncio.get_event_loop().create_future()
  135. print("Adding event")
  136. server.add_event(ven_id='venid',
  137. signal_name='simple',
  138. signal_type='level',
  139. intervals=[{'dtstart': datetime.now(timezone.utc),
  140. 'duration': timedelta(seconds=1),
  141. 'signal_payload': 1.1}],
  142. target={'ven_id': 'venid'},
  143. callback=partial(on_event_accepted, future=event_confirm_future))
  144. print("Waiting for a response to the event")
  145. result = await event_confirm_future
  146. assert result == 'optIn'
  147. assert len(caplog.records) == 0
  148. await client.stop()
  149. await server.stop()
  150. await asyncio.sleep(1)
  151. @pytest.mark.asyncio
  152. async def test_server_warning_conflicting_poll_methods(caplog):
  153. caplog.set_level(logging.WARNING)
  154. logger = logging.getLogger('openleadr')
  155. logger.setLevel(logging.DEBUG)
  156. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  157. server.add_handler('on_poll', print)
  158. server.add_event(ven_id='venid',
  159. signal_name='simple',
  160. signal_type='level',
  161. intervals=[{'dtstart': datetime.now(timezone.utc),
  162. 'duration': timedelta(seconds=1),
  163. 'signal_payload': 1.1}],
  164. target={'ven_id': 'venid'},
  165. callback=on_event_accepted)
  166. assert ("You cannot use the add_event method after you assign your own on_poll "
  167. "handler. If you use your own on_poll handler, you are responsible for "
  168. "delivering events from that handler. If you want to use OpenLEADRs "
  169. "message queuing system, you should not assign an on_poll handler. "
  170. "Your Event will NOT be added.") in [record.msg for record in caplog.records]
  171. @pytest.mark.asyncio
  172. async def test_server_warning_naive_datetimes_in_event(caplog):
  173. caplog.set_level(logging.WARNING)
  174. logger = logging.getLogger('openleadr')
  175. logger.setLevel(logging.DEBUG)
  176. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  177. server.add_event(ven_id='venid',
  178. signal_name='simple',
  179. signal_type='level',
  180. intervals=[{'dtstart': datetime.now(),
  181. 'duration': timedelta(seconds=1),
  182. 'signal_payload': 1.1}],
  183. target={'ven_id': 'venid'},
  184. callback=on_event_accepted)
  185. assert ("You supplied a naive datetime object to your interval's dtstart. "
  186. "This will be interpreted as a timestamp in your local timezone "
  187. "and then converted to UTC before sending. Please supply timezone-"
  188. "aware timestamps like datetime.datetime.new(timezone.utc) or "
  189. "datetime.datetime(..., tzinfo=datetime.timezone.utc)") in [record.msg for record in caplog.records]
  190. def test_event_with_wrong_response_required(caplog):
  191. now = datetime.now(timezone.utc)
  192. event = {'active_period': {'dtstart': now, 'duration': timedelta(seconds=10)},
  193. 'event_descriptor': {'event_id': 'event123',
  194. 'modification_number': 1,
  195. 'priority': 0,
  196. 'event_status': 'far',
  197. 'created_date_time': now},
  198. 'event_signals': [{'signal_name': 'simple',
  199. 'signal_type': 'level',
  200. 'intervals': [{'dtstart': now,
  201. 'duration': timedelta(seconds=10),
  202. 'signal_payload': 1}]}],
  203. 'targets': [{'ven_id': 'ven123'}],
  204. 'response_required': 'blabla'}
  205. msg = messaging.create_message('oadrDistributeEvent', events=[event])
  206. assert ("The response_required property in an Event should be "
  207. "'never' or 'always', not blabla. Changing to 'always'.") in caplog.messages
  208. message_type, message_payload= messaging.parse_message(msg)
  209. assert message_payload['events'][0]['response_required'] == 'always'
  210. def test_event_missing_created_date_time(caplog):
  211. now = datetime.now(timezone.utc)
  212. event = {'active_period': {'dtstart': now, 'duration': timedelta(seconds=10)},
  213. 'event_descriptor': {'event_id': 'event123',
  214. 'modification_number': 1,
  215. 'priority': 0,
  216. 'event_status': 'far'},
  217. 'event_signals': [{'signal_name': 'simple',
  218. 'signal_type': 'level',
  219. 'intervals': [{'dtstart': now,
  220. 'duration': timedelta(seconds=10),
  221. 'signal_payload': 1}]}],
  222. 'targets': [{'ven_id': 'ven123'}],
  223. 'response_required': 'always'}
  224. msg = messaging.create_message('oadrDistributeEvent', events=[event])
  225. assert ("Your event descriptor did not contain a created_date_time. "
  226. "This will be automatically added.") in caplog.messages
  227. def test_event_incongruent_targets(caplog):
  228. now = datetime.now(timezone.utc)
  229. event = {'active_period': {'dtstart': now, 'duration': timedelta(seconds=10)},
  230. 'event_descriptor': {'event_id': 'event123',
  231. 'modification_number': 1,
  232. 'priority': 0,
  233. 'event_status': 'far',
  234. 'created_date_time': now},
  235. 'event_signals': [{'signal_name': 'simple',
  236. 'signal_type': 'level',
  237. 'intervals': [{'dtstart': now,
  238. 'duration': timedelta(seconds=10),
  239. 'signal_payload': 1}]}],
  240. 'targets': [{'ven_id': 'ven123'}],
  241. 'targets_by_type': {'ven_id': ['ven456']},
  242. 'response_required': 'always'}
  243. with pytest.raises(ValueError) as err:
  244. msg = messaging.create_message('oadrDistributeEvent', events=[event])
  245. assert str(err.value) == ("You assigned both 'targets' and 'targets_by_type' in your event, "
  246. "but the two were not consistent with each other. "
  247. f"You supplied 'targets' = {event['targets']} and "
  248. f"'targets_by_type' = {event['targets_by_type']}")
  249. def test_event_only_targets_by_type(caplog):
  250. now = datetime.now(timezone.utc)
  251. event = {'active_period': {'dtstart': now, 'duration': timedelta(seconds=10)},
  252. 'event_descriptor': {'event_id': 'event123',
  253. 'modification_number': 1,
  254. 'priority': 0,
  255. 'event_status': 'far',
  256. 'created_date_time': now},
  257. 'event_signals': [{'signal_name': 'simple',
  258. 'signal_type': 'level',
  259. 'intervals': [{'dtstart': now,
  260. 'duration': timedelta(seconds=10),
  261. 'signal_payload': 1}]}],
  262. 'targets_by_type': {'ven_id': ['ven456']},
  263. 'response_required': 'always'}
  264. msg = messaging.create_message('oadrDistributeEvent', events=[event])
  265. message_type, message_payload = messaging.parse_message(msg)
  266. assert message_payload['events'][0]['targets'] == [{'ven_id': 'ven456'}]
  267. @pytest.mark.asyncio
  268. async def test_client_warning_no_update_event_handler(caplog):
  269. caplog.set_level(logging.WARNING)
  270. logger = logging.getLogger('openleadr')
  271. logger.setLevel(logging.DEBUG)
  272. server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=timedelta(seconds=1))
  273. server.add_handler('on_create_party_registration', on_create_party_registration)
  274. server.add_event(ven_id='venid',
  275. signal_name='simple',
  276. signal_type='level',
  277. intervals=[{'dtstart': datetime.now(timezone.utc),
  278. 'duration': timedelta(seconds=1),
  279. 'signal_payload': 1.1}],
  280. target={'ven_id': 'venid'},
  281. callback=on_event_accepted)
  282. client = OpenADRClient(ven_name='myven',
  283. vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b')
  284. client.add_handler('on_event', good_on_event)
  285. await server.run_async()
  286. await asyncio.sleep(0.5)
  287. await client.run()
  288. await asyncio.sleep(2)
  289. assert ("You should implement your own on_update_event handler. This handler receives "
  290. "an Event dict and should return either 'optIn' or 'optOut' based on your "
  291. "choice. Will re-use the previous opt status for this event_id for now") in [record.msg for record in caplog.records]
  292. await client.stop()
  293. await server.stop()
  294. await asyncio.gather(*[t for t in asyncio.all_tasks()][1:])