def test_event_only_targets_by_type(caplog): now = datetime.now(timezone.utc) event = { 'active_period': { 'dtstart': now, 'duration': timedelta(seconds=10) }, 'event_descriptor': { 'event_id': 'event123', 'modification_number': 1, 'priority': 0, 'event_status': 'far', 'created_date_time': now }, 'event_signals': [{ 'signal_name': 'simple', 'signal_type': 'level', 'intervals': [{ 'dtstart': now, 'duration': timedelta(seconds=10), 'signal_payload': 1 }] }], 'targets_by_type': { 'ven_id': ['ven456'] }, 'response_required': 'always' } msg = messaging.create_message('oadrDistributeEvent', events=[event]) message_type, message_payload = messaging.parse_message(msg) assert message_payload['events'][0]['targets'] == [{'ven_id': 'ven456'}]
async def test_conformance_001(): dt = datetime(2020, 1, 1, 12, 0, 0, tzinfo=timezone(offset=timedelta(hours=4))) msg = create_message( 'oadrCreateOpt', **{ 'opt_id': generate_id(), 'opt_type': enums.OPT.OPT_IN, 'opt_reason': enums.OPT_REASON.ECONOMIC, 'ven_id': generate_id(), 'created_date_time': dt, 'request_id': generate_id(), 'event_id': generate_id(), 'modification_number': 1, 'targets': [{ 'ven_id': '123' }] }) parsed_type, parsed_msg = parse_message(msg) assert parsed_msg['created_date_time'].tzinfo == timezone.utc assert parsed_msg['created_date_time'] == dt.astimezone(timezone.utc)
def test_oadr_event_targets_and_targets_by_type(): event = objects.Event( event_descriptor=objects.EventDescriptor( event_id=1, modification_number=0, market_context='MarketContext1', event_status=enums.EVENT_STATUS.NEAR), active_period=objects.ActivePeriod(dtstart=datetime.now(), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=datetime.now(), duration=timedelta(minutes=5), uid=0, signal_payload=1), objects.Interval(dtstart=datetime.now(), duration=timedelta(minutes=5), uid=1, signal_payload=2) ], targets=[objects.Target(ven_id='1234')], signal_name=enums.SIGNAL_NAME.LOAD_CONTROL, signal_type=enums.SIGNAL_TYPE.LEVEL, signal_id=1, current_value=0) ], targets=[{ 'ven_id': 'ven123' }], targets_by_type={'ven_id': ['ven123']}) msg = create_message('oadrDistributeEvent', events=[event]) validate_xml_schema(ensure_bytes(msg)) message_type, message_payload = parse_message(msg)
def test_event_missing_created_date_time(caplog): now = datetime.now(timezone.utc) event = { 'active_period': { 'dtstart': now, 'duration': timedelta(seconds=10) }, 'event_descriptor': { 'event_id': 'event123', 'modification_number': 1, 'priority': 0, 'event_status': 'far' }, 'event_signals': [{ 'signal_name': 'simple', 'signal_type': 'level', 'intervals': [{ 'dtstart': now, 'duration': timedelta(seconds=10), 'signal_payload': 1 }] }], 'targets': [{ 'ven_id': 'ven123' }], 'response_required': 'always' } msg = messaging.create_message('oadrDistributeEvent', events=[event]) assert ("Your event descriptor did not contain a created_date_time. " "This will be automatically added.") in caplog.messages
def test_oadr_event(): event = objects.Event( event_descriptor=objects.EventDescriptor( event_id=1, modification_number=0, market_context='MarketContext1', event_status=enums.EVENT_STATUS.NEAR), active_period=objects.ActivePeriod(dtstart=datetime.now(), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=datetime.now(), duration=timedelta(minutes=5), uid=0, signal_payload=1), objects.Interval(dtstart=datetime.now(), duration=timedelta(minutes=5), uid=1, signal_payload=2) ], targets=[objects.Target(ven_id='1234')], signal_name=enums.SIGNAL_NAME.LOAD_CONTROL, signal_type=enums.SIGNAL_TYPE.LEVEL, signal_id=1, current_value=0) ], targets=[objects.Target(ven_id='1234')]) response = objects.Response(response_code=200, response_description='OK', request_id='1234') msg = create_message('oadrDistributeEvent', response=response, events=[event]) message_type, message_payload = parse_message(msg)
def test_add_report_invalid_description(caplog): client = OpenADRClient(ven_name='myven', vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b') client.add_report(callback=print, report_specifier_id='myreport', measurement={'name': 'voltage', 'description': 'SomethingWrong', 'unit': 'V'}, resource_id='Device001', sampling_rate=timedelta(seconds=10)) msg = create_message('oadrRegisterReport', reports=client.reports)
def test_message_validation(): msg = create_message('oadrPoll', ven_id='123', cert=TEST_CERT, key=TEST_KEY, passphrase='openadr') parsed_type, parsed_message = parse_message(msg) assert parsed_type == 'oadrPoll'
def test_message_validation(): msg = create_message('oadrPoll', ven_id='123', cert=TEST_CERT, key=TEST_KEY) tree = etree.fromstring(msg.encode('utf-8')) validate_xml_signature(tree) parsed_type, parsed_message = parse_message(msg) assert parsed_type == 'oadrPoll'
async def test_conformance_002(): """ The uid element is REQUIRED for each eiEventSignal interval. Within a sin- gle oadrDistributeEvent eiEventSignal, uid MUST be expressed as an inter- val number with a base of 0 and an increment of 1 for each subsequent in- terval. """ event_id = generate_id() event = {'event_descriptor': {'event_id': event_id, 'modification_number': 0, 'modification_date': datetime.now(), 'priority': 0, 'market_context': 'MarketContext001', 'created_date_time': datetime.now(), 'event_status': enums.EVENT_STATUS.FAR, 'test_event': False, 'vtn_comment': 'No Comment'}, 'active_period': {'dtstart': datetime.now(), 'duration': timedelta(minutes=30)}, 'event_signals': [{'intervals': [{'duration': timedelta(minutes=10), 'signal_payload': 1}, {'duration': timedelta(minutes=10), 'signal_payload': 2}, {'duration': timedelta(minutes=10), 'signal_payload': 3}], 'signal_name': enums.SIGNAL_NAME.SIMPLE, 'signal_type': enums.SIGNAL_TYPE.DELTA, 'signal_id': generate_id() }], 'targets': [{'ven_id': '123'}] } # Create a message with this event msg = create_message('oadrDistributeEvent', response={'response_code': 200, 'response_description': 'OK', 'request_id': generate_id()}, request_id=generate_id(), vtn_id=generate_id(), events=[event]) # Parse the message parsed_type, parsed_msg = parse_message(msg) assert parsed_type == 'oadrDistributeEvent' intervals = parsed_msg['events'][0]['event_signals'][0]['intervals'] # Verify that the interval uid's are numbered consecutively and starting at 0 assert intervals[0]['uid'] == 0 assert intervals[0]['signal_payload'] == 1 assert intervals[1]['uid'] == 1 assert intervals[1]['signal_payload'] == 2 assert intervals[2]['uid'] == 2 assert intervals[2]['signal_payload'] == 3
async def test_wrong_endpoint(start_server, caplog): message = messaging.create_message("oadrQueryRegistration", request_id='req1234') client = OpenADRClient( ven_name='myven', vtn_url=f'http://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b') response_type, response_payload = await client._perform_request( 'OadrPoll', message) assert response_type == 'oadrResponse' assert response_payload['response']['response_code'] == 459
async def test_conformance_009_pass(): """ oadrDistributeEvent eiEventSignal’s with a signalName of “SIMPLE” MUST use signalPayload values of 0=normal; 1=moderate; 2=high; 3=special. """ event_id = generate_id() event = { 'event_descriptor': { 'event_id': event_id, 'modification_number': 0, 'modification_date': datetime.now(), 'priority': 0, 'market_context': 'MarketContext001', 'created_date_time': datetime.now(), 'event_status': enums.EVENT_STATUS.FAR, 'test_event': False, 'vtn_comment': 'No Comment' }, 'active_period': { 'dtstart': datetime.now(), 'duration': timedelta(minutes=30) }, 'event_signals': [{ 'intervals': [{ 'duration': timedelta(minutes=10), 'signal_payload': 1 }, { 'duration': timedelta(minutes=10), 'signal_payload': 2 }, { 'duration': timedelta(minutes=10), 'signal_payload': 3 }], 'signal_name': enums.SIGNAL_NAME.SIMPLE, 'signal_type': enums.SIGNAL_TYPE.DELTA, 'signal_id': generate_id() }], 'targets': [{ 'ven_id': '123' }] } # Create a message with this event msg = create_message('oadrDistributeEvent', response={ 'response_code': 200, 'response_description': 'OK', 'request_id': generate_id() }, request_id=generate_id(), vtn_id=generate_id(), events=[event])
async def test_conformance_008_raise(): """ oadrDistributeEvent eventSignal interval durations for a given event MUST add up to eiEvent eiActivePeriod duration. """ event_id = generate_id() event = {'event_descriptor': {'event_id': event_id, 'modification_number': 0, 'modification_date': datetime.now(), 'priority': 0, 'market_context': 'MarketContext001', 'created_date_time': datetime.now(), 'event_status': enums.EVENT_STATUS.FAR, 'test_event': False, 'vtn_comment': 'No Comment'}, 'active_period': {'dtstart': datetime.now(), 'duration': timedelta(minutes=5)}, 'event_signals': [{'intervals': [{'duration': timedelta(minutes=10), 'signal_payload': 1}, {'duration': timedelta(minutes=10), 'signal_payload': 2}, {'duration': timedelta(minutes=10), 'signal_payload': 3}], 'signal_name': enums.SIGNAL_NAME.SIMPLE, 'signal_type': enums.SIGNAL_TYPE.DELTA, 'signal_id': generate_id() }, {'intervals': [{'duration': timedelta(minutes=1), 'signal_payload': 1}, {'duration': timedelta(minutes=2), 'signal_payload': 2}, {'duration': timedelta(minutes=2), 'signal_payload': 3}], 'signal_name': enums.SIGNAL_NAME.SIMPLE, 'signal_type': enums.SIGNAL_TYPE.DELTA, 'signal_id': generate_id() }] } with pytest.raises(ValueError): msg = create_message('oadrDistributeEvent', response={'response_code': 200, 'response_description': 'OK', 'request_id': generate_id()}, request_id=generate_id(), vtn_id=generate_id(), events=[event])
async def test_conformance_008_autocorrect(caplog): """ oadrDistributeEvent eventSignal interval durations for a given event MUST add up to eiEvent eiActivePeriod duration. """ event_id = generate_id() event = {'event_descriptor': {'event_id': event_id, 'modification_number': 0, 'modification_date': datetime.now(), 'priority': 0, 'market_context': 'MarketContext001', 'created_date_time': datetime.now(), 'event_status': enums.EVENT_STATUS.FAR, 'test_event': False, 'vtn_comment': 'No Comment'}, 'active_period': {'dtstart': datetime.now(), 'duration': timedelta(minutes=5)}, 'event_signals': [{'intervals': [{'duration': timedelta(minutes=10), 'signal_payload': 1}, {'duration': timedelta(minutes=10), 'signal_payload': 2}, {'duration': timedelta(minutes=10), 'signal_payload': 3}], 'signal_name': enums.SIGNAL_NAME.SIMPLE, 'signal_type': enums.SIGNAL_TYPE.DELTA, 'signal_id': generate_id() }], 'targets': [{'ven_id': '123'}] } # Create a message with this event msg = create_message('oadrDistributeEvent', response={'response_code': 200, 'response_description': 'OK', 'request_id': generate_id()}, request_id=generate_id(), vtn_id=generate_id(), events=[event]) assert caplog.record_tuples == [("openleadr", logging.WARNING, f"The active_period duration for event {event_id} (0:05:00) differs from the sum of the interval's durations (0:30:00). The active_period duration has been adjusted to (0:30:00).")] parsed_type, parsed_msg = parse_message(msg) assert parsed_type == 'oadrDistributeEvent' total_time = sum([i['duration'] for i in parsed_msg['events'][0]['event_signals'][0]['intervals']], timedelta(seconds=0)) assert parsed_msg['events'][0]['active_period']['duration'] == total_time
async def test_xml_schema_error(start_server, caplog): message = messaging.create_message("oadrQueryRegistration", request_id='req1234') message = message.replace( '<requestID xmlns="http://docs.oasis-open.org/ns/energyinterop/201110/payloads">req1234</requestID>', '') client = OpenADRClient( ven_name='myven', vtn_url=f'http://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b') result = await client._perform_request('EiRegisterParty', message) assert result == (None, {}) logs = [rec.message for rec in caplog.records] for log in logs: if log.startswith("Non-OK status 400"): assert "XML failed validation" in log break else: assert False
def test_event_incongruent_targets(caplog): now = datetime.now(timezone.utc) event = { 'active_period': { 'dtstart': now, 'duration': timedelta(seconds=10) }, 'event_descriptor': { 'event_id': 'event123', 'modification_number': 1, 'priority': 0, 'event_status': 'far', 'created_date_time': now }, 'event_signals': [{ 'signal_name': 'simple', 'signal_type': 'level', 'intervals': [{ 'dtstart': now, 'duration': timedelta(seconds=10), 'signal_payload': 1 }] }], 'targets': [{ 'ven_id': 'ven123' }], 'targets_by_type': { 'ven_id': ['ven456'] }, 'response_required': 'always' } with pytest.raises(ValueError) as err: msg = messaging.create_message('oadrDistributeEvent', events=[event]) assert str(err.value) == ( "You assigned both 'targets' and 'targets_by_type' in your event, " "but the two were not consistent with each other. " f"You supplied 'targets' = {event['targets']} and " f"'targets_by_type' = {event['targets_by_type']}")
def test_message(message_type, data): # file = open('representations.rst', 'a') # print(f".. _{message_type}:", file=file) # print("", file=file) # print(message_type, file=file) # print("="*len(message_type), file=file) # print("", file=file) # print("OpenADR payload:", file=file) # print("", file=file) # print(".. code-block:: xml", file=file) # print(" ", file=file) message = create_message(message_type, **data) # message = re.sub(r"\s\s+","",message) # message = message.replace("\n","") # xml_lines = etree.tostring(etree.fromstring(message.replace('\n', '').encode('utf-8')), pretty_print=True).decode('utf-8').splitlines() # for line in xml_lines: # print(" " + line, file=file) # print("", file=file) # print("OpenLEADR representation:", file=file) # print(" ", file=file) # print(".. code-block:: python3", file=file) # print(" ", file=file) validate_xml_schema(message) parsed = parse_message(message)[1] # dict_lines = pformat(parsed).splitlines() # for line in dict_lines: # print(" " + line, file=file) # print("", file=file) # print("", file=file) if message_type == 'oadrRegisterReport': for report in data['reports']: for rd in report['report_descriptions']: if 'measurement' in rd: rd['measurement'].pop('ns') if message_type == 'oadrDistributeEvent': for event in data['events']: for signal in event['event_signals']: if 'measurement' in signal: signal['measurement'].pop('ns') assert parsed == data
def test_event_with_wrong_response_required(caplog): now = datetime.now(timezone.utc) event = { 'active_period': { 'dtstart': now, 'duration': timedelta(seconds=10) }, 'event_descriptor': { 'event_id': 'event123', 'modification_number': 1, 'priority': 0, 'event_status': 'far', 'created_date_time': now }, 'event_signals': [{ 'signal_name': 'simple', 'signal_type': 'level', 'intervals': [{ 'dtstart': now, 'duration': timedelta(seconds=10), 'signal_payload': 1 }] }], 'targets': [{ 'ven_id': 'ven123' }], 'response_required': 'blabla' } msg = messaging.create_message('oadrDistributeEvent', events=[event]) assert ("The response_required property in an Event should be " "'never' or 'always', not blabla. Changing to 'always'." ) in caplog.messages message_type, message_payload = messaging.parse_message(msg) assert message_payload['events'][0]['response_required'] == 'always'
def test_message(msg_type, payload): message = create_message(msg_type, **payload) print(message) tree = validate_xml_schema(ensure_bytes(message))
def test_message_validation_complex(): now = datetime.now(timezone.utc) event_id = generate_id() active_period = { "dtstart": now + timedelta(minutes=1), "duration": timedelta(minutes=9) } event_descriptor = { "event_id": event_id, "modification_number": 1, "modification_date_time": now, "priority": 1, "market_context": "http://MarketContext1", "created_date_time": now, "event_status": "near", "test_event": "false", "vtn_comment": "This is an event" } event_signals = [{ "intervals": [{ "duration": timedelta(minutes=1), "uid": 1, "signal_payload": 8 }, { "duration": timedelta(minutes=1), "uid": 2, "signal_payload": 10 }, { "duration": timedelta(minutes=1), "uid": 3, "signal_payload": 12 }, { "duration": timedelta(minutes=1), "uid": 4, "signal_payload": 14 }, { "duration": timedelta(minutes=1), "uid": 5, "signal_payload": 16 }, { "duration": timedelta(minutes=1), "uid": 6, "signal_payload": 18 }, { "duration": timedelta(minutes=1), "uid": 7, "signal_payload": 20 }, { "duration": timedelta(minutes=1), "uid": 8, "signal_payload": 10 }, { "duration": timedelta(minutes=1), "uid": 9, "signal_payload": 20 }], "signal_name": "LOAD_CONTROL", #"signal_name": "simple", #"signal_type": "level", "signal_type": "x-loadControlCapacity", "signal_id": generate_id(), "current_value": 9.99 }] event_targets = [{"ven_id": 'VEN001'}, {"ven_id": 'VEN002'}] event = { 'active_period': active_period, 'event_descriptor': event_descriptor, 'event_signals': event_signals, 'targets': event_targets, 'response_required': 'always' } msg = create_message('oadrDistributeEvent', request_id=generate_id(), response={ 'request_id': 123, 'response_code': 200, 'response_description': 'OK' }, events=[event], cert=TEST_CERT, key=TEST_KEY) tree = etree.fromstring(msg.encode('utf-8')) validate_xml_signature(tree) parsed_type, parsed_msg = parse_message(msg)
async def test_conformance_006(): """ The presence of any string except “false” in the oadrDistributeEvent testEvent element MUST be treated as a trigger for a test event. """ # Monkey patch our own formatter to prevent an error being raised from openleadr.messaging import TEMPLATES def booleanformat_monkey(value): """ Format a boolean value """ if isinstance(value, bool): if value == True: return "true" elif value == False: return "false" else: return value booleanformat_original = TEMPLATES.filters['booleanformat'] TEMPLATES.filters['booleanformat'] = booleanformat_monkey event_id = generate_id() event = { 'event_descriptor': { 'event_id': event_id, 'modification_number': 0, 'modification_date': datetime.now(), 'priority': 0, 'market_context': 'MarketContext001', 'created_date_time': datetime.now(), 'event_status': enums.EVENT_STATUS.FAR, 'test_event': "HelloThere", 'vtn_comment': 'No Comment' }, 'active_period': { 'dtstart': datetime.now(), 'duration': timedelta(minutes=30) }, 'event_signals': [{ 'intervals': [{ 'duration': timedelta(minutes=10), 'signal_payload': 1 }, { 'duration': timedelta(minutes=10), 'signal_payload': 2 }, { 'duration': timedelta(minutes=10), 'signal_payload': 3 }], 'signal_name': enums.SIGNAL_NAME.SIMPLE, 'signal_type': enums.SIGNAL_TYPE.DELTA, 'signal_id': generate_id() }], 'targets': [{ 'ven_id': '123' }] } # Create a message with this event msg = create_message('oadrDistributeEvent', response={ 'response_code': 200, 'response_description': 'OK', 'request_id': generate_id() }, request_id=generate_id(), vtn_id=generate_id(), events=[event]) parsed_type, parsed_message = parse_message(msg) assert parsed_type == 'oadrDistributeEvent' assert parsed_message['events'][0]['event_descriptor'][ 'test_event'] == True # Restore the original booleanformat function TEMPLATES.filters['booleanformat'] = booleanformat_original
def test_message(message_type, data): message = create_message(message_type, **data) print(message) parsed = parse_message(message)[1] assert parsed == data
async def test_conformance_014_warn(caplog): """ If currentValue is included in the payload, it MUST be set to 0 (normal) when the event status is not “active” for the SIMPLE signalName. """ event_id = generate_id() event = { 'event_descriptor': { 'event_id': event_id, 'modification_number': 0, 'modification_date': datetime.now(), 'priority': 0, 'market_context': 'MarketContext001', 'created_date_time': datetime.now(), 'event_status': enums.EVENT_STATUS.FAR, 'test_event': False, 'vtn_comment': 'No Comment' }, 'active_period': { 'dtstart': datetime.now() + timedelta(minutes=30), 'duration': timedelta(minutes=30) }, 'event_signals': [{ 'intervals': [{ 'duration': timedelta(minutes=10), 'signal_payload': 1 }, { 'duration': timedelta(minutes=10), 'signal_payload': 2 }, { 'duration': timedelta(minutes=10), 'signal_payload': 3 }], 'signal_name': enums.SIGNAL_NAME.SIMPLE, 'signal_type': enums.SIGNAL_TYPE.DELTA, 'signal_id': generate_id(), 'current_value': 123 }], 'targets': { 'ven_id': '123' } } # Create a message with this event msg = create_message('oadrDistributeEvent', response={ 'response_code': 200, 'response_description': 'OK', 'request_id': generate_id() }, request_id=generate_id(), vtn_id=generate_id(), events=[event]) assert caplog.record_tuples == [( "openleadr", logging.WARNING, "The current_value for a SIMPLE event that is not yet active must be 0. This will be corrected." )]