summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xexamples/disco_browser.py2
-rwxr-xr-xsetup.py1
-rw-r--r--sleekxmpp/jid.py23
-rw-r--r--sleekxmpp/plugins/__init__.py1
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/field.py4
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/form.py42
-rw-r--r--sleekxmpp/plugins/xep_0045.py10
-rw-r--r--sleekxmpp/plugins/xep_0054/stanza.py3
-rw-r--r--sleekxmpp/plugins/xep_0122/__init__.py11
-rw-r--r--sleekxmpp/plugins/xep_0122/data_validation.py19
-rw-r--r--sleekxmpp/plugins/xep_0122/stanza.py94
-rw-r--r--sleekxmpp/plugins/xep_0202/time.py7
-rw-r--r--sleekxmpp/plugins/xep_0323/sensordata.py2
-rw-r--r--sleekxmpp/plugins/xep_0332/http.py6
-rw-r--r--sleekxmpp/thirdparty/__init__.py1
-rw-r--r--sleekxmpp/thirdparty/orderedset.py89
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py2
-rw-r--r--tests/test_stanza_xep_0004.py61
-rw-r--r--tests/test_stanza_xep_0122.py189
-rw-r--r--tests/test_stream_xep_0050.py8
20 files changed, 531 insertions, 44 deletions
diff --git a/examples/disco_browser.py b/examples/disco_browser.py
index aeb4fb5e..78626e7c 100755
--- a/examples/disco_browser.py
+++ b/examples/disco_browser.py
@@ -94,7 +94,7 @@ class Disco(sleekxmpp.ClientXMPP):
info = self['xep_0030'].get_info(jid=self.target_jid,
node=self.target_node,
block=True)
- elif self.get in self.items_types:
+ if self.get in self.items_types:
# The same applies from above. Listen for the
# disco_items event or pass a callback function
# if you need to process a non-blocking request.
diff --git a/setup.py b/setup.py
index 566683b7..1cd245aa 100755
--- a/setup.py
+++ b/setup.py
@@ -93,6 +93,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0108',
'sleekxmpp/plugins/xep_0115',
'sleekxmpp/plugins/xep_0118',
+ 'sleekxmpp/plugins/xep_0122',
'sleekxmpp/plugins/xep_0128',
'sleekxmpp/plugins/xep_0131',
'sleekxmpp/plugins/xep_0152',
diff --git a/sleekxmpp/jid.py b/sleekxmpp/jid.py
index 613b272e..754a3d3a 100644
--- a/sleekxmpp/jid.py
+++ b/sleekxmpp/jid.py
@@ -72,19 +72,18 @@ JID_CACHE_LOCK = threading.Lock()
JID_CACHE_MAX_SIZE = 1024
def _cache(key, parts, locked):
- JID_CACHE[key] = (parts, locked)
- if len(JID_CACHE) > JID_CACHE_MAX_SIZE:
- with JID_CACHE_LOCK:
- while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
- found = None
- for key, item in JID_CACHE.items():
- if not item[1]: # if not locked
- found = key
- break
- if not found: # more than MAX_SIZE locked
- # warn?
+ with JID_CACHE_LOCK:
+ JID_CACHE[key] = (parts, locked)
+ while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
+ found = None
+ for key, item in JID_CACHE.items():
+ if not item[1]: # if not locked
+ found = key
break
- del JID_CACHE[found]
+ if not found: # more than MAX_SIZE locked
+ # warn?
+ break
+ del JID_CACHE[found]
# pylint: disable=c0103
#: The nodeprep profile of stringprep used to validate the local,
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index 2c90d357..f501687b 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -47,6 +47,7 @@ __all__ = [
'xep_0108', # User Activity
'xep_0115', # Entity Capabilities
'xep_0118', # User Tune
+ 'xep_0122', # Data Forms Validation
'xep_0128', # Extended Service Discovery
'xep_0131', # Standard Headers and Internet Metadata
'xep_0133', # Service Administration
diff --git a/sleekxmpp/plugins/xep_0004/stanza/field.py b/sleekxmpp/plugins/xep_0004/stanza/field.py
index 51f85995..73e48758 100644
--- a/sleekxmpp/plugins/xep_0004/stanza/field.py
+++ b/sleekxmpp/plugins/xep_0004/stanza/field.py
@@ -13,8 +13,9 @@ class FormField(ElementBase):
namespace = 'jabber:x:data'
name = 'field'
plugin_attrib = 'field'
+ plugin_multi_attrib = 'fields'
interfaces = set(('answer', 'desc', 'required', 'value',
- 'options', 'label', 'type', 'var'))
+ 'label', 'type', 'var'))
sub_interfaces = set(('desc',))
plugin_tag_map = {}
plugin_attrib_map = {}
@@ -165,6 +166,7 @@ class FieldOption(ElementBase):
plugin_attrib = 'option'
interfaces = set(('label', 'value'))
sub_interfaces = set(('value',))
+ plugin_multi_attrib = 'options'
FormField.addOption = FormField.add_option
diff --git a/sleekxmpp/plugins/xep_0004/stanza/form.py b/sleekxmpp/plugins/xep_0004/stanza/form.py
index 1d733760..3dcc7821 100644
--- a/sleekxmpp/plugins/xep_0004/stanza/form.py
+++ b/sleekxmpp/plugins/xep_0004/stanza/form.py
@@ -9,7 +9,7 @@
import copy
import logging
-from sleekxmpp.thirdparty import OrderedDict
+from sleekxmpp.thirdparty import OrderedDict, OrderedSet
from sleekxmpp.xmlstream import ElementBase, ET
from sleekxmpp.plugins.xep_0004.stanza import FormField
@@ -22,8 +22,7 @@ class Form(ElementBase):
namespace = 'jabber:x:data'
name = 'x'
plugin_attrib = 'form'
- interfaces = set(('fields', 'instructions', 'items',
- 'reported', 'title', 'type', 'values'))
+ interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
sub_interfaces = set(('title',))
form_types = set(('cancel', 'form', 'result', 'submit'))
@@ -43,12 +42,12 @@ class Form(ElementBase):
@property
def field(self):
- return self['fields']
+ return self.get_fields()
def set_type(self, ftype):
self._set_attr('type', ftype)
if ftype == 'submit':
- fields = self['fields']
+ fields = self.get_fields()
for var in fields:
field = fields[var]
del field['type']
@@ -74,7 +73,8 @@ class Form(ElementBase):
field['desc'] = desc
field['required'] = required
if options is not None:
- field['options'] = options
+ for option in options:
+ field.add_option(**option)
else:
del field['type']
self.append(field)
@@ -169,7 +169,7 @@ class Form(ElementBase):
def get_reported(self):
fields = OrderedDict()
xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
- FormField.namespace))
+ FormField.namespace))
for field in xml:
field = FormField(xml=field)
fields[field['var']] = field
@@ -177,7 +177,7 @@ class Form(ElementBase):
def get_values(self):
values = OrderedDict()
- fields = self['fields']
+ fields = self.get_fields()
for var in fields:
values[var] = fields[var]['value']
return values
@@ -219,17 +219,33 @@ class Form(ElementBase):
self.add_item(item)
def set_reported(self, reported):
+ """
+ This either needs a dictionary or dictionaries or a dictionary of form fields.
+ :param reported:
+ :return:
+ """
for var in reported:
field = reported[var]
- field['var'] = var
- self.add_reported(var, **field)
+
+ if isinstance(field, dict):
+ self.add_reported(**field)
+ else:
+ reported = self.xml.find('{%s}reported' % self.namespace)
+ if reported is None:
+ reported = ET.Element('{%s}reported' % self.namespace)
+ self.xml.append(reported)
+
+ fieldXML = ET.Element('{%s}field' % FormField.namespace)
+ reported.append(fieldXML)
+ new_field = FormField(xml=fieldXML)
+ new_field.values = field.values
def set_values(self, values):
- fields = self['fields']
+ fields = self.get_fields()
for field in values:
- if field not in fields:
+ if field not in self.get_fields():
fields[field] = self.add_field(var=field)
- fields[field]['value'] = values[field]
+ self.get_fields()[field]['value'] = values[field]
def merge(self, other):
new = copy.copy(self)
diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py
index ca5ed1ef..cc96d66e 100644
--- a/sleekxmpp/plugins/xep_0045.py
+++ b/sleekxmpp/plugins/xep_0045.py
@@ -397,6 +397,16 @@ class XEP_0045(BasePlugin):
return None
return self.rooms[room].keys()
+ def getUsersByAffiliation(cls, room, affiliation='member', ifrom=None):
+ if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
+ raise TypeError
+ query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
+ item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation': affiliation})
+ query.append(item)
+ iq = cls.xmpp.Iq(sto=room, sfrom=ifrom, stype='get')
+ iq.append(query)
+ return iq.send()
+
xep_0045 = XEP_0045
register_plugin(XEP_0045)
diff --git a/sleekxmpp/plugins/xep_0054/stanza.py b/sleekxmpp/plugins/xep_0054/stanza.py
index 72da0b51..2d017d6e 100644
--- a/sleekxmpp/plugins/xep_0054/stanza.py
+++ b/sleekxmpp/plugins/xep_0054/stanza.py
@@ -128,7 +128,8 @@ class Telephone(ElementBase):
def setup(self, xml=None):
super(Telephone, self).setup(xml=xml)
- self._set_sub_text('NUMBER', '', keep=True)
+ ## this blanks out numbers received from server
+ ##self._set_sub_text('NUMBER', '', keep=True)
def set_number(self, value):
self._set_sub_text('NUMBER', value, keep=True)
diff --git a/sleekxmpp/plugins/xep_0122/__init__.py b/sleekxmpp/plugins/xep_0122/__init__.py
new file mode 100644
index 00000000..4b3e9483
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0122/__init__.py
@@ -0,0 +1,11 @@
+
+from sleekxmpp.plugins.base import register_plugin
+from sleekxmpp.plugins.xep_0122.stanza import FormValidation
+from sleekxmpp.plugins.xep_0122.data_validation import XEP_0122
+
+
+register_plugin(XEP_0122)
+
+
+# Retain some backwards compatibility
+xep_0122 = XEP_0122
diff --git a/sleekxmpp/plugins/xep_0122/data_validation.py b/sleekxmpp/plugins/xep_0122/data_validation.py
new file mode 100644
index 00000000..ec2cdfcc
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0122/data_validation.py
@@ -0,0 +1,19 @@
+from sleekxmpp.xmlstream import register_stanza_plugin
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.plugins.xep_0004 import stanza
+from sleekxmpp.plugins.xep_0004.stanza import FormField
+from sleekxmpp.plugins.xep_0122.stanza import FormValidation
+
+
+class XEP_0122(BasePlugin):
+ """
+ XEP-0004: Data Forms
+ """
+
+ name = 'xep_0122'
+ description = 'XEP-0122: Data Forms Validation'
+ dependencies = set(['xep_0004'])
+ stanza = stanza
+
+ def plugin_init(self):
+ register_stanza_plugin(FormField, FormValidation)
diff --git a/sleekxmpp/plugins/xep_0122/stanza.py b/sleekxmpp/plugins/xep_0122/stanza.py
new file mode 100644
index 00000000..bc3c177a
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0122/stanza.py
@@ -0,0 +1,94 @@
+
+from sleekxmpp.xmlstream import ElementBase, ET
+
+
+class FormValidation(ElementBase):
+ """
+ Validation values for form fields.
+
+ Example:
+
+ <field var='evt.date' type='text-single' label='Event Date/Time'>
+ <validate xmlns='http://jabber.org/protocol/xdata-validate'
+ datatype='xs:dateTime'/>
+ <value>2003-10-06T11:22:00-07:00</value>
+ </field>
+
+ Questions:
+ Should this look at the datatype value and convert the range values as appropriate?
+ Should this stanza provide a pass/fail for a value from the field, or convert field value to datatype?
+ """
+
+ namespace = 'http://jabber.org/protocol/xdata-validate'
+ name = 'validate'
+ plugin_attrib = 'validate'
+ interfaces = {'datatype', 'basic', 'open', 'range', 'regex', }
+ sub_interfaces = {'basic', 'open', 'range', 'regex', }
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+
+ def _add_field(self, name):
+ self.remove_all()
+ item_xml = ET.Element('{%s}%s' % (self.namespace, name))
+ self.xml.append(item_xml)
+ return item_xml
+
+ def set_basic(self, value):
+ if value:
+ self._add_field('basic')
+ else:
+ del self['basic']
+
+ def set_open(self, value):
+ if value:
+ self._add_field('open')
+ else:
+ del self['open']
+
+ def set_regex(self, regex):
+ if regex:
+ _regex = self._add_field('regex')
+ _regex.text = regex
+ else:
+ del self['regex']
+
+ def set_range(self, value, minimum=None, maximum=None):
+ if value:
+ _range = self._add_field('range')
+ _range.attrib['min'] = str(minimum)
+ _range.attrib['max'] = str(maximum)
+ else:
+ del self['range']
+
+ def remove_all(self, except_tag=None):
+ for a in self.sub_interfaces:
+ if a != except_tag:
+ del self[a]
+
+ def get_basic(self):
+ present = self.xml.find('{%s}basic' % self.namespace)
+ return present is not None
+
+ def get_open(self):
+ present = self.xml.find('{%s}open' % self.namespace)
+ return present is not None
+
+ def get_regex(self):
+ present = self.xml.find('{%s}regex' % self.namespace)
+ if present is not None:
+ return present.text
+
+ return False
+
+ def get_range(self):
+ present = self.xml.find('{%s}range' % self.namespace)
+ if present is not None:
+ attributes = present.attrib
+ return_value = dict()
+ if 'min' in attributes:
+ return_value['minimum'] = attributes['min']
+ if 'max' in attributes:
+ return_value['maximum'] = attributes['max']
+ return return_value
+
+ return False
diff --git a/sleekxmpp/plugins/xep_0202/time.py b/sleekxmpp/plugins/xep_0202/time.py
index d5b3af37..4e48eae8 100644
--- a/sleekxmpp/plugins/xep_0202/time.py
+++ b/sleekxmpp/plugins/xep_0202/time.py
@@ -72,9 +72,10 @@ class XEP_0202(BasePlugin):
Arguments:
iq -- The Iq time request stanza.
"""
- iq.reply()
- iq['entity_time']['time'] = self.local_time(iq['to'])
- iq.send()
+ if iq['type'] == 'get':
+ iq.reply()
+ iq['entity_time']['time'] = self.local_time(iq['to'])
+ iq.send()
def get_entity_time(self, to, ifrom=None, **iqargs):
"""
diff --git a/sleekxmpp/plugins/xep_0323/sensordata.py b/sleekxmpp/plugins/xep_0323/sensordata.py
index 87a62980..a3d4cf34 100644
--- a/sleekxmpp/plugins/xep_0323/sensordata.py
+++ b/sleekxmpp/plugins/xep_0323/sensordata.py
@@ -482,10 +482,10 @@ class XEP_0323(BasePlugin):
if result == "done":
self.sessions[session]["commTimers"][nodeId].cancel()
self.sessions[session]["nodeDone"][nodeId] = True
- msg['fields']['done'] = 'true'
if (self._all_nodes_done(session)):
# The session is complete, delete it
del self.sessions[session]
+ msg['fields']['done'] = 'true'
else:
# Restart comm timer
self.sessions[session]["commTimers"][nodeId].reset()
diff --git a/sleekxmpp/plugins/xep_0332/http.py b/sleekxmpp/plugins/xep_0332/http.py
index 03d88b65..70bcafa6 100644
--- a/sleekxmpp/plugins/xep_0332/http.py
+++ b/sleekxmpp/plugins/xep_0332/http.py
@@ -108,6 +108,8 @@ class XEP_0332(BasePlugin):
iq['http-req']['method'] = method
iq['http-req']['resource'] = resource
iq['http-req']['version'] = '1.1' # TODO: set this implicitly
+ if 'id' in kwargs:
+ iq['id'] = kwargs["id"]
if data is not None:
iq['http-req']['data'] = data
return iq.send(
@@ -127,6 +129,8 @@ class XEP_0332(BasePlugin):
iq['http-resp']['code'] = code
iq['http-resp']['message'] = message
iq['http-resp']['version'] = '1.1' # TODO: set this implicitly
+ if 'id' in kwargs:
+ iq['id'] = kwargs["id"]
if data is not None:
iq['http-resp']['data'] = data
return iq.send(
@@ -145,6 +149,8 @@ class XEP_0332(BasePlugin):
iq['error']['code'] = ecode
iq['error']['type'] = etype
iq['error']['condition'] = econd
+ if 'id' in kwargs:
+ iq['id'] = kwargs["id"]
return iq.send(
timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py
index 2a1db717..337598ac 100644
--- a/sleekxmpp/thirdparty/__init__.py
+++ b/sleekxmpp/thirdparty/__init__.py
@@ -10,3 +10,4 @@ except:
from sleekxmpp.thirdparty import socks
from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso
+from sleekxmpp.thirdparty.orderedset import OrderedSet
diff --git a/sleekxmpp/thirdparty/orderedset.py b/sleekxmpp/thirdparty/orderedset.py
new file mode 100644
index 00000000..f6642db3
--- /dev/null
+++ b/sleekxmpp/thirdparty/orderedset.py
@@ -0,0 +1,89 @@
+# Copyright (c) 2009 Raymond Hettinger
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+
+import collections
+
+class OrderedSet(collections.MutableSet):
+
+ def __init__(self, iterable=None):
+ self.end = end = []
+ end += [None, end, end] # sentinel node for doubly linked list
+ self.map = {} # key --> [key, prev, next]
+ if iterable is not None:
+ self |= iterable
+
+ def __len__(self):
+ return len(self.map)
+
+ def __contains__(self, key):
+ return key in self.map
+
+ def add(self, key):
+ if key not in self.map:
+ end = self.end
+ curr = end[1]
+ curr[2] = end[1] = self.map[key] = [key, curr, end]
+
+ def discard(self, key):
+ if key in self.map:
+ key, prev, next = self.map.pop(key)
+ prev[2] = next
+ next[1] = prev
+
+ def __iter__(self):
+ end = self.end
+ curr = end[2]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[2]
+
+ def __reversed__(self):
+ end = self.end
+ curr = end[1]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[1]
+
+ def pop(self, last=True):
+ if not self:
+ raise KeyError('set is empty')
+ key = self.end[1][0] if last else self.end[2][0]
+ self.discard(key)
+ return key
+
+ def __repr__(self):
+ if not self:
+ return '%s()' % (self.__class__.__name__,)
+ return '%s(%r)' % (self.__class__.__name__, list(self))
+
+ def __eq__(self, other):
+ if isinstance(other, OrderedSet):
+ return len(self) == len(other) and list(self) == list(other)
+ return set(self) == set(other)
+
+
+if __name__ == '__main__':
+ s = OrderedSet('abracadaba')
+ t = OrderedSet('simsalabim')
+ print(s | t)
+ print(s & t)
+ print(s - t) \ No newline at end of file
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
index 11c8dd67..c2e0f718 100644
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ b/sleekxmpp/xmlstream/stanzabase.py
@@ -563,7 +563,7 @@ class ElementBase(object):
.. versionadded:: 1.0-Beta1
"""
- values = {}
+ values = OrderedDict()
values['lang'] = self['lang']
for interface in self.interfaces:
if isinstance(self[interface], JID):
diff --git a/tests/test_stanza_xep_0004.py b/tests/test_stanza_xep_0004.py
index 9056c663..b87afb24 100644
--- a/tests/test_stanza_xep_0004.py
+++ b/tests/test_stanza_xep_0004.py
@@ -11,8 +11,8 @@ class TestDataForms(SleekTest):
def setUp(self):
register_stanza_plugin(Message, xep_0004.Form)
- register_stanza_plugin(xep_0004.Form, xep_0004.FormField)
- register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption)
+ register_stanza_plugin(xep_0004.Form, xep_0004.FormField, iterable=True)
+ register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption, iterable=True)
def testMultipleInstructions(self):
"""Testing using multiple instructions elements in a data form."""
@@ -68,7 +68,7 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
- form['fields'] = fields
+ form.set_fields(fields)
self.check(msg, """
@@ -141,13 +141,13 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
- form['fields'] = fields
+ form.set_fields(fields)
form['type'] = 'submit'
- form['values'] = {'f1': 'username',
+ form.set_values({'f1': 'username',
'f2': 'hunter2',
'f3': 'A long\nmultiline\nmessage',
- 'f4': 'cool'}
+ 'f4': 'cool'})
self.check(form, """
<x xmlns="jabber:x:data" type="submit">
@@ -189,7 +189,7 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
- form['fields'] = fields
+ form.set_fields(fields)
form['type'] = 'cancel'
@@ -197,5 +197,52 @@ class TestDataForms(SleekTest):
<x xmlns="jabber:x:data" type="cancel" />
""")
+ def testReported(self):
+ msg = self.Message()
+ form = msg['form']
+ form['type'] = 'result'
+
+ form.add_reported(var='f1', ftype='text-single', label='Username')
+
+ form.add_item({'f1': 'username@example.org'})
+
+ self.check(msg, """
+ <message>
+ <x xmlns="jabber:x:data" type="result">
+ <reported>
+ <field var="f1" type="text-single" label="Username" />
+ </reported>
+ <item>
+ <field var="f1">
+ <value>username@example.org</value>
+ </field>
+ </item>
+ </x>
+ </message>
+ """)
+
+ def testSetReported(self):
+ msg = self.Message()
+ form = msg['form']
+ form['type'] = 'result'
+
+ reported = {'f1': {
+ 'var': 'f1',
+ 'type': 'text-single',
+ 'label': 'Username'
+ }}
+
+ form.set_reported(reported)
+
+ self.check(msg, """
+ <message>
+ <x xmlns="jabber:x:data" type="result">
+ <reported>
+ <field var="f1" type="text-single" label="Username" />
+ </reported>
+ </x>
+ </message>
+ """)
+
suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms)
diff --git a/tests/test_stanza_xep_0122.py b/tests/test_stanza_xep_0122.py
new file mode 100644
index 00000000..fca49bbb
--- /dev/null
+++ b/tests/test_stanza_xep_0122.py
@@ -0,0 +1,189 @@
+import unittest
+
+from sleekxmpp import Message
+from sleekxmpp.test import SleekTest
+import sleekxmpp.plugins.xep_0004 as xep_0004
+import sleekxmpp.plugins.xep_0122 as xep_0122
+from sleekxmpp.xmlstream import register_stanza_plugin
+
+
+class TestDataForms(SleekTest):
+
+ def setUp(self):
+ register_stanza_plugin(Message, xep_0004.Form)
+ register_stanza_plugin(xep_0004.Form, xep_0004.FormField, iterable=True)
+ register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption, iterable=True)
+ register_stanza_plugin(xep_0004.FormField, xep_0122.FormValidation)
+
+ def test_basic_validation(self):
+ """Testing basic validation setting and getting."""
+ msg = self.Message()
+ form = msg['form']
+ field = form.addField(var='f1',
+ ftype='text-single',
+ label='Text',
+ desc='A text field',
+ required=True,
+ value='Some text!')
+
+ validation = field['validate']
+ validation['datatype'] = 'xs:string'
+ validation.set_basic(True)
+
+ self.check(msg, """
+ <message>
+ <x xmlns="jabber:x:data" type="form">
+ <field var="f1" type="text-single" label="Text">
+ <desc>A text field</desc>
+ <required />
+ <value>Some text!</value>
+ <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:string">
+ <basic/>
+ </validate>
+ </field>
+ </x>
+ </message>
+ """)
+
+ self.assertTrue(validation.get_basic())
+ self.assertFalse(validation.get_open())
+ self.assertFalse(validation.get_range())
+ self.assertFalse(validation.get_regex())
+
+ def test_open_validation(self):
+ """Testing open validation setting and getting."""
+ msg = self.Message()
+ form = msg['form']
+ field = form.addField(var='f1',
+ ftype='text-single',
+ label='Text',
+ desc='A text field',
+ required=True,
+ value='Some text!')
+
+ validation = field['validate']
+ validation.set_open(True)
+
+ self.check(msg, """
+ <message>
+ <x xmlns="jabber:x:data" type="form">
+ <field var="f1" type="text-single" label="Text">
+ <desc>A text field</desc>
+ <required />
+ <value>Some text!</value>
+ <validate xmlns="http://jabber.org/protocol/xdata-validate">
+ <open />
+ </validate>
+ </field>
+ </x>
+ </message>
+ """)
+
+ self.assertFalse(validation.get_basic())
+ self.assertTrue(validation.get_open())
+ self.assertFalse(validation.get_range())
+ self.assertFalse(validation.get_regex())
+
+ def test_regex_validation(self):
+ """Testing regex validation setting and getting."""
+ msg = self.Message()
+ form = msg['form']
+ field = form.addField(var='f1',
+ ftype='text-single',
+ label='Text',
+ desc='A text field',
+ required=True,
+ value='Some text!')
+
+ regex_value = '[0-9]+'
+
+ validation = field['validate']
+ validation.set_regex(regex_value)
+
+ self.check(msg, """
+ <message>
+ <x xmlns="jabber:x:data" type="form">
+ <field var="f1" type="text-single" label="Text">
+ <desc>A text field</desc>
+ <required />
+ <value>Some text!</value>
+ <validate xmlns="http://jabber.org/protocol/xdata-validate">
+ <regex>[0-9]+</regex>
+ </validate>
+ </field>
+ </x>
+ </message>
+ """)
+
+ self.assertFalse(validation.get_basic())
+ self.assertFalse(validation.get_open())
+ self.assertFalse(validation.get_range())
+ self.assertTrue(validation.get_regex())
+
+ self.assertEqual(regex_value, validation.get_regex())
+
+ def test_range_validation(self):
+ """Testing range validation setting and getting."""
+ msg = self.Message()
+ form = msg['form']
+ field = form.addField(var='f1',
+ ftype='text-single',
+ label='Text',
+ desc='A text field',
+ required=True,
+ value='Some text!')
+
+ validation = field['validate']
+ validation.set_range(True, minimum=0, maximum=10)
+
+ self.check(msg, """
+ <message>
+ <x xmlns="jabber:x:data" type="form">
+ <field var="f1" type="text-single" label="Text">
+ <desc>A text field</desc>
+ <required />
+ <value>Some text!</value>
+ <validate xmlns="http://jabber.org/protocol/xdata-validate">
+ <range min="0" max="10" />
+ </validate>
+ </field>
+ </x>
+ </message>
+ """)
+
+ self.assertDictEqual(dict(minimum=str(0), maximum=str(10)), validation.get_range())
+
+ def test_reported_field_validation(self):
+ """
+ Testing adding validation to the field when it's stored in the reported.
+ :return:
+ """
+ msg = self.Message()
+ form = msg['form']
+ field = form.addReported(var='f1', ftype='text-single', label='Text')
+ validation = field['validate']
+ validation.set_basic(True)
+
+ form.addItem({'f1': 'Some text!'})
+
+ self.check(msg, """
+ <message>
+ <x xmlns="jabber:x:data" type="form">
+ <reported>
+ <field var="f1" type="text-single" label="Text">
+ <validate xmlns="http://jabber.org/protocol/xdata-validate">
+ <basic />
+ </validate>
+ </field>
+ </reported>
+ <item>
+ <field var="f1">
+ <value>Some text!</value>
+ </field>
+ </item>
+ </x>
+ </message>
+ """)
+
+
+suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms)
diff --git a/tests/test_stream_xep_0050.py b/tests/test_stream_xep_0050.py
index 8fd09a8d..e38c4add 100644
--- a/tests/test_stream_xep_0050.py
+++ b/tests/test_stream_xep_0050.py
@@ -119,7 +119,7 @@ class TestAdHocCommands(SleekTest):
def handle_command(iq, session):
def handle_form(form, session):
- results.append(form['values']['foo'])
+ results.append(form.get_values()['foo'])
session['payload'] = None
form = self.xmpp['xep_0004'].makeForm('form')
@@ -192,11 +192,11 @@ class TestAdHocCommands(SleekTest):
def handle_command(iq, session):
def handle_step2(form, session):
- results.append(form['values']['bar'])
+ results.append(form.get_values()['bar'])
session['payload'] = None
def handle_step1(form, session):
- results.append(form['values']['foo'])
+ results.append(form.get_values()['foo'])
form = self.xmpp['xep_0004'].makeForm('form')
form.addField(var='bar', ftype='text-single', label='Bar')
@@ -428,7 +428,7 @@ class TestAdHocCommands(SleekTest):
def handle_form(forms, session):
for form in forms:
- results.append(form['values']['FORM_TYPE'])
+ results.append(form.get_values()['FORM_TYPE'])
session['payload'] = None
form1 = self.xmpp['xep_0004'].makeForm('form')