[Asterisk-code-review] Add tests to verify pidf+xml & xpidf+xml NOTIFY bodies. (testsuite[master])
Matt Jordan
asteriskteam at digium.com
Wed Aug 19 08:45:06 CDT 2015
Matt Jordan has submitted this change and it was merged.
Change subject: Add tests to verify pidf+xml & xpidf+xml NOTIFY bodies.
......................................................................
Add tests to verify pidf+xml & xpidf+xml NOTIFY bodies.
This adds tests to verify pidf+xml & xpidf+xml NOTIFY bodies in PJSIP. The
tests set different custom states and verifies that the NOTIFY bodies match
what is expected. The tests SHARE a test module to listen for SIP packets and
to verify bodies.
The pcap.py library has been modified so XPIDF bodies are identified.
AFS-129
Change-Id: Ib852d1d42ea391fe4e7d1abf0eb7ea6eabb62cba
---
M lib/python/asterisk/pcap.py
M tests/channels/pjsip/subscriptions/presence/tests.yaml
A tests/channels/pjsip/subscriptions/presence/verify_bodies/presence.py
A tests/channels/pjsip/subscriptions/presence/verify_bodies/tests.yaml
A tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/configs/ast1/extensions.conf
A tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/configs/ast1/pjsip.conf
A tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/sipp/subscribe.xml
A tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/test-config.yaml
A tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/configs/ast1/extensions.conf
A tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/configs/ast1/pjsip.conf
A tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/sipp/subscribe.xml
A tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/test-config.yaml
12 files changed, 1,288 insertions(+), 0 deletions(-)
Approvals:
Mark Michelson: Looks good to me, but someone else must approve
Matt Jordan: Looks good to me, approved; Verified
diff --git a/lib/python/asterisk/pcap.py b/lib/python/asterisk/pcap.py
index 2db2b9c..e0065b3 100644
--- a/lib/python/asterisk/pcap.py
+++ b/lib/python/asterisk/pcap.py
@@ -225,6 +225,15 @@
self.content_id = content_id
+class XPIDFPacket(Packet):
+ '''A XPIDF presence body. Owned by SIPPacket or a MultipartPacket.'''
+
+ def __init__(self, ascii_packet, raw_packet, content_id):
+ Packet.__init__(self, packet_type="XPIDF", raw_packet=raw_packet)
+ self.xml = ascii_packet.strip()
+ self.content_id = content_id
+
+
class MWIPacket(Packet):
'''An MWI body. Owned by SIPPacket or a MultipartPacket.'''
@@ -309,6 +318,8 @@
return RLMIPacket(ascii_packet, raw_packet)
elif (body_type == 'application/pidf+xml'):
return PIDFPacket(ascii_packet, raw_packet, content_id)
+ elif (body_type == 'application/xpidf+xml'):
+ return XPIDFPacket(ascii_packet, raw_packet, content_id)
elif (body_type == 'application/simple-message-summary'):
return MWIPacket(ascii_packet, raw_packet, content_id)
else:
diff --git a/tests/channels/pjsip/subscriptions/presence/tests.yaml b/tests/channels/pjsip/subscriptions/presence/tests.yaml
index 9123536..96684a6 100644
--- a/tests/channels/pjsip/subscriptions/presence/tests.yaml
+++ b/tests/channels/pjsip/subscriptions/presence/tests.yaml
@@ -10,3 +10,4 @@
- test: 'presencestate_repeat'
- test: 'presencestate_repeat_okay'
- test: 'dialog_info_xml'
+ - dir: 'verify_bodies'
diff --git a/tests/channels/pjsip/subscriptions/presence/verify_bodies/presence.py b/tests/channels/pjsip/subscriptions/presence/verify_bodies/presence.py
new file mode 100644
index 0000000..143ea42
--- /dev/null
+++ b/tests/channels/pjsip/subscriptions/presence/verify_bodies/presence.py
@@ -0,0 +1,421 @@
+#!/usr/bin/env python
+"""Pluggable module for tests that verify NOTIFY bodies.
+
+Copyright (C) 2015, Digium, Inc.
+John Bigelow <jbigelow at digium.com>
+
+This program is free software, distributed under the terms of
+the GNU General Public License Version 2.
+"""
+
+import sys
+import logging
+import xml.etree.ElementTree as ET
+import re
+
+sys.path.append('lib/python')
+
+from pcap import VOIPListener
+from twisted.internet import reactor
+
+LOGGER = logging.getLogger(__name__)
+
+
+class BodyCheck(VOIPListener):
+ """SIP notify listener and expected results generator.
+
+ A test module that observes incoming SIP notifies and generates the
+ expected results for the body of each.
+ """
+ def __init__(self, module_config, test_object):
+ """Constructor
+
+ Arguments:
+ module_config Dictionary containing test configuration
+ test_object The test object for the running test.
+ """
+ self.set_pcap_defaults(module_config)
+ VOIPListener.__init__(self, module_config, test_object)
+
+ self.test_object = test_object
+ self.token = test_object.create_fail_token("Haven't handled all "
+ "expected NOTIFY packets.")
+ self.expected_config = module_config['expected_body']
+ self.expected_notifies = int(module_config['expected_notifies'])
+ self.body_type = module_config['expected_body_type']
+ self.notify_count = 0
+
+ if self.body_type.upper() not in ('PIDF', 'XPIDF'):
+ msg = "Body type of '{0}' not supported."
+ raise Exception(msg.format(self.body_type))
+
+ if self.expected_config.get('namespaces') is not None:
+ if self.expected_config['namespaces'].get('default') is None:
+ msg = "Namespaces configuration does not include a 'default'."
+ raise Exception(msg)
+
+ # Add calback for SIP packets
+ self.add_callback('SIP', self.packet_handler)
+
+ def gen_expected_data(self):
+ """Generate expected data results.
+
+ Generates a single dictionary containing the expected results for a
+ body.
+
+ Returns:
+ Dictionary of expected results.
+ """
+ expected_data = {}
+ # Use full tags if we have namespaces.
+ if self.expected_config.get('namespaces') is not None:
+ full_tags = self.gen_full_tags()
+ else:
+ full_tags = self.expected_config['tags']
+
+ # Get expected attributes corresponding to the notify body received.
+ attribs = self.expected_config['attributes'][self.notify_count - 1]
+
+ text = self.expected_config.get('text')
+ # Get expected text corresponding to the notify body received.
+ if text is not None:
+ text = text[self.notify_count - 1]
+
+ # Build dict of the expected results
+ for full_tag in full_tags:
+ expected_data[full_tag] = {}
+ for tag in attribs.keys():
+ if tag not in full_tag:
+ continue
+ expected_data[full_tag]['attribs'] = attribs[tag]
+ try:
+ for tag in text.keys():
+ if tag not in full_tag:
+ continue
+ expected_data[full_tag]['text'] = text[tag]
+ except AttributeError:
+ pass
+
+ return expected_data
+
+ def gen_full_tags(self):
+ """Generate fully qualified element tags.
+
+ This generates fully qualified element tags by prefixing the tag name
+ with it's corresponding namespace that is enclosed in curly braces.
+ This is so our expected tags will properly match ElementTree tags.
+
+ The format for an Element tag is: {<namespace>}<tag name>
+
+ Returns:
+ List of full tag names.
+ """
+ full_tags = []
+ namespaces = self.expected_config['namespaces']
+
+ for tag in self.expected_config['tags']:
+ try:
+ prefix, tag = tag.split(':')
+ namespace = '{' + namespaces[prefix] + '}'
+ except ValueError:
+ namespace = '{' + namespaces['default'] + '}'
+ except KeyError as keyerr:
+ msg = "Key {0} not found in namespace configuration for tag."
+ raise Exception(msg.format(keyerr))
+
+ full_tags.append("{0}{1}".format(namespace, tag))
+
+ return full_tags
+
+ def set_pcap_defaults(self, module_config):
+ """Set default PcapListener config that isn't explicitly overridden.
+
+ Arguments:
+ module_config Dict of module configuration
+ """
+ pcap_defaults = {'device': 'lo', 'snaplen': 2000,
+ 'bpf-filter': 'udp port 5061', 'debug-packets': False,
+ 'buffer-size': 4194304, 'register-observer': True}
+ for name, value in pcap_defaults.items():
+ module_config[name] = module_config.get(name, value)
+
+ def packet_handler(self, packet):
+ """Handle incoming SIP packets and verify contents.
+
+ Check to see if a packet is a NOTIFY packet with the expected body
+ type. If so then verify the body in the packet against the expected
+ results.
+
+ Arguments:
+ packet Incoming SIP Packet
+ """
+
+ LOGGER.debug('Received SIP packet')
+
+ if 'NOTIFY' not in packet.request_line:
+ LOGGER.debug('Ignoring packet, not a NOTIFY.')
+ return
+
+ if packet.body.packet_type != self.body_type.upper():
+ msg = "Ignoring packet, NOTIFY does not contain a '{0}' body type."
+ LOGGER.warn(msg.format(self.body_type.upper()))
+ return
+
+ self.notify_count += 1
+
+ # Generate dict of expected results for this notify body and validate
+ # the body using it.
+ expected = self.gen_expected_data()
+ validator = Validator(self.test_object, packet, expected)
+ if not validator.verify_body():
+ LOGGER.error('Body validation failed.')
+ return
+
+ info_msg = "Body #{0} validated successfully."
+ LOGGER.info(info_msg.format(self.notify_count))
+
+ if self.notify_count == self.expected_notifies:
+ self.test_object.remove_fail_token(self.token)
+ self.test_object.set_passed(True)
+ self.test_object.stop_reactor()
+
+
+class Validator(object):
+ """Validate a PIDF/XPIDF body against a set of expected data."""
+ def __init__(self, test_object, packet, expected_data):
+ """Constructor
+
+ Arguments:
+ test_object The test object for the running test.
+ packet A packet containing a SIP NOTIFY with a pidf or xpidf body.
+ """
+ super(Validator, self).__init__()
+ self.test_object = test_object
+ self.packet = packet
+ self.body_types = ('PIDF', 'XPIDF')
+ self.expected_data = expected_data
+
+ def verify_body(self):
+ """Verify a PIDF/XPIDF body.
+
+ This uses XML ElementTree to parse the PIDF/XPIDF body. It verifies
+ that the XML is not malformed and verifies the elements match what is
+ expected. This will fail the test and stop the reactor if the body type
+ is not recognized or if the body could not be parsed.
+
+ Returns:
+ True if body type is supported, body is successfully parsed, and body
+ matches what is expected. False otherwise.
+ """
+ if self.packet.body.packet_type not in self.body_types:
+ msg = "Unrecognized body type of '{0}'"
+ self.fail_test(msg.format(self.packet.body.packet_type))
+ return False
+
+ # Attempt to parse the body
+ try:
+ root = ET.fromstring(self.packet.body.xml)
+ except Exception as ex:
+ self.fail_test("Exception when parsing body XML: %s" % ex)
+ return False
+
+ # Verify top-level elements and their children
+ for element in root.findall('.'):
+ if not self.verify_element(element):
+ return False
+
+ return True
+
+ def verify_element(self, element):
+ """Verify the element matches what is expected.
+
+ This verifies the tag, attributes, text, and extra text of an element.
+ If child elements are found this will call back into itself to verify
+ them.
+
+ Arguments:
+ element Element object.
+
+ Returns:
+ True if the element matches what is expected. False otherwise.
+ """
+ # Verify tag, attributes, text, and extra text of the element.
+ if not self.verify_tag(element):
+ return False
+ if not self.verify_attributes(element):
+ return False
+ if not self.verify_text(element):
+ return False
+ if not self.verify_extra_text(element):
+ return False
+
+ # Find child elements
+ children = element.findall('*')
+ if not children:
+ return True
+
+ # Verify child elements.
+ for child in children:
+ if not self.verify_element(child):
+ return False
+
+ return True
+
+ def verify_tag(self, element):
+ """Verify element tag is expected.
+
+ This will fail the test and stop the reactor if the element tag is not
+ expected.
+
+ Arguments:
+ element Element object.
+
+ Returns:
+ True if element tag is in expected tags. False otherwise.
+ """
+ LOGGER.debug("Checking tag: '{0}'".format(element.tag))
+ if element.tag in self.expected_data.keys():
+ return True
+
+ self.fail_test("Unexpected tag: '{0}'.".format(element.tag))
+
+ return False
+
+ def verify_attributes(self, element):
+ """Verify element attributes.
+
+ Ensure the element contains only the attributes that are expected and
+ the attribute values match what are expected. This will fail the test
+ and stop the reactor if conditions are not met.
+
+ Arguments:
+ element Element object.
+
+ Returns:
+ True if attributes not expected and none found, expected attribute
+ values match found attribute values. Otherwise False.
+ """
+ expected = self.expected_data[element.tag].get('attribs')
+ LOGGER.debug("Checking attributes.")
+
+ # If attributes are not expected and none are in the element then
+ # there's nothing more to do.
+ if not element.keys() and expected is None:
+ msg = "Attributes not expected and none found."
+ LOGGER.debug(msg.format())
+ return True
+
+ # Check if we expect attributes but element doesn't have any.
+ if not element.keys() and expected is not None:
+ msg = "Expected attributes not found: {0}"
+ self.fail_test(msg.format(', '.join(expected.keys())))
+ return False
+
+ # Check if we don't expect attributes but element has some.
+ if element.keys() and expected is None:
+ msg = "Unexpected attributes found: {0}"
+ self.fail_test(msg.format(', '.join(element.keys())))
+ return False
+
+ # Ensure all expected attributes exist in the element.
+ not_found = [ex for ex in expected.keys() if ex not in element.keys()]
+ if not_found:
+ msg = "Expected attributes not found in element: {0}"
+ self.fail_test(msg.format(', '.join(not_found)))
+ return False
+
+ for xml_attrib in element.keys():
+ LOGGER.debug("Checking attribute: '{0}'".format(xml_attrib))
+ # Check if we don't expect attributes this particular attribute for
+ # this element.
+ if expected.get(xml_attrib) is None:
+ msg = "Unexpected attribute found: '{0}'"
+ self.fail_test(msg.format(xml_attrib))
+ return False
+
+ if not re.match(expected[xml_attrib], element.get(xml_attrib)):
+ msg = "Attribute '{0}' value '{1}' does not match '{2}'"
+ self.fail_test(msg.format(xml_attrib, element.get(xml_attrib),
+ expected[xml_attrib]))
+ return False
+
+ return True
+
+ def verify_text(self, element):
+ """Verify element text.
+
+ Ensure the element text matches the expected text. This will fail the
+ test and stop the reactor if conditions are not met.
+
+ Arguments:
+ element Element object.
+
+ Returns:
+ True if element text matches expected text. Otherwise False.
+ """
+ expected = self.expected_data[element.tag].get('text', '')
+ element_text = element.text
+
+ # Set to empty string if None so we can strip it and try to match it.
+ if element_text is None:
+ element_text = ''
+ element_text = element_text.strip()
+
+ LOGGER.debug("Checking text: '{0}'".format(element_text))
+ # Check if we don't expect any text or we don't expect this particular
+ # text for this element.
+ if element_text and not expected:
+ msg = "Unexpected text found: '{0}'"
+ self.fail_test(msg.format(element_text))
+ return False
+
+ # Check if we expect text but element doesn't have any.
+ if not element_text and expected:
+ msg = "Expected text not found: '{0}'"
+ self.fail_test(msg.format(expected))
+ return False
+
+ if not re.match(expected, element_text):
+ msg = "Element text '{0}' does not match '{1}'"
+ self.fail_test(msg.format(element_text, expected))
+ return False
+
+ return True
+
+ def verify_extra_text(self, element):
+ """Verify extra text is not present in element.
+
+ Ensure there is no extra text in the element. This will fail the test
+ and stop the reactor if extra text is found.
+
+ Arguments:
+ element Element object.
+
+ Returns:
+ True if extra text was not found or only whitespace was found.
+ Otherwise False.
+ """
+ LOGGER.debug("Checking for extra text.")
+ if element.tail is None:
+ return True
+
+ # Ignore any whitespace
+ extra_text = str(element.tail)
+ extra_text = extra_text.strip()
+ if not extra_text:
+ return True
+
+ msg = "Unexpected extra text found on element '%s': '%s'"
+ self.fail_test(msg.format(element.tag, extra_text))
+
+ return False
+
+ def fail_test(self, message):
+ """Mark the test as failed and stop the reactor
+
+ Arguments:
+ message Reason for the test failure
+ """
+ LOGGER.error(message)
+ self.test_object.set_passed(False)
+ self.test_object.stop_reactor()
diff --git a/tests/channels/pjsip/subscriptions/presence/verify_bodies/tests.yaml b/tests/channels/pjsip/subscriptions/presence/verify_bodies/tests.yaml
new file mode 100644
index 0000000..2345db0
--- /dev/null
+++ b/tests/channels/pjsip/subscriptions/presence/verify_bodies/tests.yaml
@@ -0,0 +1,4 @@
+# Enter tests here in the order they should be considered for execution:
+tests:
+ - test: 'verify_pidf'
+ - test: 'verify_xpidf'
diff --git a/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/configs/ast1/extensions.conf b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/configs/ast1/extensions.conf
new file mode 100644
index 0000000..b8dcca3
--- /dev/null
+++ b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/configs/ast1/extensions.conf
@@ -0,0 +1,2 @@
+[default]
+exten => bob,hint,Custom:bob
diff --git a/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/configs/ast1/pjsip.conf b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/configs/ast1/pjsip.conf
new file mode 100644
index 0000000..2e71a90
--- /dev/null
+++ b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/configs/ast1/pjsip.conf
@@ -0,0 +1,11 @@
+[global]
+type=global
+debug=no
+
+[local-transport]
+type=transport
+bind = 127.0.0.1
+
+[alice]
+type=endpoint
+context=default
diff --git a/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/sipp/subscribe.xml b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/sipp/subscribe.xml
new file mode 100644
index 0000000..8c335b8
--- /dev/null
+++ b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/sipp/subscribe.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<scenario name="Subscribe">
+ <send retrans="500">
+ <![CDATA[
+
+ SUBSCRIBE sip:bob@[remote_ip]:[remote_port] SIP/2.0
+ Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+ From: "alice" <sip:alice@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
+ To: <sip:bob@[remote_ip]:[remote_port]>
+ Call-ID: [call_id]
+ CSeq: 1 SUBSCRIBE
+ Contact: "alice" <sip:alice@[local_ip]:[local_port]>
+ Expires: 600
+ Max-Forwards: 70
+ Event: presence
+ Supported: replaces, 100rel, timer, norefersub
+ Accept: application/pidf+xml
+ Allow-Events: presence, message-summary, refer
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv response="200" rtd="true" />
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+</scenario>
diff --git a/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/test-config.yaml b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/test-config.yaml
new file mode 100644
index 0000000..17bba83
--- /dev/null
+++ b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_pidf/test-config.yaml
@@ -0,0 +1,266 @@
+testinfo:
+ summary: 'Ensure NOTIFY bodies for pidf+xml subscriptions are correct.'
+ description: |
+ 'A SIPp instance subscribes to "bob" using pidf+xml. Upon the
+ first TestEvent indicating that there is an active subscription, the
+ state of "Custom:bob" is changed. Each AMI event indicating that a
+ state change occurred triggers another state change until all states
+ have been set. The SIPp scenario expects to receive a NOTIFY message
+ for each state and simply responds to them.
+
+ A test module is used to verify each NOTIFY body that it finds while
+ listening for SIP packets. The module parses the body and verifies the
+ tags, attributes, and text for each element using the specified
+ configuration. If the body is not able to be parsed(IE. malformed XML)
+ or any component of the body does not match what is expected, the test
+ fails.'
+
+properties:
+ minversion: '12.5.0'
+ dependencies:
+ - buildoption: 'TEST_FRAMEWORK'
+ - sipp :
+ version : 'v3.0'
+ - python: 'twisted'
+ - python: 'starpy'
+ - python: 'yappcap'
+ - asterisk: 'res_pjsip'
+ - asterisk: 'res_pjsip_exten_state'
+ - asterisk: 'res_pjsip_pidf_body_generator'
+ tags:
+ - pjsip
+
+test-modules:
+ add-relative-to-search-path: ['..']
+ test-object:
+ config-section: sipp-config
+ typename: 'sipp.SIPpTestCase'
+ modules:
+ -
+ config-section: test-config
+ typename: 'presence.BodyCheck'
+ -
+ config-section: ami-config
+ typename: 'pluggable_modules.EventActionModule'
+
+sipp-config:
+ reactor-timeout: 30
+ fail-on-any: True
+ test-iterations:
+ -
+ scenarios:
+ - { 'key-args': {'scenario': 'subscribe.xml', '-p': '5061'},
+ 'ordered-args': ['-timeout_error'] }
+
+test-config:
+ # Expected number of NOTIFY messages with the expected body type.
+ expected_notifies: '7'
+ # Expected body type.
+ expected_body_type: 'pidf'
+ # Expected body.
+ expected_body:
+ # Expected namespaces for PIDF bodies.
+ namespaces:
+ default: 'urn:ietf:params:xml:ns:pidf'
+ pp: 'urn:ietf:params:xml:ns:pidf:person'
+ ep: 'urn:ietf:params:xml:ns:pidf:rpid:rpid-person'
+ # All expected element tag names for PIDF bodies.
+ tags:
+ ['presence', 'note', 'tuple', 'status', 'basic', 'contact',
+ 'pp:person', 'ep:activities']
+ # Regex patterns for element attributes for each body in order. Some
+ # attribute values of elements differ for each body depending on the
+ # body type and state changes. Therefore we list them in the order that
+ # matches the state changes. For this test the attribute values should
+ # be the same for all bodies.
+ attributes:
+ -
+ presence:
+ entity: '^sip:bob at 127.0.0.1(:5060)?$'
+ tuple:
+ id: '^bob$'
+ contact:
+ priority: '^1$'
+ -
+ presence:
+ entity: '^sip:bob at 127.0.0.1(:5060)?$'
+ tuple:
+ id: '^bob$'
+ contact:
+ priority: '^1$'
+ -
+ presence:
+ entity: '^sip:bob at 127.0.0.1(:5060)?$'
+ tuple:
+ id: '^bob$'
+ contact:
+ priority: '^1$'
+ -
+ presence:
+ entity: '^sip:bob at 127.0.0.1(:5060)?$'
+ tuple:
+ id: '^bob$'
+ contact:
+ priority: '^1$'
+ -
+ presence:
+ entity: '^sip:bob at 127.0.0.1(:5060)?$'
+ tuple:
+ id: '^bob$'
+ contact:
+ priority: '^1$'
+ -
+ presence:
+ entity: '^sip:bob at 127.0.0.1(:5060)?$'
+ tuple:
+ id: '^bob$'
+ contact:
+ priority: '^1$'
+ -
+ presence:
+ entity: '^sip:bob at 127.0.0.1(:5060)?$'
+ tuple:
+ id: '^bob$'
+ contact:
+ priority: '^1$'
+ # Regex patterns for element text for each body in order. Some text
+ # of elements differ for each body depending on the body type and state
+ # changes. Therefore we list them in the order that matches the state
+ # changes.
+ text:
+ -
+ note: 'Ready'
+ basic: 'open'
+ contact: '^"alice" <sip:alice at 127.0.0.1(:5060)?>$'
+ -
+ note: 'Ringing'
+ basic: 'closed'
+ activities: 'ep:busy'
+ contact: '^"alice" <sip:alice at 127.0.0.1(:5060)?>$'
+ -
+ note: 'On the phone'
+ basic: 'closed'
+ activities: 'ep:busy'
+ contact: '^"alice" <sip:alice at 127.0.0.1(:5060)?>$'
+ -
+ note: 'On hold'
+ basic: 'closed'
+ activities: 'ep:busy'
+ contact: '^"alice" <sip:alice at 127.0.0.1(:5060)?>$'
+ -
+ note: 'On the phone'
+ basic: 'closed'
+ activities: 'ep:busy'
+ contact: '^"alice" <sip:alice at 127.0.0.1(:5060)?>$'
+ -
+ note: 'Unavailable'
+ basic: 'closed'
+ activities: 'ep:away'
+ contact: '^"alice" <sip:alice at 127.0.0.1(:5060)?>$'
+ -
+ note: 'Ready'
+ basic: 'open'
+ contact: '^"alice" <sip:alice at 127.0.0.1(:5060)?>$'
+
+ami-config:
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'TestEvent'
+ State: 'SUBSCRIPTION_STATE_SET'
+ StateText: 'ACTIVE'
+ Endpoint: 'alice'
+ count: '>1'
+ trigger-on-count: True
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'RINGING'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'Ringing'
+ count: '1'
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'INUSE'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'InUse'
+ count: '1'
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'ONHOLD'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'Hold'
+ count: '1'
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'BUSY'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'Busy'
+ count: '1'
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'UNAVAILABLE'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'Unavailable'
+ count: '1'
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'NOT_INUSE'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'Idle'
+ count: '1'
diff --git a/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/configs/ast1/extensions.conf b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/configs/ast1/extensions.conf
new file mode 100644
index 0000000..b8dcca3
--- /dev/null
+++ b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/configs/ast1/extensions.conf
@@ -0,0 +1,2 @@
+[default]
+exten => bob,hint,Custom:bob
diff --git a/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/configs/ast1/pjsip.conf b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/configs/ast1/pjsip.conf
new file mode 100644
index 0000000..2e71a90
--- /dev/null
+++ b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/configs/ast1/pjsip.conf
@@ -0,0 +1,11 @@
+[global]
+type=global
+debug=no
+
+[local-transport]
+type=transport
+bind = 127.0.0.1
+
+[alice]
+type=endpoint
+context=default
diff --git a/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/sipp/subscribe.xml b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/sipp/subscribe.xml
new file mode 100644
index 0000000..d359595
--- /dev/null
+++ b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/sipp/subscribe.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<scenario name="Subscribe">
+ <send retrans="500">
+ <![CDATA[
+
+ SUBSCRIBE sip:bob@[remote_ip]:[remote_port] SIP/2.0
+ Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+ From: "alice" <sip:alice@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
+ To: <sip:bob@[remote_ip]:[remote_port]>
+ Call-ID: [call_id]
+ CSeq: 1 SUBSCRIBE
+ Contact: "alice" <sip:alice@[local_ip]:[local_port]>
+ Expires: 600
+ Max-Forwards: 70
+ Event: presence
+ Supported: replaces, 100rel, timer, norefersub
+ Accept: application/xpidf+xml
+ Allow-Events: presence, message-summary, refer
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv response="200" rtd="true" />
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+ <recv request="NOTIFY" crlf="true"/>
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 200 OK
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+ Content-Length: 0
+
+ ]]>
+ </send>
+
+</scenario>
diff --git a/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/test-config.yaml b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/test-config.yaml
new file mode 100644
index 0000000..b6bfc53
--- /dev/null
+++ b/tests/channels/pjsip/subscriptions/presence/verify_bodies/verify_xpidf/test-config.yaml
@@ -0,0 +1,265 @@
+testinfo:
+ summary: 'Ensure NOTIFY bodies for xpidf+xml subscriptions are correct.'
+ description: |
+ 'A SIPp instance subscribes to "bob" using xpidf+xml. Upon the
+ first TestEvent indicating that there is an active subscription, the
+ state of "Custom:bob" is changed. Each AMI event indicating that a
+ state change occurred triggers another state change until all states
+ have been set. The SIPp scenario expects to receive a NOTIFY message
+ for each state and simply responds to them.
+
+ A test module is used to verify each NOTIFY body that it finds while
+ listening for SIP packets. The module parses the body and verifies the
+ tags, attributes, and text for each element using the specified
+ configuration. If the body is not able to be parsed(IE. malformed XML)
+ or any component of the body does not match what is expected, the test
+ fails.'
+
+properties:
+ minversion: '12.5.0'
+ dependencies:
+ - buildoption: 'TEST_FRAMEWORK'
+ - sipp :
+ version : 'v3.0'
+ - python: 'twisted'
+ - python: 'starpy'
+ - python: 'yappcap'
+ - asterisk: 'res_pjsip'
+ - asterisk: 'res_pjsip_exten_state'
+ - asterisk: 'res_pjsip_xpidf_body_generator'
+ tags:
+ - pjsip
+
+test-modules:
+ add-relative-to-search-path: ['..']
+ test-object:
+ config-section: sipp-config
+ typename: 'sipp.SIPpTestCase'
+ modules:
+ -
+ config-section: test-config
+ typename: 'presence.BodyCheck'
+ -
+ config-section: ami-config
+ typename: 'pluggable_modules.EventActionModule'
+
+sipp-config:
+ reactor-timeout: 30
+ fail-on-any: True
+ test-iterations:
+ -
+ scenarios:
+ - { 'key-args': {'scenario': 'subscribe.xml', '-p': '5061'},
+ 'ordered-args': ['-timeout_error'] }
+
+test-config:
+ # Expected number of NOTIFY messages with the expected body type.
+ expected_notifies: '7'
+ # Expected body type.
+ expected_body_type: 'xpidf'
+ # Expected body.
+ expected_body:
+ # All expected element tag names for XPIDF bodies.
+ tags:
+ ['presence', 'presentity', 'atom', 'address', 'status',
+ 'msnsubstatus']
+ # Regex patterns for element attributes for each body in order. Some
+ # attribute values of elements differ for each body depending on the
+ # body type and state changes. Therefore we list them in the order that
+ # matches the state changes. For this test some attribute values will
+ # differ between all bodies.
+ attributes:
+ -
+ presentity:
+ uri: '^sip:bob at 127.0.0.1(:5060)?;method=SUBSCRIBE$'
+ atom:
+ atomid: '^[a-z0-9-]+$'
+ id: '^bob$'
+ address:
+ uri: '^"alice" <sip:alice at 127.0.0.1(:5060)?>;user=ip$'
+ priority: '^0\.80000$'
+ status:
+ status: '^open$'
+ msnsubstatus:
+ substatus: '^online$'
+ -
+ presentity:
+ uri: '^sip:bob at 127.0.0.1(:5060)?;method=SUBSCRIBE$'
+ atom:
+ atomid: '^[a-z0-9-]+$'
+ id: '^bob$'
+ address:
+ uri: '^"alice" <sip:alice at 127.0.0.1(:5060)?>;user=ip$'
+ priority: '^0\.80000$'
+ status:
+ status: '^inuse$'
+ msnsubstatus:
+ substatus: '^onthephone$'
+ -
+ presentity:
+ uri: '^sip:bob at 127.0.0.1(:5060)?;method=SUBSCRIBE$'
+ atom:
+ atomid: '^[a-z0-9-]+$'
+ id: '^bob$'
+ address:
+ uri: '^"alice" <sip:alice at 127.0.0.1(:5060)?>;user=ip$'
+ priority: '^0\.80000$'
+ status:
+ status: '^inuse$'
+ msnsubstatus:
+ substatus: '^onthephone$'
+ -
+ presentity:
+ uri: '^sip:bob at 127.0.0.1(:5060)?;method=SUBSCRIBE$'
+ atom:
+ atomid: '^[a-z0-9-]+$'
+ id: '^bob$'
+ address:
+ uri: '^"alice" <sip:alice at 127.0.0.1(:5060)?>;user=ip$'
+ priority: '^0\.80000$'
+ status:
+ status: '^closed$'
+ msnsubstatus:
+ substatus: '^offline$'
+ -
+ presentity:
+ uri: '^sip:bob at 127.0.0.1(:5060)?;method=SUBSCRIBE$'
+ atom:
+ atomid: '^[a-z0-9-]+$'
+ id: '^bob$'
+ address:
+ uri: '^"alice" <sip:alice at 127.0.0.1(:5060)?>;user=ip$'
+ priority: '^0\.80000$'
+ status:
+ status: '^closed$'
+ msnsubstatus:
+ substatus: '^offline$'
+ -
+ presentity:
+ uri: '^sip:bob at 127.0.0.1(:5060)?;method=SUBSCRIBE$'
+ atom:
+ atomid: '^[a-z0-9-]+$'
+ id: '^bob$'
+ address:
+ uri: '^"alice" <sip:alice at 127.0.0.1(:5060)?>;user=ip$'
+ priority: '^0\.80000$'
+ status:
+ status: '^closed$'
+ msnsubstatus:
+ substatus: '^offline$'
+ -
+ presentity:
+ uri: '^sip:bob at 127.0.0.1(:5060)?;method=SUBSCRIBE$'
+ atom:
+ atomid: '^[a-z0-9-]+$'
+ id: '^bob$'
+ address:
+ uri: '^"alice" <sip:alice at 127.0.0.1(:5060)?>;user=ip$'
+ priority: '^0\.80000$'
+ status:
+ status: '^open$'
+ msnsubstatus:
+ substatus: '^online$'
+
+ami-config:
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'TestEvent'
+ State: 'SUBSCRIPTION_STATE_SET'
+ StateText: 'ACTIVE'
+ Endpoint: 'alice'
+ count: '>1'
+ trigger-on-count: True
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'RINGING'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'Ringing'
+ count: '1'
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'INUSE'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'InUse'
+ count: '1'
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'ONHOLD'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'Hold'
+ count: '1'
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'BUSY'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'Busy'
+ count: '1'
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'UNAVAILABLE'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'Unavailable'
+ count: '1'
+ ami-actions:
+ action:
+ action: 'SetVar'
+ variable: 'DEVICE_STATE(Custom:bob)'
+ value: 'NOT_INUSE'
+ -
+ ami-events:
+ id: '0'
+ conditions:
+ match:
+ Event: 'ExtensionStatus'
+ Exten: 'bob'
+ Hint: 'Custom:bob'
+ StatusText: 'Idle'
+ count: '1'
--
To view, visit https://gerrit.asterisk.org/1023
To unsubscribe, visit https://gerrit.asterisk.org/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ib852d1d42ea391fe4e7d1abf0eb7ea6eabb62cba
Gerrit-PatchSet: 4
Gerrit-Project: testsuite
Gerrit-Branch: master
Gerrit-Owner: John Bigelow <jbigelow at digium.com>
Gerrit-Reviewer: John Bigelow <jbigelow at digium.com>
Gerrit-Reviewer: Mark Michelson <mmichelson at digium.com>
Gerrit-Reviewer: Matt Jordan <mjordan at digium.com>
More information about the asterisk-code-review
mailing list