async def test_update_reports(): """ Tests the timely delivery of requested reports """ # Create a server logger = logging.getLogger('openleadr') logger.setLevel(logging.DEBUG) loop = asyncio.get_event_loop() server = OpenADRServer(vtn_id='testvtn') register_report_future_1 = loop.create_future() register_report_future_2 = loop.create_future() register_report_futures = [ register_report_future_1, register_report_future_2 ] receive_report_future_1 = loop.create_future() receive_report_future_2 = loop.create_future() receive_report_future_3 = loop.create_future() receive_report_future_4 = loop.create_future() receive_report_futures = [ receive_report_future_1, receive_report_future_2, receive_report_future_3, receive_report_future_4 ] server.add_handler( 'on_register_report', partial(on_register_report, futures=register_report_futures, receive_futures=receive_report_futures)) party_future = loop.create_future() server.add_handler( 'on_create_party_registration', partial(on_create_party_registration, future=party_future)) # Create a client client = OpenADRClient( ven_name='myven', vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b') # Add 4 reports future_1 = loop.create_future() client.add_report(callback=partial(collect_data, future=future_1), report_specifier_id='PowerReport', resource_id='Device001', measurement='power_real', sampling_rate=timedelta(seconds=2), unit='W') future_2 = loop.create_future() client.add_report(callback=partial(collect_data, future=future_2), report_specifier_id='PowerReport', resource_id='Device002', measurement='power_real', sampling_rate=timedelta(seconds=2), unit='W') future_3 = loop.create_future() client.add_report(callback=partial(collect_data, future=future_3), report_specifier_id='VoltageReport', resource_id='Device001', measurement='voltage', sampling_rate=timedelta(seconds=2), unit='V') future_4 = loop.create_future() client.add_report(callback=partial(collect_data, future=future_4), report_specifier_id='VoltageReport', resource_id='Device002', measurement='voltage', sampling_rate=timedelta(seconds=2), unit='V') assert len(client.reports) == 2 asyncio.create_task(server.run_async()) await asyncio.sleep(1) # Run the client asynchronously print("Running the client") asyncio.create_task(client.run()) print("Awaiting party future") await party_future print("Awaiting report futures") await asyncio.gather(register_report_future_1, register_report_future_2) await asyncio.sleep(0.1) assert len(server.services['report_service'].report_callbacks) == 4 print("Awaiting data collection futures") await future_1 await future_2 await future_3 await future_4 print("Awaiting update report futures") await asyncio.gather(receive_report_future_1, receive_report_future_2, receive_report_future_3, receive_report_future_4) print("Done gathering") assert receive_report_future_1.result()[0][1] == future_1.result() assert receive_report_future_2.result()[0][1] == future_2.result() assert receive_report_future_3.result()[0][1] == future_3.result() assert receive_report_future_4.result()[0][1] == future_4.result() await client.stop() await server.stop()
'response_description': 'OK', 'request_id': payload['request_id'] }, 'ven_id': ven_id, 'registration_id': registration_id, 'profiles': [{ 'profile_name': '2.0b', 'transports': { 'transport_name': 'simpleHttp' } }], 'requested_oadr_poll_freq': timedelta(seconds=10) } return 'oadrCreatedPartyRegistration', payload server = OpenADRServer(vtn_id=VTN_ID, http_port=SERVER_PORT) server.add_handler('on_create_party_registration', _on_create_party_registration) server.add_handler('on_poll', _on_poll) @pytest.fixture async def start_server(): await server.run_async() yield await server.stop()
async def test_event_external_polling_function(): async def opt_in_to_event(event, future=None): if future: future.set_result(True) return 'optIn' async def on_update_event(event, future=None): if future: future.set_result(event) return 'optIn' async def on_poll(ven_id, future=None): if future and not future.done(): future.set_result(True) return objects.Event( event_descriptor=objects.EventDescriptor( event_id='event001', modification_number=0, event_status='far', market_context='http://marketcontext01'), event_signals=[ objects.EventSignal( signal_id='signal001', signal_type='level', signal_name='simple', intervals=[ objects.Interval( dtstart=now, duration=datetime.timedelta(minutes=10), signal_payload=1) ]), objects.EventSignal( signal_id='signal002', signal_type='price', signal_name='ELECTRICITY_PRICE', intervals=[ objects.Interval( dtstart=now, duration=datetime.timedelta(minutes=10), signal_payload=1) ]) ], targets=[objects.Target(ven_id=ven_id)]) else: print("Returning None") return None loop = asyncio.get_event_loop() now = datetime.datetime.now(datetime.timezone.utc) server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=datetime.timedelta(seconds=1)) server.add_handler('on_create_party_registration', on_create_party_registration) poll_fut = loop.create_future() server.add_handler('on_poll', partial(on_poll, future=poll_fut)) await server.run() client = OpenADRClient( ven_name='ven123', vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b') fut = loop.create_future() client.add_handler('on_event', partial(opt_in_to_event, future=fut)) await client.run() await fut assert len(client.responded_events) == 1 assert len(client.received_events) == 1 await server.stop() await client.stop()
async def test_multiple_events_in_queue(): now = datetime.datetime.now(datetime.timezone.utc) server = OpenADRServer(vtn_id='myvtn') server.add_handler('on_create_party_registration', on_create_party_registration) loop = asyncio.get_event_loop() event_1_callback_future = loop.create_future() event_2_callback_future = loop.create_future() server.add_event(ven_id='ven123', signal_name='simple', signal_type='level', intervals=[ objects.Interval( dtstart=now, duration=datetime.timedelta(seconds=1), signal_payload=1) ], callback=event_1_callback_future) await server.run() on_event_future = loop.create_future() client = OpenADRClient( ven_name='ven123', vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b') await client.create_party_registration() response_type, response_payload = await client.request_event() assert response_type == 'oadrDistributeEvent' events = response_payload['events'] assert len(events) == 1 event_id = events[0]['event_descriptor']['event_id'] request_id = response_payload['request_id'] await client.created_event(request_id=request_id, event_id=event_id, opt_type='optIn', modification_number=0) server.add_event(ven_id='ven123', signal_name='simple', signal_type='level', intervals=[ objects.Interval( dtstart=now + datetime.timedelta(seconds=1), duration=datetime.timedelta(seconds=1), signal_payload=1) ], callback=event_2_callback_future) response_type, response_payload = await client.request_event() assert response_type == 'oadrDistributeEvent' events = response_payload['events'] # Assert that we still have two events in the response assert len(events) == 2 # Wait one second and retrieve the events again await asyncio.sleep(1) response_type, response_payload = await client.request_event() assert response_type == 'oadrDistributeEvent' events = response_payload['events'] assert len(events) == 2 assert events[1]['event_descriptor']['event_status'] == 'completed' response_type, response_payload = await client.request_event() assert response_type == 'oadrDistributeEvent' events = response_payload['events'] assert len(events) == 1 await asyncio.sleep(1) response_type, response_payload = await client.request_event() assert response_type == 'oadrDistributeEvent' response_type, response_payload = await client.request_event() assert response_type == 'oadrResponse' await server.stop()
ven_id, 'registration_id': registration_id, 'profiles': [{ 'profile_name': '2.0b', 'transports': { 'transport_name': 'simpleHttp' } }], 'requested_oadr_poll_freq': timedelta(seconds=10) } return 'oadrCreatedPartyRegistration', payload server = OpenADRServer(vtn_id=VTN_ID) server.add_handler('on_create_party_registration', _on_create_party_registration) server.add_handler('on_poll', _on_poll) @pytest.fixture async def start_server(): runner = web.AppRunner(server.app) await runner.setup() site = web.TCPSite(runner, 'localhost', SERVER_PORT) await site.start() print("SERVER IS NOW RUNNING") yield print("SERVER IS NOW STOPPING") await runner.cleanup()
async def test_report_registration_broken_handlers_raw_message(caplog): msg = """<?xml version="1.0" encoding="UTF-8" standalone="no" ?> <p1:oadrPayload xmlns:p1="http://openadr.org/oadr-2.0b/2012/07"> <p1:oadrSignedObject> <p1:oadrRegisterReport xmlns:p3="http://docs.oasis-open.org/ns/energyinterop/201110" p3:schemaVersion="2.0b" xmlns:p2="http://docs.oasis-open.org/ns/energyinterop/201110/payloads"> <p2:requestID>B8A6E0D2D4</p2:requestID> <p1:oadrReport xmlns:p3="urn:ietf:params:xml:ns:icalendar-2.0" xmlns:p4="http://docs.oasis-open.org/ns/energyinterop/201110"> <p3:duration> <p3:duration>PT120M</p3:duration> </p3:duration> <p1:oadrReportDescription xmlns:p4="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:p5="http://docs.oasis-open.org/ns/emix/2011/06/power" xmlns:p6="http://docs.oasis-open.org/ns/emix/2011/06"> <p4:rID>rid_energy_4184bb93</p4:rID> <p4:reportDataSource> <p4:resourceID>DEVICE1</p4:resourceID> </p4:reportDataSource> <p4:reportType>reading</p4:reportType> <p5:energyReal xmlns:p6="http://docs.oasis-open.org/ns/emix/2011/06/siscale"> <p5:itemDescription/> <p5:itemUnits>Wh</p5:itemUnits> <p6:siScaleCode>none</p6:siScaleCode> </p5:energyReal> <p4:readingType>Direct Read</p4:readingType> <p6:marketContext/> <p1:oadrSamplingRate> <p1:oadrMinPeriod>PT1M</p1:oadrMinPeriod> <p1:oadrMaxPeriod>PT1M</p1:oadrMaxPeriod> <p1:oadrOnChange>false</p1:oadrOnChange> </p1:oadrSamplingRate> </p1:oadrReportDescription> <p1:oadrReportDescription xmlns:p4="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:p5="http://docs.oasis-open.org/ns/emix/2011/06/power" xmlns:p6="http://docs.oasis-open.org/ns/emix/2011/06"> <p4:rID>rid_power_4184bb93</p4:rID> <p4:reportDataSource> <p4:resourceID>DEVICE1</p4:resourceID> </p4:reportDataSource> <p4:reportType>reading</p4:reportType> <p5:powerReal xmlns:p6="http://docs.oasis-open.org/ns/emix/2011/06/siscale"> <p5:itemDescription/> <p5:itemUnits>W</p5:itemUnits> <p6:siScaleCode>none</p6:siScaleCode> <p5:powerAttributes> <p5:hertz>60</p5:hertz> <p5:voltage>120</p5:voltage> <p5:ac>true</p5:ac> </p5:powerAttributes> </p5:powerReal> <p4:readingType>Direct Read</p4:readingType> <p6:marketContext/> <p1:oadrSamplingRate> <p1:oadrMinPeriod>PT1M</p1:oadrMinPeriod> <p1:oadrMaxPeriod>PT1M</p1:oadrMaxPeriod> <p1:oadrOnChange>false</p1:oadrOnChange> </p1:oadrSamplingRate> </p1:oadrReportDescription> <p4:reportRequestID>0</p4:reportRequestID> <p4:reportSpecifierID>DEMO_TELEMETRY_USAGE</p4:reportSpecifierID> <p4:reportName>METADATA_TELEMETRY_USAGE</p4:reportName> <p4:createdDateTime>2020-12-15T14:10:32Z</p4:createdDateTime> </p1:oadrReport> <p3:venID>ven_id</p3:venID> </p1:oadrRegisterReport> </p1:oadrSignedObject> </p1:oadrPayload>""" server = OpenADRServer(vtn_id='myvtn') await server.run() # Test with no configured callbacks from aiohttp import ClientSession async with ClientSession() as session: async with session.post("http://localhost:8080/OpenADR2/Simple/2.0b/EiReport", headers={'content-type': 'Application/XML'}, data=msg.encode('utf-8')) as resp: assert resp.status == 200 # Test with a working callback def report_callback(data): print(data) def working_on_register_report(ven_id, resource_id, measurement, unit, scale, min_sampling_interval, max_sampling_interval): return report_callback, min_sampling_interval server.add_handler('on_register_report', working_on_register_report) async with ClientSession() as session: async with session.post("http://localhost:8080/OpenADR2/Simple/2.0b/EiReport", headers={'content-type': 'Application/XML'}, data=msg.encode('utf-8')) as resp: assert resp.status == 200 # Test with a broken callback def broken_on_register_report(ven_id, resource_id, measurement, unit, scale, min_sampling_interval, max_sampling_interval): return "Hello There" server.add_handler('on_register_report', broken_on_register_report) async with ClientSession() as session: async with session.post("http://localhost:8080/OpenADR2/Simple/2.0b/EiReport", headers={'content-type': 'Application/XML'}, data=msg.encode('utf-8')) as resp: assert resp.status == 200 # assert "Your on_register_report handler must return a tuple; it returned 'Hello There' (str)." in caplog.messages # Test with a broken full callback def broken_on_register_report_full(report): return "Hello There Again" server.add_handler('on_register_report', broken_on_register_report_full) async with ClientSession() as session: async with session.post("http://localhost:8080/OpenADR2/Simple/2.0b/EiReport", headers={'content-type': 'Application/XML'}, data=msg.encode('utf-8')) as resp: assert resp.status == 200 assert f"Your on_register_report handler must return a list of tuples or None; it returned 'Hello There Again' (str)." in caplog.messages await server.stop()
async def test_different_on_register_report_handlers(caplog): def on_create_party_registration(registration_info): return 'ven123', 'reg123' def get_value(): return 123.456 def report_callback(data): pass def on_register_report_returning_none(ven_id, resource_id, measurement, unit, scale, min_sampling_interval, max_sampling_interval): return None def on_register_report_returning_string(ven_id, resource_id, measurement, unit, scale, min_sampling_interval, max_sampling_interval): return "Hello There" def on_register_report_returning_uncallable_first_element(ven_id, resource_id, measurement, unit, scale, min_sampling_interval, max_sampling_interval): return ("Hello", "There") def on_register_report_returning_non_datetime_second_element(ven_id, resource_id, measurement, unit, scale, min_sampling_interval, max_sampling_interval): return (report_callback, "Hello There") def on_register_report_returning_non_datetime_third_element(ven_id, resource_id, measurement, unit, scale, min_sampling_interval, max_sampling_interval): return (report_callback, timedelta(minutes=10), "Hello There") def on_register_report_returning_too_long_tuple(ven_id, resource_id, measurement, unit, scale, min_sampling_interval, max_sampling_interval): return (report_callback, timedelta(minutes=10), timedelta(minutes=10), "Hello") def on_register_report_full_returning_string(report): return "Hello There" def on_register_report_full_returning_list_of_strings(report): return ["Hello", "There"] def on_register_report_full_returning_list_of_tuples_of_wrong_length(report): return [("Hello", "There")] def on_register_report_full_returning_list_of_tuples_with_no_callable(report): return [("Hello", "There", "World")] def on_register_report_full_returning_list_of_tuples_with_no_timedelta(report): return [(report_callback, "Hello There")] server = OpenADRServer(vtn_id='myvtn') server.add_handler('on_create_party_registration', on_create_party_registration) client = OpenADRClient(ven_name='myven', vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b') client.add_report(resource_id='Device001', measurement='voltage', sampling_rate=timedelta(minutes=10), callback=get_value) await server.run() await client.create_party_registration() assert client.ven_id == 'ven123' caplog.clear() await client.register_reports(client.reports) assert len(client.report_requests) == 0 messages = [rec.message for rec in caplog.records if rec.levelno == logging.ERROR] assert len(messages) == 0 caplog.clear() server.add_handler('on_register_report', on_register_report_returning_none) await client.register_reports(client.reports) assert len(client.report_requests) == 0 messages = [rec.message for rec in caplog.records if rec.levelno == logging.ERROR] assert len(messages) == 0 caplog.clear() server.add_handler('on_register_report', on_register_report_returning_string) await client.register_reports(client.reports) assert len(client.report_requests) == 0 assert "Your on_register_report handler must return a tuple or None; it returned 'Hello There' (str)." in caplog.messages caplog.clear() server.add_handler('on_register_report', on_register_report_returning_uncallable_first_element) await client.register_reports(client.reports) assert len(client.report_requests) == 0 assert(f"Your on_register_report handler did not return the correct tuple. " "It should return a (callback, sampling_interval) or " "(callback, sampling_interval, reporting_interval) tuple, where " "the callback is a callable function or coroutine, and " "sampling_interval and reporting_interval are of type datetime.timedelta. " "It returned: '('Hello', 'There')'. The first element was not callable.") in caplog.messages caplog.clear() server.add_handler('on_register_report', on_register_report_returning_non_datetime_second_element) await client.register_reports(client.reports) assert len(client.report_requests) == 0 assert (f"Your on_register_report handler did not return the correct tuple. " "It should return a (callback, sampling_interval) or " "(callback, sampling_interval, reporting_interval) tuple, where " "sampling_interval and reporting_interval are of type datetime.timedelta. " f"It returned: '{(report_callback, 'Hello There')}'. The second element was not of type timedelta.") in caplog.messages caplog.clear() server.add_handler('on_register_report', on_register_report_returning_non_datetime_third_element) await client.register_reports(client.reports) assert len(client.report_requests) == 0 assert ("Your on_register_report handler did not return the correct tuple. " "It should return a (callback, sampling_interval) or " "(callback, sampling_interval, reporting_interval) tuple, where " "sampling_interval and reporting_interval are of type datetime.timedelta. " f"It returned: '{(report_callback, timedelta(minutes=10), 'Hello There')}'. The third element was not of type timedelta.") in caplog.messages caplog.clear() server.add_handler('on_register_report', on_register_report_returning_too_long_tuple) await client.register_reports(client.reports) assert len(client.report_requests) == 0 assert ("Your on_register_report handler returned a tuple of the wrong length. " "It should be 2 or 3. " f"It returned: '{(report_callback, timedelta(minutes=10), timedelta(minutes=10), 'Hello')}'.") in caplog.messages caplog.clear() server.add_handler('on_register_report', on_register_report_full_returning_string) await client.register_reports(client.reports) assert len(client.report_requests) == 0 assert "Your on_register_report handler must return a list of tuples or None; it returned 'Hello There' (str)." in caplog.messages caplog.clear() server.add_handler('on_register_report', on_register_report_full_returning_list_of_strings) await client.register_reports(client.reports) assert len(client.report_requests) == 0 assert ("Your on_register_report handler must return a list of tuples or None; " f"The first item from the list was 'Hello' (str).") in caplog.messages caplog.clear() server.add_handler('on_register_report', on_register_report_full_returning_list_of_tuples_of_wrong_length) await client.register_reports(client.reports) assert len(client.report_requests) == 0 assert ("Your on_register_report handler returned tuples of the wrong length. " "It should be 3 or 4. It returned: '('Hello', 'There')'.") in caplog.messages caplog.clear() server.add_handler('on_register_report', on_register_report_full_returning_list_of_tuples_with_no_callable) await client.register_reports(client.reports) assert len(client.report_requests) == 0 assert ("Your on_register_report handler did not return the correct tuple. " "It should return a list of (r_id, callback, sampling_interval) or " "(r_id, callback, sampling_interval, reporting_interval) tuples, " "where the r_id is a string, callback is a callable function or " "coroutine, and sampling_interval and reporting_interval are of " "type datetime.timedelta. It returned: '('Hello', 'There', 'World')'. " "The second element was not callable.") in caplog.messages caplog.clear() server.add_handler('on_register_report', on_register_report_full_returning_list_of_tuples_with_no_timedelta) await client.register_reports(client.reports) assert len(client.report_requests) == 0 assert ("Your on_register_report handler returned tuples of the wrong length. " f"It should be 3 or 4. It returned: '({report_callback}, 'Hello There')'.") in caplog.messages await server.stop() await client.stop()