Changeset 160:33ef849a77d8 for wokkel


Ignore:
Timestamp:
Jan 8, 2012, 9:26:02 AM (9 years ago)
Author:
Ralph Meijer <ralphm@…>
Branch:
default
Message:

Use symbolic constants instead of integers MUC status code.

Instead of using normal constant values for representing MUC status codes,
UserPresence now uses twisted.python.constants to define these. This
makes code better to understand and helps debugging.

If a user presence includes one or more status codes, they are stored in the
mucStatuses attribute, as an instance of Statuses. This replaces the
former statusCodes attribute.

Location:
wokkel
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • wokkel/compat.py

    r96 r160  
    33# Copyright (c) Twisted Matrix Laboratories.
    44# See LICENSE for details.
     5
     6"""
     7Compatibility module to provide backwards compatibility with Twisted features.
     8"""
     9
     10__all__ = ['BootstrapMixin', 'XmlStreamServerFactory', 'IQ',
     11           'NamedConstant', 'ValueConstant', 'Names', 'Values']
     12
     13from itertools import count
    514
    615from twisted.internet import protocol
     
    116125            # Patch the XmlStream instance so that it has a _callLater
    117126            self._xmlstream._callLater = reactor.callLater
     127
     128
     129
     130_unspecified = object()
     131_constantOrder = count().next
     132
     133
     134class _Constant(object):
     135    """
     136    @ivar _index: A C{int} allocated from a shared counter in order to keep
     137        track of the order in which L{_Constant}s are instantiated.
     138
     139    @ivar name: A C{str} giving the name of this constant; only set once the
     140        constant is initialized by L{_ConstantsContainer}.
     141
     142    @ivar _container: The L{_ConstantsContainer} subclass this constant belongs
     143        to; only set once the constant is initialized by that subclass.
     144    """
     145    def __init__(self):
     146        self._index = _constantOrder()
     147
     148
     149    def __get__(self, oself, cls):
     150        """
     151        Ensure this constant has been initialized before returning it.
     152        """
     153        cls._initializeEnumerants()
     154        return self
     155
     156
     157    def __repr__(self):
     158        """
     159        Return text identifying both which constant this is and which collection
     160        it belongs to.
     161        """
     162        return "<%s=%s>" % (self._container.__name__, self.name)
     163
     164
     165    def _realize(self, container, name, value):
     166        """
     167        Complete the initialization of this L{_Constant}.
     168
     169        @param container: The L{_ConstantsContainer} subclass this constant is
     170            part of.
     171
     172        @param name: The name of this constant in its container.
     173
     174        @param value: The value of this constant; not used, as named constants
     175            have no value apart from their identity.
     176        """
     177        self._container = container
     178        self.name = name
     179
     180
     181
     182class _EnumerantsInitializer(object):
     183    """
     184    L{_EnumerantsInitializer} is a descriptor used to initialize a cache of
     185    objects representing named constants for a particular L{_ConstantsContainer}
     186    subclass.
     187    """
     188    def __get__(self, oself, cls):
     189        """
     190        Trigger the initialization of the enumerants cache on C{cls} and then
     191        return it.
     192        """
     193        cls._initializeEnumerants()
     194        return cls._enumerants
     195
     196
     197
     198class _ConstantsContainer(object):
     199    """
     200    L{_ConstantsContainer} is a class with attributes used as symbolic
     201    constants.  It is up to subclasses to specify what kind of constants are
     202    allowed.
     203
     204    @cvar _constantType: Specified by a L{_ConstantsContainer} subclass to
     205        specify the type of constants allowed by that subclass.
     206
     207    @cvar _enumerantsInitialized: A C{bool} tracking whether C{_enumerants} has
     208        been initialized yet or not.
     209
     210    @cvar _enumerants: A C{dict} mapping the names of constants (eg
     211        L{NamedConstant} instances) found in the class definition to those
     212        instances.  This is initialized via the L{_EnumerantsInitializer}
     213        descriptor the first time it is accessed.
     214    """
     215    _constantType = None
     216
     217    _enumerantsInitialized = False
     218    _enumerants = _EnumerantsInitializer()
     219
     220    def __new__(cls):
     221        """
     222        Classes representing constants containers are not intended to be
     223        instantiated.
     224
     225        The class object itself is used directly.
     226        """
     227        raise TypeError("%s may not be instantiated." % (cls.__name__,))
     228
     229
     230    def _initializeEnumerants(cls):
     231        """
     232        Find all of the L{NamedConstant} instances in the definition of C{cls},
     233        initialize them with constant values, and build a mapping from their
     234        names to them to attach to C{cls}.
     235        """
     236        if not cls._enumerantsInitialized:
     237            constants = []
     238            for (name, descriptor) in cls.__dict__.iteritems():
     239                if isinstance(descriptor, cls._constantType):
     240                    constants.append((descriptor._index, name, descriptor))
     241            enumerants = {}
     242            for (index, enumerant, descriptor) in constants:
     243                value = cls._constantFactory(enumerant)
     244                descriptor._realize(cls, enumerant, value)
     245                enumerants[enumerant] = descriptor
     246            # Replace the _enumerants descriptor with the result so future
     247            # access will go directly to the values.  The _enumerantsInitialized
     248            # flag is still necessary because NamedConstant.__get__ may also
     249            # call this method.
     250            cls._enumerants = enumerants
     251            cls._enumerantsInitialized = True
     252    _initializeEnumerants = classmethod(_initializeEnumerants)
     253
     254
     255    def _constantFactory(cls, name):
     256        """
     257        Construct the value for a new constant to add to this container.
     258
     259        @param name: The name of the constant to create.
     260
     261        @return: L{NamedConstant} instances have no value apart from identity,
     262            so return a meaningless dummy value.
     263        """
     264        return _unspecified
     265    _constantFactory = classmethod(_constantFactory)
     266
     267
     268    def lookupByName(cls, name):
     269        """
     270        Retrieve a constant by its name or raise a C{ValueError} if there is no
     271        constant associated with that name.
     272
     273        @param name: A C{str} giving the name of one of the constants defined by
     274            C{cls}.
     275
     276        @raise ValueError: If C{name} is not the name of one of the constants
     277            defined by C{cls}.
     278
     279        @return: The L{NamedConstant} associated with C{name}.
     280        """
     281        if name in cls._enumerants:
     282            return getattr(cls, name)
     283        raise ValueError(name)
     284    lookupByName = classmethod(lookupByName)
     285
     286
     287    def iterconstants(cls):
     288        """
     289        Iteration over a L{Names} subclass results in all of the constants it
     290        contains.
     291
     292        @return: an iterator the elements of which are the L{NamedConstant}
     293            instances defined in the body of this L{Names} subclass.
     294        """
     295        constants = cls._enumerants.values()
     296        constants.sort(key=lambda descriptor: descriptor._index)
     297        return iter(constants)
     298    iterconstants = classmethod(iterconstants)
     299
     300
     301
     302class NamedConstant(_Constant):
     303    """
     304    L{NamedConstant} defines an attribute to be a named constant within a
     305    collection defined by a L{Names} subclass.
     306
     307    L{NamedConstant} is only for use in the definition of L{Names}
     308    subclasses.  Do not instantiate L{NamedConstant} elsewhere and do not
     309    subclass it.
     310    """
     311
     312
     313
     314class Names(_ConstantsContainer):
     315    """
     316    A L{Names} subclass contains constants which differ only in their names and
     317    identities.
     318    """
     319    _constantType = NamedConstant
     320
     321
     322
     323class ValueConstant(_Constant):
     324    """
     325    L{ValueConstant} defines an attribute to be a named constant within a
     326    collection defined by a L{Values} subclass.
     327
     328    L{ValueConstant} is only for use in the definition of L{Values} subclasses.
     329    Do not instantiate L{ValueConstant} elsewhere and do not subclass it.
     330    """
     331    def __init__(self, value):
     332        _Constant.__init__(self)
     333        self.value = value
     334
     335
     336
     337class Values(_ConstantsContainer):
     338    """
     339    A L{Values} subclass contains constants which are associated with arbitrary
     340    values.
     341    """
     342    _constantType = ValueConstant
     343
     344    def lookupByValue(cls, value):
     345        """
     346        Retrieve a constant by its value or raise a C{ValueError} if there is no
     347        constant associated with that value.
     348
     349        @param value: The value of one of the constants defined by C{cls}.
     350
     351        @raise ValueError: If C{value} is not the value of one of the constants
     352            defined by C{cls}.
     353
     354        @return: The L{ValueConstant} associated with C{value}.
     355        """
     356        for constant in cls.iterconstants():
     357            if constant.value == value:
     358                return constant
     359        raise ValueError(value)
     360    lookupByValue = classmethod(lookupByValue)
  • wokkel/iwokkel.py

    r158 r160  
    806806class IMUCClient(Interface):
    807807    """
    808     Multi-User Chat Client
     808    Multi-User Chat Client.
    809809
    810810    A client interface to XEP-045 : http://xmpp.org/extensions/xep-0045.html
    811 
    812811    """
    813812
     
    10281027        @type sender: L{jid.JID}
    10291028        """
     1029
     1030
     1031class IMUCStatuses(Interface):
     1032    """
     1033    Interface for a container of Multi-User Chat status conditions.
     1034    """
     1035
     1036    def __contains__(key):
     1037        """
     1038        Return if a status exists in the container.
     1039        """
     1040
     1041
     1042    def __iter__():
     1043        """
     1044        Return an iterator over the status codes.
     1045        """
     1046
     1047
     1048    def __len__():
     1049        """
     1050        Return the number of status conditions.
     1051        """
  • wokkel/muc.py

    r156 r160  
    88
    99This protocol is specified in
    10 U{XEP-0045<http://www.xmpp.org/extensions/xep-0045.html>}.
     10U{XEP-0045<http://xmpp.org/extensions/xep-0045.html>}.
    1111"""
    1212from dateutil.tz import tzutc
     
    1818from twisted.words.xish import domish
    1919
    20 from wokkel import data_form, generic, xmppim
     20from wokkel import data_form, generic, iwokkel, xmppim
     21from wokkel.compat import Values, ValueConstant
    2122from wokkel.delay import Delay, DelayMixin
    2223from wokkel.subprotocols import XMPPHandler
     
    4142
    4243DEFER_TIMEOUT = 30 # basic timeout is 30 seconds
     44
     45class STATUS_CODE(Values):
     46    REALJID_PUBLIC = ValueConstant(100)
     47    AFFILIATION_CHANGED = ValueConstant(101)
     48    UNAVAILABLE_SHOWN = ValueConstant(102)
     49    UNAVAILABLE_NOT_SHOWN = ValueConstant(103)
     50    CONFIGURATION_CHANGED = ValueConstant(104)
     51    SELF_PRESENCE = ValueConstant(110)
     52    LOGGING_ENABLED = ValueConstant(170)
     53    LOGGING_DISABLED = ValueConstant(171)
     54    NON_ANONYMOUS = ValueConstant(172)
     55    SEMI_ANONYMOUS = ValueConstant(173)
     56    FULLY_ANONYMOUS = ValueConstant(174)
     57    ROOM_CREATED = ValueConstant(201)
     58    NICK_ASSIGNED = ValueConstant(210)
     59    BANNED = ValueConstant(301)
     60    NEW_NICK = ValueConstant(303)
     61    KICKED = ValueConstant(307)
     62    REMOVED_AFFILIATION = ValueConstant(321)
     63    REMOVED_MEMBERSHIP = ValueConstant(322)
     64    REMOVED_SHUTDOWN = ValueConstant(332)
     65
     66
     67class Statuses(set):
     68    """
     69    Container of MUC status conditions.
     70
     71    This is currently implemented as a set of constant values from
     72    L{STATUS_CODE}. Instances of this class provide L{IMUCStatuses}, that
     73    defines the supported operations. Even though this class currently derives
     74    from C{set}, future versions might not. This provides an upgrade path to
     75    cater for extensible status conditions, as defined in
     76    U{XEP-0306<http://xmpp.org/extensions/xep-0306.html>}.
     77    """
     78    implements(iwokkel.IMUCStatuses)
    4379
    4480
     
    354390    """
    355391    Availability presence sent from MUC service to client.
     392
     393    @ivar affiliation: Affiliation of the entity to the room.
     394    @type affiliation: C{unicode}
     395
     396    @ivar role: Role of the entity in the room.
     397    @type role: C{unicode}
     398
     399    @ivar entity: The real JID of the entity this presence is from.
     400    @type entity: L{jid.JID}
     401
     402    @ivar mucStatuses: Set of one or more status codes from L{STATUS_CODE}.
     403        See L{Statuses} for usage notes.
     404    @type mucStatuses: L{Statuses}
     405
     406    @ivar nick: The nick name of the entity in the room.
     407    @type nick: C{unicode}
    356408    """
    357409
     
    361413    nick = None
    362414
    363     statusCodes = None
     415    mucStatuses = None
    364416
    365417    childParsers = {(NS_MUC_USER, 'x'): '_childParser_mucUser'}
    366418
     419    def __init__(self, *args, **kwargs):
     420        self.mucStatuses = Statuses()
     421        xmppim.AvailabilityPresence.__init__(self, *args, **kwargs)
     422
     423
    367424    def _childParser_mucUser(self, element):
    368         statusCodes = set()
    369 
     425        """
     426        Parse the MUC user extension element.
     427        """
    370428        for child in element.elements():
    371429            if child.uri != NS_MUC_USER:
     
    374432            elif child.name == 'status':
    375433                try:
    376                     statusCode = int(child.getAttribute('code'))
     434                    value = int(child.getAttribute('code'))
     435                    statusCode = STATUS_CODE.lookupByValue(value)
    377436                except (TypeError, ValueError):
    378437                    continue
    379438
    380                 statusCodes.add(statusCode)
     439                self.mucStatuses.add(statusCode)
    381440
    382441            elif child.name == 'item':
     
    393452            # TODO: destroy
    394453
    395         if statusCodes:
    396             self.statusCodes = statusCodes
    397454
    398455
  • wokkel/test/test_compat.py

    r96 r160  
    1 # Copyright (c) 2001-2008 Twisted Matrix Laboratories.
     1# Copyright (c) Twisted Matrix Laboratories.
    22# Copyright (c) Ralph Meijer.
    33# See LICENSE for details.
     
    1414from twisted.words.xish import utility
    1515from twisted.words.protocols.jabber import xmlstream
     16
    1617from wokkel.compat import BootstrapMixin, IQ, XmlStreamServerFactory
     18from wokkel.compat import NamedConstant, Names, ValueConstant, Values
    1719
    1820class DummyProtocol(protocol.Protocol, utility.EventDispatcher):
     
    208210        self.assertFalse(xs.iqDeferreds)
    209211        return d
     212
     213
     214
     215class NamedConstantTests(unittest.TestCase):
     216    """
     217    Tests for the L{twisted.python.constants.NamedConstant} class which is used
     218    to represent individual values.
     219    """
     220    def setUp(self):
     221        """
     222        Create a dummy container into which constants can be placed.
     223        """
     224        class foo(Names):
     225            pass
     226        self.container = foo
     227
     228
     229    def test_name(self):
     230        """
     231        The C{name} attribute of a L{NamedConstant} refers to the value passed
     232        for the C{name} parameter to C{_realize}.
     233        """
     234        name = NamedConstant()
     235        name._realize(self.container, "bar", None)
     236        self.assertEqual("bar", name.name)
     237
     238
     239    def test_representation(self):
     240        """
     241        The string representation of an instance of L{NamedConstant} includes
     242        the container the instances belongs to as well as the instance's name.
     243        """
     244        name = NamedConstant()
     245        name._realize(self.container, "bar", None)
     246        self.assertEqual("<foo=bar>", repr(name))
     247
     248
     249    def test_equality(self):
     250        """
     251        A L{NamedConstant} instance compares equal to itself.
     252        """
     253        name = NamedConstant()
     254        name._realize(self.container, "bar", None)
     255        self.assertTrue(name == name)
     256        self.assertFalse(name != name)
     257
     258
     259    def test_nonequality(self):
     260        """
     261        Two different L{NamedConstant} instances do not compare equal to each
     262        other.
     263        """
     264        first = NamedConstant()
     265        first._realize(self.container, "bar", None)
     266        second = NamedConstant()
     267        second._realize(self.container, "bar", None)
     268        self.assertFalse(first == second)
     269        self.assertTrue(first != second)
     270
     271
     272    def test_hash(self):
     273        """
     274        Because two different L{NamedConstant} instances do not compare as equal
     275        to each other, they also have different hashes to avoid collisions when
     276        added to a C{dict} or C{set}.
     277        """
     278        first = NamedConstant()
     279        first._realize(self.container, "bar", None)
     280        second = NamedConstant()
     281        second._realize(self.container, "bar", None)
     282        self.assertNotEqual(hash(first), hash(second))
     283
     284
     285
     286class _ConstantsTestsMixin(object):
     287    """
     288    Mixin defining test helpers common to multiple types of constants
     289    collections.
     290    """
     291    def _notInstantiableTest(self, name, cls):
     292        """
     293        Assert that an attempt to instantiate the constants class raises
     294        C{TypeError}.
     295
     296        @param name: A C{str} giving the name of the constants collection.
     297        @param cls: The constants class to test.
     298        """
     299        exc = self.assertRaises(TypeError, cls)
     300        self.assertEqual(name + " may not be instantiated.", str(exc))
     301
     302
     303
     304class NamesTests(unittest.TestCase, _ConstantsTestsMixin):
     305    """
     306    Tests for L{twisted.python.constants.Names}, a base class for containers of
     307    related constaints.
     308    """
     309    def setUp(self):
     310        """
     311        Create a fresh new L{Names} subclass for each unit test to use.  Since
     312        L{Names} is stateful, re-using the same subclass across test methods
     313        makes exercising all of the implementation code paths difficult.
     314        """
     315        class METHOD(Names):
     316            """
     317            A container for some named constants to use in unit tests for
     318            L{Names}.
     319            """
     320            GET = NamedConstant()
     321            PUT = NamedConstant()
     322            POST = NamedConstant()
     323            DELETE = NamedConstant()
     324
     325        self.METHOD = METHOD
     326
     327
     328    def test_notInstantiable(self):
     329        """
     330        A subclass of L{Names} raises C{TypeError} if an attempt is made to
     331        instantiate it.
     332        """
     333        self._notInstantiableTest("METHOD", self.METHOD)
     334
     335
     336    def test_symbolicAttributes(self):
     337        """
     338        Each name associated with a L{NamedConstant} instance in the definition
     339        of a L{Names} subclass is available as an attribute on the resulting
     340        class.
     341        """
     342        self.assertTrue(hasattr(self.METHOD, "GET"))
     343        self.assertTrue(hasattr(self.METHOD, "PUT"))
     344        self.assertTrue(hasattr(self.METHOD, "POST"))
     345        self.assertTrue(hasattr(self.METHOD, "DELETE"))
     346
     347
     348    def test_withoutOtherAttributes(self):
     349        """
     350        As usual, names not defined in the class scope of a L{Names}
     351        subclass are not available as attributes on the resulting class.
     352        """
     353        self.assertFalse(hasattr(self.METHOD, "foo"))
     354
     355
     356    def test_representation(self):
     357        """
     358        The string representation of a constant on a L{Names} subclass includes
     359        the name of the L{Names} subclass and the name of the constant itself.
     360        """
     361        self.assertEqual("<METHOD=GET>", repr(self.METHOD.GET))
     362
     363
     364    def test_lookupByName(self):
     365        """
     366        Constants can be looked up by name using L{Names.lookupByName}.
     367        """
     368        method = self.METHOD.lookupByName("GET")
     369        self.assertIdentical(self.METHOD.GET, method)
     370
     371
     372    def test_notLookupMissingByName(self):
     373        """
     374        Names not defined with a L{NamedConstant} instance cannot be looked up
     375        using L{Names.lookupByName}.
     376        """
     377        self.assertRaises(ValueError, self.METHOD.lookupByName, "lookupByName")
     378        self.assertRaises(ValueError, self.METHOD.lookupByName, "__init__")
     379        self.assertRaises(ValueError, self.METHOD.lookupByName, "foo")
     380
     381
     382    def test_name(self):
     383        """
     384        The C{name} attribute of one of the named constants gives that
     385        constant's name.
     386        """
     387        self.assertEqual("GET", self.METHOD.GET.name)
     388
     389
     390    def test_attributeIdentity(self):
     391        """
     392        Repeated access of an attribute associated with a L{NamedConstant} value
     393        in a L{Names} subclass results in the same object.
     394        """
     395        self.assertIdentical(self.METHOD.GET, self.METHOD.GET)
     396
     397
     398    def test_iterconstants(self):
     399        """
     400        L{Names.iterconstants} returns an iterator over all of the constants
     401        defined in the class, in the order they were defined.
     402        """
     403        constants = list(self.METHOD.iterconstants())
     404        self.assertEqual(
     405            [self.METHOD.GET, self.METHOD.PUT,
     406             self.METHOD.POST, self.METHOD.DELETE],
     407            constants)
     408
     409
     410    def test_attributeIterconstantsIdentity(self):
     411        """
     412        The constants returned from L{Names.iterconstants} are identical to the
     413        constants accessible using attributes.
     414        """
     415        constants = list(self.METHOD.iterconstants())
     416        self.assertIdentical(self.METHOD.GET, constants[0])
     417        self.assertIdentical(self.METHOD.PUT, constants[1])
     418        self.assertIdentical(self.METHOD.POST, constants[2])
     419        self.assertIdentical(self.METHOD.DELETE, constants[3])
     420
     421
     422    def test_iterconstantsIdentity(self):
     423        """
     424        The constants returned from L{Names.iterconstants} are identical on each
     425        call to that method.
     426        """
     427        constants = list(self.METHOD.iterconstants())
     428        again = list(self.METHOD.iterconstants())
     429        self.assertIdentical(again[0], constants[0])
     430        self.assertIdentical(again[1], constants[1])
     431        self.assertIdentical(again[2], constants[2])
     432        self.assertIdentical(again[3], constants[3])
     433
     434
     435    def test_initializedOnce(self):
     436        """
     437        L{Names._enumerants} is initialized once and its value re-used on
     438        subsequent access.
     439        """
     440        first = self.METHOD._enumerants
     441        self.METHOD.GET # Side-effects!
     442        second = self.METHOD._enumerants
     443        self.assertIdentical(first, second)
     444
     445
     446
     447class ValuesTests(unittest.TestCase, _ConstantsTestsMixin):
     448    """
     449    Tests for L{twisted.python.constants.Names}, a base class for containers of
     450    related constaints with arbitrary values.
     451    """
     452    def setUp(self):
     453        """
     454        Create a fresh new L{Values} subclass for each unit test to use.  Since
     455        L{Values} is stateful, re-using the same subclass across test methods
     456        makes exercising all of the implementation code paths difficult.
     457        """
     458        class STATUS(Values):
     459            OK = ValueConstant("200")
     460            NOT_FOUND = ValueConstant("404")
     461
     462        self.STATUS = STATUS
     463
     464
     465    def test_notInstantiable(self):
     466        """
     467        A subclass of L{Values} raises C{TypeError} if an attempt is made to
     468        instantiate it.
     469        """
     470        self._notInstantiableTest("STATUS", self.STATUS)
     471
     472
     473    def test_symbolicAttributes(self):
     474        """
     475        Each name associated with a L{ValueConstant} instance in the definition
     476        of a L{Values} subclass is available as an attribute on the resulting
     477        class.
     478        """
     479        self.assertTrue(hasattr(self.STATUS, "OK"))
     480        self.assertTrue(hasattr(self.STATUS, "NOT_FOUND"))
     481
     482
     483    def test_withoutOtherAttributes(self):
     484        """
     485        As usual, names not defined in the class scope of a L{Values}
     486        subclass are not available as attributes on the resulting class.
     487        """
     488        self.assertFalse(hasattr(self.STATUS, "foo"))
     489
     490
     491    def test_representation(self):
     492        """
     493        The string representation of a constant on a L{Values} subclass includes
     494        the name of the L{Values} subclass and the name of the constant itself.
     495        """
     496        self.assertEqual("<STATUS=OK>", repr(self.STATUS.OK))
     497
     498
     499    def test_lookupByName(self):
     500        """
     501        Constants can be looked up by name using L{Values.lookupByName}.
     502        """
     503        method = self.STATUS.lookupByName("OK")
     504        self.assertIdentical(self.STATUS.OK, method)
     505
     506
     507    def test_notLookupMissingByName(self):
     508        """
     509        Names not defined with a L{ValueConstant} instance cannot be looked up
     510        using L{Values.lookupByName}.
     511        """
     512        self.assertRaises(ValueError, self.STATUS.lookupByName, "lookupByName")
     513        self.assertRaises(ValueError, self.STATUS.lookupByName, "__init__")
     514        self.assertRaises(ValueError, self.STATUS.lookupByName, "foo")
     515
     516
     517    def test_lookupByValue(self):
     518        """
     519        Constants can be looked up by their associated value, defined by the
     520        argument passed to L{ValueConstant}, using L{Values.lookupByValue}.
     521        """
     522        status = self.STATUS.lookupByValue("200")
     523        self.assertIdentical(self.STATUS.OK, status)
     524
     525
     526    def test_lookupDuplicateByValue(self):
     527        """
     528        If more than one constant is associated with a particular value,
     529        L{Values.lookupByValue} returns whichever of them is defined first.
     530        """
     531        class TRANSPORT_MESSAGE(Values):
     532            """
     533            Message types supported by an SSH transport.
     534            """
     535            KEX_DH_GEX_REQUEST_OLD = ValueConstant(30)
     536            KEXDH_INIT = ValueConstant(30)
     537
     538        self.assertIdentical(
     539            TRANSPORT_MESSAGE.lookupByValue(30),
     540            TRANSPORT_MESSAGE.KEX_DH_GEX_REQUEST_OLD)
     541
     542
     543    def test_notLookupMissingByValue(self):
     544        """
     545        L{Values.lookupByValue} raises L{ValueError} when called with a value
     546        with which no constant is associated.
     547        """
     548        self.assertRaises(ValueError, self.STATUS.lookupByValue, "OK")
     549        self.assertRaises(ValueError, self.STATUS.lookupByValue, 200)
     550        self.assertRaises(ValueError, self.STATUS.lookupByValue, "200.1")
     551
     552
     553    def test_name(self):
     554        """
     555        The C{name} attribute of one of the constants gives that constant's
     556        name.
     557        """
     558        self.assertEqual("OK", self.STATUS.OK.name)
     559
     560
     561    def test_attributeIdentity(self):
     562        """
     563        Repeated access of an attribute associated with a L{ValueConstant} value
     564        in a L{Values} subclass results in the same object.
     565        """
     566        self.assertIdentical(self.STATUS.OK, self.STATUS.OK)
     567
     568
     569    def test_iterconstants(self):
     570        """
     571        L{Values.iterconstants} returns an iterator over all of the constants
     572        defined in the class, in the order they were defined.
     573        """
     574        constants = list(self.STATUS.iterconstants())
     575        self.assertEqual(
     576            [self.STATUS.OK, self.STATUS.NOT_FOUND],
     577            constants)
     578
     579
     580    def test_attributeIterconstantsIdentity(self):
     581        """
     582        The constants returned from L{Values.iterconstants} are identical to the
     583        constants accessible using attributes.
     584        """
     585        constants = list(self.STATUS.iterconstants())
     586        self.assertIdentical(self.STATUS.OK, constants[0])
     587        self.assertIdentical(self.STATUS.NOT_FOUND, constants[1])
     588
     589
     590    def test_iterconstantsIdentity(self):
     591        """
     592        The constants returned from L{Values.iterconstants} are identical on
     593        each call to that method.
     594        """
     595        constants = list(self.STATUS.iterconstants())
     596        again = list(self.STATUS.iterconstants())
     597        self.assertIdentical(again[0], constants[0])
     598        self.assertIdentical(again[1], constants[1])
     599
     600
     601    def test_initializedOnce(self):
     602        """
     603        L{Values._enumerants} is initialized once and its value re-used on
     604        subsequent access.
     605        """
     606        first = self.STATUS._enumerants
     607        self.STATUS.OK # Side-effects!
     608        second = self.STATUS._enumerants
     609        self.assertIdentical(first, second)
  • wokkel/test/test_muc.py

    r157 r160  
    4343
    4444
     45class StatusCodeTest(unittest.TestCase):
     46    """
     47    Tests for L{muc.STATUS_CODE}.
     48    """
     49
     50    def test_lookupByValue(self):
     51        """
     52        The registered MUC status codes map to STATUS_CODE value constants.
     53
     54        Note: the identifiers used in the dictionary of status codes are
     55        borrowed from U{XEP-0306<http://xmpp.org/extensions/xep-0306.html>}
     56        that defines Extensible Status Conditions for Multi-User Chat. If this
     57        specification is implemented itself, the dictionary could move there.
     58        """
     59        codes = {
     60            100: 'realjid-public',
     61            101: 'affiliation-changed',
     62            102: 'unavailable-shown',
     63            103: 'unavailable-not-shown',
     64            104: 'configuration-changed',
     65            110: 'self-presence',
     66            170: 'logging-enabled',
     67            171: 'logging-disabled',
     68            172: 'non-anonymous',
     69            173: 'semi-anonymous',
     70            174: 'fully-anonymous',
     71            201: 'room-created',
     72            210: 'nick-assigned',
     73            301: 'banned',
     74            303: 'new-nick',
     75            307: 'kicked',
     76            321: 'removed-affiliation',
     77            322: 'removed-membership',
     78            332: 'removed-shutdown',
     79        }
     80
     81        for code, condition in codes.iteritems():
     82            constantName = condition.replace('-', '_').upper()
     83            self.assertEqual(getattr(muc.STATUS_CODE, constantName),
     84                             muc.STATUS_CODE.lookupByValue(code))
     85
     86
     87
     88class StatusesTest(unittest.TestCase):
     89    """
     90    Tests for L{muc.Statuses}.
     91    """
     92
     93    def setUp(self):
     94        self.mucStatuses = muc.Statuses()
     95        self.mucStatuses.add(muc.STATUS_CODE.SELF_PRESENCE)
     96        self.mucStatuses.add(muc.STATUS_CODE.ROOM_CREATED)
     97
     98
     99    def test_interface(self):
     100        """
     101        Instances of L{Statuses} provide L{iwokkel.IMUCStatuses}.
     102        """
     103        verify.verifyObject(iwokkel.IMUCStatuses, self.mucStatuses)
     104
     105
     106    def test_contains(self):
     107        """
     108        The status contained are 'in' the container.
     109        """
     110        self.assertIn(muc.STATUS_CODE.SELF_PRESENCE, self.mucStatuses)
     111        self.assertIn(muc.STATUS_CODE.ROOM_CREATED, self.mucStatuses)
     112        self.assertNotIn(muc.STATUS_CODE.NON_ANONYMOUS, self.mucStatuses)
     113
     114
     115    def test_iter(self):
     116        """
     117        All statuses can be iterated over.
     118        """
     119        statuses = set()
     120        for status in self.mucStatuses:
     121            statuses.add(status)
     122
     123        self.assertEqual(set([muc.STATUS_CODE.SELF_PRESENCE,
     124                              muc.STATUS_CODE.ROOM_CREATED]), statuses)
     125
     126
     127    def test_len(self):
     128        """
     129        The number of items in this container is returned by C{__len__}.
     130        """
     131        self.assertEqual(2, len(self.mucStatuses))
     132
     133
     134
    45135class GroupChatTest(unittest.TestCase):
    46136    """
    47     Tests for {muc.GroupChat}.
     137    Tests for L{muc.GroupChat}.
    48138    """
    49139
     
    121211
    122212
     213
    123214class UserPresenceTest(unittest.TestCase):
    124215    """
     
    126217    """
    127218
    128 
    129     def test_toElementUnknownChild(self):
     219    def test_fromElementNoUserElement(self):
     220        """
     221        Without user element, all associated attributes are None.
     222        """
     223        xml = """
     224            <presence from='coven@chat.shakespeare.lit/thirdwitch'
     225                      id='026B3509-2CCE-4D69-96D6-25F41FFDC408'
     226                      to='hag66@shakespeare.lit/pda'>
     227            </presence>
     228        """
     229
     230        element = parseXml(xml)
     231        presence = muc.UserPresence.fromElement(element)
     232
     233        self.assertIdentical(None, presence.affiliation)
     234        self.assertIdentical(None, presence.role)
     235        self.assertIdentical(None, presence.entity)
     236        self.assertIdentical(None, presence.nick)
     237        self.assertEqual(0, len(presence.mucStatuses))
     238
     239
     240    def test_fromElementUnknownChild(self):
    130241        """
    131242        Unknown child elements are ignored.
     
    144255        presence = muc.UserPresence.fromElement(element)
    145256
    146         self.assertIdentical(None, presence.statusCodes)
    147 
    148 
    149     def test_toElementStatusOne(self):
     257        self.assertEqual(0, len(presence.mucStatuses))
     258
     259
     260    def test_fromElementStatusOne(self):
    150261        """
    151262        Status codes are extracted.
     
    165276        presence = muc.UserPresence.fromElement(element)
    166277
    167         self.assertIn(110, presence.statusCodes)
    168 
    169 
    170     def test_toElementStatusMultiple(self):
     278        self.assertIn(muc.STATUS_CODE.SELF_PRESENCE, presence.mucStatuses)
     279
     280
     281    def test_fromElementStatusMultiple(self):
    171282        """
    172283        Multiple status codes are all extracted.
     
    187298        presence = muc.UserPresence.fromElement(element)
    188299
    189         self.assertIn(110, presence.statusCodes)
    190         self.assertIn(100, presence.statusCodes)
    191 
    192 
    193     def test_toElementStatusEmpty(self):
     300        self.assertIn(muc.STATUS_CODE.SELF_PRESENCE, presence.mucStatuses)
     301        self.assertIn(muc.STATUS_CODE.REALJID_PUBLIC, presence.mucStatuses)
     302
     303
     304    def test_fromElementStatusEmpty(self):
    194305        """
    195306        Empty status elements are ignored.
     
    209320        presence = muc.UserPresence.fromElement(element)
    210321
    211         self.assertIdentical(None, presence.statusCodes)
    212 
    213 
    214     def test_toElementStatusBad(self):
     322        self.assertEqual(0, len(presence.mucStatuses))
     323
     324
     325    def test_fromElementStatusBad(self):
    215326        """
    216327        Bad status codes are ignored.
     
    230341        presence = muc.UserPresence.fromElement(element)
    231342
    232         self.assertIdentical(None, presence.statusCodes)
    233 
    234 
    235     def test_toElementStatusUnknown(self):
    236         """
    237         Unknown status codes are still recorded in C{statusCodes}.
     343        self.assertEqual(0, len(presence.mucStatuses))
     344
     345
     346    def test_fromElementStatusUnknown(self):
     347        """
     348        Unknown status codes are not recorded in C{mucStatuses}.
    238349        """
    239350        xml = """
     
    251362        presence = muc.UserPresence.fromElement(element)
    252363
    253         self.assertIn(999, presence.statusCodes)
    254 
    255 
    256     def test_toElementItem(self):
     364        self.assertEqual(0, len(presence.mucStatuses))
     365
     366
     367    def test_fromElementItem(self):
    257368        """
    258369        Item attributes are parsed properly.
Note: See TracChangeset for help on using the changeset viewer.