Ignore:
Timestamp:
Jan 8, 2012, 9:26:02 AM (11 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/test
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • 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.