[143] | 1 | # Copyright (c) Ralph Meijer. |
---|
[107] | 2 | # See LICENSE for details. |
---|
| 3 | |
---|
| 4 | """ |
---|
| 5 | Tests for L{wokkel.muc} |
---|
| 6 | """ |
---|
| 7 | |
---|
[141] | 8 | from datetime import datetime |
---|
| 9 | from dateutil.tz import tzutc |
---|
| 10 | |
---|
[107] | 11 | from zope.interface import verify |
---|
| 12 | |
---|
| 13 | from twisted.trial import unittest |
---|
[145] | 14 | from twisted.internet import defer, task |
---|
[107] | 15 | from twisted.words.xish import domish, xpath |
---|
| 16 | from twisted.words.protocols.jabber.jid import JID |
---|
[145] | 17 | from twisted.words.protocols.jabber.error import StanzaError |
---|
| 18 | from twisted.words.protocols.jabber.xmlstream import TimeoutError, toResponse |
---|
[107] | 19 | |
---|
[135] | 20 | from wokkel import data_form, iwokkel, muc |
---|
| 21 | from wokkel.generic import parseXml |
---|
[148] | 22 | from wokkel.test.helpers import TestableStreamManager |
---|
[107] | 23 | |
---|
| 24 | |
---|
[130] | 25 | NS_MUC_ADMIN = 'http://jabber.org/protocol/muc#admin' |
---|
[107] | 26 | |
---|
| 27 | def calledAsync(fn): |
---|
| 28 | """ |
---|
| 29 | Function wrapper that fires a deferred upon calling the given function. |
---|
| 30 | """ |
---|
| 31 | d = defer.Deferred() |
---|
| 32 | |
---|
| 33 | def func(*args, **kwargs): |
---|
| 34 | try: |
---|
| 35 | result = fn(*args, **kwargs) |
---|
| 36 | except: |
---|
| 37 | d.errback() |
---|
| 38 | else: |
---|
| 39 | d.callback(result) |
---|
| 40 | |
---|
| 41 | return d, func |
---|
| 42 | |
---|
[145] | 43 | |
---|
| 44 | |
---|
| 45 | class MUCClientTest(unittest.TestCase): |
---|
[107] | 46 | timeout = 2 |
---|
| 47 | |
---|
| 48 | def setUp(self): |
---|
[145] | 49 | self.clock = task.Clock() |
---|
[147] | 50 | self.sessionManager = TestableStreamManager(reactor=self.clock) |
---|
| 51 | self.stub = self.sessionManager.stub |
---|
[145] | 52 | self.protocol = muc.MUCClient(reactor=self.clock) |
---|
[147] | 53 | self.protocol.setHandlerParent(self.sessionManager) |
---|
| 54 | |
---|
[144] | 55 | self.roomIdentifier = 'test' |
---|
| 56 | self.service = 'conference.example.org' |
---|
| 57 | self.nick = 'Nick' |
---|
[107] | 58 | |
---|
[144] | 59 | self.occupantJID = JID(tuple=(self.roomIdentifier, |
---|
| 60 | self.service, |
---|
| 61 | self.nick)) |
---|
| 62 | self.roomJID = self.occupantJID.userhostJID() |
---|
| 63 | self.userJID = JID('test@example.org/Testing') |
---|
[112] | 64 | |
---|
[129] | 65 | |
---|
[113] | 66 | def _createRoom(self): |
---|
[130] | 67 | """ |
---|
| 68 | A helper method to create a test room. |
---|
[117] | 69 | """ |
---|
[113] | 70 | # create a room |
---|
[144] | 71 | room = muc.Room(self.roomIdentifier, |
---|
| 72 | self.service, |
---|
| 73 | self.nick) |
---|
| 74 | self.protocol._addRoom(room) |
---|
[113] | 75 | |
---|
| 76 | |
---|
[107] | 77 | def test_interface(self): |
---|
| 78 | """ |
---|
| 79 | Do instances of L{muc.MUCClient} provide L{iwokkel.IMUCClient}? |
---|
| 80 | """ |
---|
| 81 | verify.verifyObject(iwokkel.IMUCClient, self.protocol) |
---|
| 82 | |
---|
| 83 | |
---|
[113] | 84 | def test_userJoinedRoom(self): |
---|
[130] | 85 | """ |
---|
[144] | 86 | Joins by others to a room we're in are passed to userJoinedRoom |
---|
[109] | 87 | """ |
---|
[135] | 88 | xml = """ |
---|
| 89 | <presence to='%s' from='%s'> |
---|
| 90 | <x xmlns='http://jabber.org/protocol/muc#user'> |
---|
| 91 | <item affiliation='member' role='participant'/> |
---|
| 92 | </x> |
---|
| 93 | </presence> |
---|
[144] | 94 | """ % (self.userJID.full(), self.occupantJID.full()) |
---|
[113] | 95 | |
---|
| 96 | # create a room |
---|
| 97 | self._createRoom() |
---|
| 98 | |
---|
[135] | 99 | def userJoinedRoom(room, user): |
---|
[144] | 100 | self.assertEquals(self.roomIdentifier, room.roomIdentifier, |
---|
[135] | 101 | 'Wrong room name') |
---|
[132] | 102 | self.assertTrue(room.inRoster(user), 'User not in roster') |
---|
[129] | 103 | |
---|
[135] | 104 | d, self.protocol.userJoinedRoom = calledAsync(userJoinedRoom) |
---|
| 105 | self.stub.send(parseXml(xml)) |
---|
[109] | 106 | return d |
---|
| 107 | |
---|
| 108 | |
---|
[145] | 109 | def test_receivedSubject(self): |
---|
| 110 | """ |
---|
| 111 | Subject received from a room we're in are passed to receivedSubject. |
---|
| 112 | """ |
---|
| 113 | xml = u""" |
---|
| 114 | <message to='%s' from='%s' type='groupchat'> |
---|
| 115 | <subject>test</subject> |
---|
| 116 | </message> |
---|
| 117 | """ % (self.userJID, self.occupantJID) |
---|
| 118 | |
---|
| 119 | self._createRoom() |
---|
| 120 | |
---|
| 121 | # add user to room |
---|
| 122 | user = muc.User(self.nick) |
---|
| 123 | room = self.protocol._getRoom(self.roomJID) |
---|
| 124 | room.addUser(user) |
---|
| 125 | |
---|
| 126 | def receivedSubject(room, user, subject): |
---|
| 127 | self.assertEquals('test', subject, "Wrong group chat message") |
---|
| 128 | self.assertEquals(self.roomIdentifier, room.roomIdentifier, |
---|
| 129 | 'Wrong room name') |
---|
| 130 | self.assertEquals(self.nick, user.nick) |
---|
| 131 | |
---|
| 132 | d, self.protocol.receivedSubject = calledAsync(receivedSubject) |
---|
| 133 | self.stub.send(parseXml(xml)) |
---|
| 134 | return d |
---|
| 135 | |
---|
| 136 | |
---|
[144] | 137 | def test_receivedGroupChat(self): |
---|
[130] | 138 | """ |
---|
[144] | 139 | Messages received from a room we're in are passed to receivedGroupChat. |
---|
[109] | 140 | """ |
---|
[144] | 141 | xml = u""" |
---|
[135] | 142 | <message to='test@test.com' from='%s' type='groupchat'> |
---|
| 143 | <body>test</body> |
---|
| 144 | </message> |
---|
[144] | 145 | """ % (self.occupantJID) |
---|
[113] | 146 | |
---|
| 147 | self._createRoom() |
---|
| 148 | |
---|
[144] | 149 | def receivedGroupChat(room, user, message): |
---|
[141] | 150 | self.assertEquals('test', message.body, "Wrong group chat message") |
---|
[144] | 151 | self.assertEquals(self.roomIdentifier, room.roomIdentifier, |
---|
[135] | 152 | 'Wrong room name') |
---|
[129] | 153 | |
---|
[144] | 154 | d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat) |
---|
[135] | 155 | self.stub.send(parseXml(xml)) |
---|
[109] | 156 | return d |
---|
| 157 | |
---|
[108] | 158 | |
---|
[144] | 159 | def test_receivedGroupChatRoom(self): |
---|
[142] | 160 | """ |
---|
[144] | 161 | Messages received from the room itself have C{user} set to C{None}. |
---|
[142] | 162 | """ |
---|
[144] | 163 | xml = u""" |
---|
[142] | 164 | <message to='test@test.com' from='%s' type='groupchat'> |
---|
| 165 | <body>test</body> |
---|
| 166 | </message> |
---|
[144] | 167 | """ % (self.roomJID) |
---|
[142] | 168 | |
---|
| 169 | self._createRoom() |
---|
| 170 | |
---|
[144] | 171 | def receivedGroupChat(room, user, message): |
---|
| 172 | self.assertIdentical(None, user) |
---|
[142] | 173 | |
---|
[144] | 174 | d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat) |
---|
[142] | 175 | self.stub.send(parseXml(xml)) |
---|
| 176 | return d |
---|
| 177 | |
---|
| 178 | |
---|
[145] | 179 | def test_join(self): |
---|
[107] | 180 | """ |
---|
[144] | 181 | Joining a room waits for confirmation, deferred fires room. |
---|
[107] | 182 | """ |
---|
[129] | 183 | |
---|
[107] | 184 | def cb(room): |
---|
[144] | 185 | self.assertEquals(self.roomIdentifier, room.roomIdentifier) |
---|
[107] | 186 | |
---|
[144] | 187 | d = self.protocol.join(self.service, self.roomIdentifier, self.nick) |
---|
[107] | 188 | d.addCallback(cb) |
---|
| 189 | |
---|
[145] | 190 | element = self.stub.output[-1] |
---|
| 191 | self.assertEquals('presence', element.name, "Need to be presence") |
---|
| 192 | self.assertNotIdentical(None, element.x, 'No muc x element') |
---|
[108] | 193 | |
---|
[129] | 194 | # send back user presence, they joined |
---|
[135] | 195 | xml = """ |
---|
| 196 | <presence from='%s@%s/%s'> |
---|
| 197 | <x xmlns='http://jabber.org/protocol/muc#user'> |
---|
| 198 | <item affiliation='member' role='participant'/> |
---|
| 199 | </x> |
---|
| 200 | </presence> |
---|
[144] | 201 | """ % (self.roomIdentifier, self.service, self.nick) |
---|
[135] | 202 | self.stub.send(parseXml(xml)) |
---|
[108] | 203 | return d |
---|
| 204 | |
---|
| 205 | |
---|
[146] | 206 | def test_joinHistory(self): |
---|
| 207 | """ |
---|
| 208 | Passing a history parameter sends a 'maxstanzas' history limit. |
---|
| 209 | """ |
---|
| 210 | |
---|
| 211 | def cb(room): |
---|
| 212 | self.assertEquals(self.roomIdentifier, room.roomIdentifier) |
---|
| 213 | |
---|
| 214 | d = self.protocol.join(self.service, self.roomIdentifier, self.nick, |
---|
| 215 | history=10) |
---|
| 216 | d.addCallback(cb) |
---|
| 217 | |
---|
| 218 | element = self.stub.output[-1] |
---|
| 219 | query = "/*/x[@xmlns='%s']/history[@xmlns='%s']" % (muc.NS_MUC, |
---|
| 220 | muc.NS_MUC) |
---|
| 221 | result = xpath.queryForNodes(query, element) |
---|
| 222 | history = result[0] |
---|
| 223 | self.assertEquals('10', history.getAttribute('maxstanzas')) |
---|
| 224 | |
---|
| 225 | # send back user presence, they joined |
---|
| 226 | xml = """ |
---|
| 227 | <presence from='%s@%s/%s'> |
---|
| 228 | <x xmlns='http://jabber.org/protocol/muc#user'> |
---|
| 229 | <item affiliation='member' role='participant'/> |
---|
| 230 | </x> |
---|
| 231 | </presence> |
---|
| 232 | """ % (self.roomIdentifier, self.service, self.nick) |
---|
| 233 | self.stub.send(parseXml(xml)) |
---|
| 234 | return d |
---|
| 235 | |
---|
| 236 | |
---|
[145] | 237 | def test_joinForbidden(self): |
---|
[130] | 238 | """ |
---|
[145] | 239 | A forbidden error in response to a join errbacks with L{StanzaError}. |
---|
[108] | 240 | """ |
---|
| 241 | |
---|
| 242 | def cb(error): |
---|
[145] | 243 | self.assertEquals('forbidden', error.condition, |
---|
[135] | 244 | 'Wrong muc condition') |
---|
[129] | 245 | |
---|
[144] | 246 | d = self.protocol.join(self.service, self.roomIdentifier, self.nick) |
---|
[145] | 247 | self.assertFailure(d, StanzaError) |
---|
| 248 | d.addCallback(cb) |
---|
[129] | 249 | |
---|
[135] | 250 | # send back error, forbidden |
---|
[144] | 251 | xml = u""" |
---|
[135] | 252 | <presence from='%s' type='error'> |
---|
| 253 | <error type='auth'> |
---|
| 254 | <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> |
---|
| 255 | </error> |
---|
| 256 | </presence> |
---|
[144] | 257 | """ % (self.occupantJID) |
---|
[135] | 258 | self.stub.send(parseXml(xml)) |
---|
[129] | 259 | return d |
---|
[109] | 260 | |
---|
[125] | 261 | |
---|
[145] | 262 | def test_joinForbiddenFromRoomJID(self): |
---|
| 263 | """ |
---|
| 264 | An error response to a join sent from the room JID should errback. |
---|
| 265 | |
---|
| 266 | Some service implementations send error stanzas from the room JID |
---|
| 267 | instead of the JID the join presence was sent to. |
---|
| 268 | """ |
---|
| 269 | |
---|
| 270 | d = self.protocol.join(self.service, self.roomIdentifier, self.nick) |
---|
| 271 | self.assertFailure(d, StanzaError) |
---|
| 272 | |
---|
| 273 | # send back error, forbidden |
---|
| 274 | xml = u""" |
---|
| 275 | <presence from='%s' type='error'> |
---|
| 276 | <error type='auth'> |
---|
| 277 | <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> |
---|
| 278 | </error> |
---|
| 279 | </presence> |
---|
| 280 | """ % (self.roomJID) |
---|
| 281 | self.stub.send(parseXml(xml)) |
---|
| 282 | return d |
---|
| 283 | |
---|
| 284 | |
---|
| 285 | def test_joinBadJID(self): |
---|
[130] | 286 | """ |
---|
[135] | 287 | Client joining a room and getting a jid-malformed error. |
---|
[125] | 288 | """ |
---|
| 289 | |
---|
| 290 | def cb(error): |
---|
[145] | 291 | self.assertEquals('jid-malformed', error.condition, |
---|
[135] | 292 | 'Wrong muc condition') |
---|
[129] | 293 | |
---|
[144] | 294 | d = self.protocol.join(self.service, self.roomIdentifier, self.nick) |
---|
[145] | 295 | self.assertFailure(d, StanzaError) |
---|
| 296 | d.addCallback(cb) |
---|
[129] | 297 | |
---|
[135] | 298 | # send back error, bad JID |
---|
[144] | 299 | xml = u""" |
---|
[135] | 300 | <presence from='%s' type='error'> |
---|
| 301 | <error type='modify'> |
---|
| 302 | <jid-malformed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> |
---|
| 303 | </error> |
---|
| 304 | </presence> |
---|
[144] | 305 | """ % (self.occupantJID) |
---|
[135] | 306 | self.stub.send(parseXml(xml)) |
---|
[129] | 307 | return d |
---|
[125] | 308 | |
---|
| 309 | |
---|
[145] | 310 | def test_joinTimeout(self): |
---|
| 311 | """ |
---|
| 312 | After not receiving a response to a join, errback with L{TimeoutError}. |
---|
| 313 | """ |
---|
| 314 | |
---|
| 315 | d = self.protocol.join(self.service, self.roomIdentifier, self.nick) |
---|
| 316 | self.assertFailure(d, TimeoutError) |
---|
| 317 | self.clock.advance(muc.DEFER_TIMEOUT) |
---|
| 318 | return d |
---|
| 319 | |
---|
| 320 | |
---|
| 321 | def test_leave(self): |
---|
[130] | 322 | """ |
---|
| 323 | Client leaves a room |
---|
[117] | 324 | """ |
---|
[112] | 325 | def cb(left): |
---|
[135] | 326 | self.assertTrue(left, 'did not leave room') |
---|
[112] | 327 | |
---|
[130] | 328 | self._createRoom() |
---|
[145] | 329 | d = self.protocol.leave(self.roomJID) |
---|
[112] | 330 | d.addCallback(cb) |
---|
| 331 | |
---|
[145] | 332 | element = self.stub.output[-1] |
---|
[129] | 333 | |
---|
[145] | 334 | self.assertEquals('unavailable', element['type'], |
---|
[135] | 335 | 'Unavailable is not being sent') |
---|
[129] | 336 | |
---|
[144] | 337 | xml = u""" |
---|
| 338 | <presence to='%s' from='%s' type='unavailable'/> |
---|
| 339 | """ % (self.userJID, self.occupantJID) |
---|
[135] | 340 | self.stub.send(parseXml(xml)) |
---|
[112] | 341 | return d |
---|
[129] | 342 | |
---|
[110] | 343 | |
---|
[145] | 344 | def test_userLeftRoom(self): |
---|
[130] | 345 | """ |
---|
[145] | 346 | Unavailable presence from a participant removes it from the room. |
---|
[118] | 347 | """ |
---|
| 348 | |
---|
[144] | 349 | xml = u""" |
---|
[135] | 350 | <presence to='%s' from='%s' type='unavailable'/> |
---|
[144] | 351 | """ % (self.userJID, self.occupantJID) |
---|
[118] | 352 | |
---|
| 353 | # create a room |
---|
| 354 | self._createRoom() |
---|
[135] | 355 | |
---|
[118] | 356 | # add user to room |
---|
[144] | 357 | user = muc.User(self.nick) |
---|
[145] | 358 | room = self.protocol._getRoom(self.roomJID) |
---|
[131] | 359 | room.addUser(user) |
---|
[118] | 360 | |
---|
[145] | 361 | def userLeftRoom(room, user): |
---|
[144] | 362 | self.assertEquals(self.roomIdentifier, room.roomIdentifier, |
---|
[135] | 363 | 'Wrong room name') |
---|
| 364 | self.assertFalse(room.inRoster(user), 'User in roster') |
---|
[129] | 365 | |
---|
[145] | 366 | d, self.protocol.userLeftRoom = calledAsync(userLeftRoom) |
---|
[135] | 367 | self.stub.send(parseXml(xml)) |
---|
[118] | 368 | return d |
---|
[129] | 369 | |
---|
[118] | 370 | |
---|
[110] | 371 | def test_ban(self): |
---|
[130] | 372 | """ |
---|
| 373 | Ban an entity in a room. |
---|
[117] | 374 | """ |
---|
[135] | 375 | banned = JID('ban@jabber.org/TroubleMaker') |
---|
| 376 | |
---|
[112] | 377 | def cb(banned): |
---|
[135] | 378 | self.assertTrue(banned, 'Did not ban user') |
---|
[129] | 379 | |
---|
[144] | 380 | d = self.protocol.ban(self.occupantJID, banned, reason='Spam', |
---|
| 381 | sender=self.userJID) |
---|
[112] | 382 | d.addCallback(cb) |
---|
| 383 | |
---|
| 384 | iq = self.stub.output[-1] |
---|
[129] | 385 | |
---|
[135] | 386 | self.assertTrue(xpath.matches( |
---|
[144] | 387 | u"/iq[@type='set' and @to='%s']/query/item" |
---|
| 388 | "[@affiliation='outcast']" % (self.roomJID,), |
---|
[135] | 389 | iq), |
---|
| 390 | 'Wrong ban stanza') |
---|
[112] | 391 | |
---|
| 392 | response = toResponse(iq, 'result') |
---|
| 393 | self.stub.send(response) |
---|
| 394 | |
---|
| 395 | return d |
---|
| 396 | |
---|
[110] | 397 | |
---|
| 398 | def test_kick(self): |
---|
[130] | 399 | """ |
---|
| 400 | Kick an entity from a room. |
---|
[117] | 401 | """ |
---|
[135] | 402 | nick = 'TroubleMaker' |
---|
[131] | 403 | |
---|
[112] | 404 | def cb(kicked): |
---|
[135] | 405 | self.assertTrue(kicked, 'Did not kick user') |
---|
[112] | 406 | |
---|
[144] | 407 | d = self.protocol.kick(self.occupantJID, nick, reason='Spam', |
---|
| 408 | sender=self.userJID) |
---|
[112] | 409 | d.addCallback(cb) |
---|
| 410 | |
---|
| 411 | iq = self.stub.output[-1] |
---|
[129] | 412 | |
---|
[135] | 413 | self.assertTrue(xpath.matches( |
---|
[144] | 414 | u"/iq[@type='set' and @to='%s']/query/item" |
---|
| 415 | "[@affiliation='none']" % (self.roomJID,), |
---|
[135] | 416 | iq), |
---|
| 417 | 'Wrong kick stanza') |
---|
[112] | 418 | |
---|
| 419 | response = toResponse(iq, 'result') |
---|
| 420 | self.stub.send(response) |
---|
| 421 | |
---|
| 422 | return d |
---|
| 423 | |
---|
[110] | 424 | |
---|
| 425 | def test_password(self): |
---|
[130] | 426 | """ |
---|
| 427 | Sending a password via presence to a password protected room. |
---|
[110] | 428 | """ |
---|
[129] | 429 | |
---|
[144] | 430 | self.protocol.password(self.occupantJID, 'secret') |
---|
[129] | 431 | |
---|
[145] | 432 | element = self.stub.output[-1] |
---|
[129] | 433 | |
---|
[135] | 434 | self.assertTrue(xpath.matches( |
---|
[144] | 435 | u"/presence[@to='%s']/x/password" |
---|
| 436 | "[text()='secret']" % (self.occupantJID,), |
---|
[145] | 437 | element), |
---|
[135] | 438 | 'Wrong presence stanza') |
---|
[112] | 439 | |
---|
[110] | 440 | |
---|
[145] | 441 | def test_receivedHistory(self): |
---|
[130] | 442 | """ |
---|
| 443 | Receiving history on room join. |
---|
[112] | 444 | """ |
---|
[144] | 445 | xml = u""" |
---|
[135] | 446 | <message to='test@test.com' from='%s' type='groupchat'> |
---|
| 447 | <body>test</body> |
---|
| 448 | <delay xmlns='urn:xmpp:delay' stamp="2002-10-13T23:58:37Z" |
---|
| 449 | from="%s"/> |
---|
| 450 | </message> |
---|
[144] | 451 | """ % (self.occupantJID, self.userJID) |
---|
[129] | 452 | |
---|
[113] | 453 | self._createRoom() |
---|
| 454 | |
---|
[129] | 455 | |
---|
[145] | 456 | def receivedHistory(room, user, message): |
---|
[141] | 457 | self.assertEquals('test', message.body, "wrong message body") |
---|
| 458 | stamp = datetime(2002, 10, 13, 23, 58, 37, tzinfo=tzutc()) |
---|
| 459 | self.assertEquals(stamp, message.delay.stamp, |
---|
| 460 | 'Does not have a history stamp') |
---|
[112] | 461 | |
---|
[145] | 462 | d, self.protocol.receivedHistory = calledAsync(receivedHistory) |
---|
[135] | 463 | self.stub.send(parseXml(xml)) |
---|
[112] | 464 | return d |
---|
[110] | 465 | |
---|
| 466 | |
---|
| 467 | def test_oneToOneChat(self): |
---|
[130] | 468 | """ |
---|
| 469 | Converting a one to one chat to a multi-user chat. |
---|
[110] | 470 | """ |
---|
| 471 | archive = [] |
---|
| 472 | thread = "e0ffe42b28561960c6b12b944a092794b9683a38" |
---|
| 473 | # create messages |
---|
[145] | 474 | element = domish.Element((None, 'message')) |
---|
| 475 | element['to'] = 'testing@example.com' |
---|
| 476 | element['type'] = 'chat' |
---|
| 477 | element.addElement('body', None, 'test') |
---|
| 478 | element.addElement('thread', None, thread) |
---|
[110] | 479 | |
---|
[145] | 480 | archive.append({'stanza': element, |
---|
[141] | 481 | 'timestamp': datetime(2002, 10, 13, 23, 58, 37, |
---|
| 482 | tzinfo=tzutc())}) |
---|
[110] | 483 | |
---|
[145] | 484 | element = domish.Element((None, 'message')) |
---|
| 485 | element['to'] = 'testing2@example.com' |
---|
| 486 | element['type'] = 'chat' |
---|
| 487 | element.addElement('body', None, 'yo') |
---|
| 488 | element.addElement('thread', None, thread) |
---|
[110] | 489 | |
---|
[145] | 490 | archive.append({'stanza': element, |
---|
[141] | 491 | 'timestamp': datetime(2002, 10, 13, 23, 58, 43, |
---|
| 492 | tzinfo=tzutc())}) |
---|
[110] | 493 | |
---|
[144] | 494 | self.protocol.history(self.occupantJID, archive) |
---|
[110] | 495 | |
---|
| 496 | |
---|
| 497 | while len(self.stub.output)>0: |
---|
[145] | 498 | element = self.stub.output.pop() |
---|
[110] | 499 | # check for delay element |
---|
[145] | 500 | self.assertEquals('message', element.name, 'Wrong stanza') |
---|
| 501 | self.assertTrue(xpath.matches("/message/delay", element), |
---|
| 502 | 'Invalid history stanza') |
---|
[129] | 503 | |
---|
[110] | 504 | |
---|
| 505 | def test_invite(self): |
---|
[117] | 506 | """ |
---|
[130] | 507 | Invite a user to a room |
---|
| 508 | """ |
---|
[144] | 509 | invitee = JID('other@example.org') |
---|
[112] | 510 | |
---|
[144] | 511 | self.protocol.invite(self.roomJID, invitee, u'This is a test') |
---|
[112] | 512 | |
---|
[144] | 513 | message = self.stub.output[-1] |
---|
[112] | 514 | |
---|
[144] | 515 | self.assertEquals('message', message.name) |
---|
| 516 | self.assertEquals(self.roomJID.full(), message.getAttribute('to')) |
---|
| 517 | self.assertEquals(muc.NS_MUC_USER, message.x.uri) |
---|
| 518 | self.assertEquals(muc.NS_MUC_USER, message.x.invite.uri) |
---|
| 519 | self.assertEquals(invitee.full(), message.x.invite.getAttribute('to')) |
---|
| 520 | self.assertEquals(muc.NS_MUC_USER, message.x.invite.reason.uri) |
---|
| 521 | self.assertEquals(u'This is a test', unicode(message.x.invite.reason)) |
---|
[112] | 522 | |
---|
[110] | 523 | |
---|
[144] | 524 | def test_groupChat(self): |
---|
[130] | 525 | """ |
---|
| 526 | Send private messages to muc entities. |
---|
[112] | 527 | """ |
---|
[144] | 528 | self.protocol.groupChat(self.roomJID, u'This is a test') |
---|
[112] | 529 | |
---|
[144] | 530 | message = self.stub.output[-1] |
---|
[112] | 531 | |
---|
[144] | 532 | self.assertEquals('message', message.name) |
---|
| 533 | self.assertEquals(self.roomJID.full(), message.getAttribute('to')) |
---|
| 534 | self.assertEquals('groupchat', message.getAttribute('type')) |
---|
| 535 | self.assertEquals(u'This is a test', unicode(message.body)) |
---|
[112] | 536 | |
---|
[144] | 537 | |
---|
| 538 | def test_chat(self): |
---|
| 539 | """ |
---|
| 540 | Send private messages to muc entities. |
---|
| 541 | """ |
---|
| 542 | otherOccupantJID = JID(self.occupantJID.userhost()+'/OtherNick') |
---|
| 543 | |
---|
| 544 | self.protocol.chat(otherOccupantJID, u'This is a test') |
---|
| 545 | |
---|
| 546 | message = self.stub.output[-1] |
---|
| 547 | |
---|
| 548 | self.assertEquals('message', message.name) |
---|
| 549 | self.assertEquals(otherOccupantJID.full(), message.getAttribute('to')) |
---|
| 550 | self.assertEquals('chat', message.getAttribute('type')) |
---|
| 551 | self.assertEquals(u'This is a test', unicode(message.body)) |
---|
[112] | 552 | |
---|
[110] | 553 | |
---|
| 554 | def test_register(self): |
---|
[130] | 555 | """ |
---|
[135] | 556 | Client registering with a room. |
---|
[111] | 557 | |
---|
[135] | 558 | http://xmpp.org/extensions/xep-0045.html#register |
---|
[111] | 559 | """ |
---|
[129] | 560 | |
---|
[144] | 561 | # FIXME: this doesn't really test the registration |
---|
| 562 | |
---|
[111] | 563 | def cb(iq): |
---|
| 564 | # check for a result |
---|
[136] | 565 | self.assertEquals('result', iq['type'], 'We did not get a result') |
---|
[129] | 566 | |
---|
[144] | 567 | d = self.protocol.register(self.roomJID) |
---|
[111] | 568 | d.addCallback(cb) |
---|
| 569 | |
---|
| 570 | iq = self.stub.output[-1] |
---|
[135] | 571 | query = "/iq/query[@xmlns='%s']" % muc.NS_REQUEST |
---|
| 572 | self.assertTrue(xpath.matches(query, iq), 'Invalid iq register request') |
---|
[129] | 573 | |
---|
[111] | 574 | response = toResponse(iq, 'result') |
---|
| 575 | self.stub.send(response) |
---|
| 576 | return d |
---|
[110] | 577 | |
---|
[129] | 578 | |
---|
[110] | 579 | def test_voice(self): |
---|
[130] | 580 | """ |
---|
| 581 | Client requesting voice for a room. |
---|
[111] | 582 | """ |
---|
[144] | 583 | self.protocol.voice(self.occupantJID) |
---|
[110] | 584 | |
---|
| 585 | m = self.stub.output[-1] |
---|
[129] | 586 | |
---|
[135] | 587 | query = ("/message/x[@type='submit']/field/value" |
---|
| 588 | "[text()='%s']") % muc.NS_MUC_REQUEST |
---|
| 589 | self.assertTrue(xpath.matches(query, m), 'Invalid voice message stanza') |
---|
[110] | 590 | |
---|
| 591 | |
---|
[148] | 592 | def test_configure(self): |
---|
[130] | 593 | """ |
---|
| 594 | Default configure and changing the room name. |
---|
[111] | 595 | """ |
---|
[109] | 596 | |
---|
[111] | 597 | def cb(iq): |
---|
[136] | 598 | self.assertEquals('result', iq['type'], 'Not a result') |
---|
[129] | 599 | |
---|
[148] | 600 | values = {'muc#roomconfig_roomname': self.roomIdentifier} |
---|
[111] | 601 | |
---|
[148] | 602 | d = self.protocol.configure(self.roomJID, values) |
---|
[111] | 603 | d.addCallback(cb) |
---|
[109] | 604 | |
---|
[111] | 605 | iq = self.stub.output[-1] |
---|
[148] | 606 | |
---|
| 607 | self.assertEquals('set', iq.getAttribute('type')) |
---|
| 608 | self.assertEquals(self.roomJID.full(), iq.getAttribute('to')) |
---|
| 609 | |
---|
| 610 | query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER) |
---|
| 611 | nodes = xpath.queryForNodes(query, iq) |
---|
| 612 | self.assertNotIdentical(None, nodes, 'Bad configure request') |
---|
| 613 | |
---|
| 614 | form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG) |
---|
| 615 | self.assertNotIdentical(None, form, 'Missing configuration form') |
---|
| 616 | self.assertEquals('submit', form.formType) |
---|
[129] | 617 | |
---|
[111] | 618 | response = toResponse(iq, 'result') |
---|
| 619 | self.stub.send(response) |
---|
| 620 | return d |
---|
| 621 | |
---|
| 622 | |
---|
[148] | 623 | def test_configureCancel(self): |
---|
| 624 | """ |
---|
| 625 | Cancelling room configuration should send a cancel form. |
---|
| 626 | """ |
---|
| 627 | |
---|
| 628 | d = self.protocol.configure(self.roomJID, None) |
---|
| 629 | |
---|
| 630 | iq = self.stub.output[-1] |
---|
| 631 | |
---|
| 632 | query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER) |
---|
| 633 | nodes = xpath.queryForNodes(query, iq) |
---|
| 634 | |
---|
| 635 | form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG) |
---|
| 636 | self.assertNotIdentical(None, form, 'Missing configuration form') |
---|
| 637 | self.assertEquals('cancel', form.formType) |
---|
| 638 | |
---|
| 639 | response = toResponse(iq, 'result') |
---|
| 640 | self.stub.send(response) |
---|
| 641 | return d |
---|
| 642 | |
---|
| 643 | |
---|
| 644 | def test_getConfiguration(self): |
---|
| 645 | """ |
---|
| 646 | The response of a configure form request should extract the form. |
---|
| 647 | """ |
---|
| 648 | |
---|
| 649 | def cb(form): |
---|
| 650 | self.assertEquals('form', form.formType) |
---|
| 651 | |
---|
| 652 | d = self.protocol.getConfiguration(self.roomJID) |
---|
| 653 | d.addCallback(cb) |
---|
| 654 | |
---|
| 655 | iq = self.stub.output[-1] |
---|
| 656 | |
---|
| 657 | query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER) |
---|
| 658 | nodes = xpath.queryForNodes(query, iq) |
---|
| 659 | self.assertNotIdentical(None, nodes, 'Missing query element') |
---|
| 660 | |
---|
| 661 | self.assertRaises(StopIteration, nodes[0].elements().next) |
---|
| 662 | |
---|
| 663 | xml = u""" |
---|
| 664 | <iq from='%s' id='%s' to='%s' type='result'> |
---|
| 665 | <query xmlns='http://jabber.org/protocol/muc#owner'> |
---|
| 666 | <x xmlns='jabber:x:data' type='form'> |
---|
| 667 | <field type='hidden' |
---|
| 668 | var='FORM_TYPE'> |
---|
| 669 | <value>http://jabber.org/protocol/muc#roomconfig</value> |
---|
| 670 | </field> |
---|
| 671 | <field label='Natural-Language Room Name' |
---|
| 672 | type='text-single' |
---|
| 673 | var='muc#roomconfig_roomname'/> |
---|
| 674 | </x> |
---|
| 675 | </query> |
---|
| 676 | </iq> |
---|
| 677 | """ % (self.roomJID, iq['id'], self.userJID) |
---|
| 678 | self.stub.send(parseXml(xml)) |
---|
| 679 | |
---|
| 680 | return d |
---|
| 681 | |
---|
| 682 | |
---|
| 683 | def test_getConfigurationNoOptions(self): |
---|
| 684 | """ |
---|
| 685 | The response of a configure form request should extract the form. |
---|
| 686 | """ |
---|
| 687 | |
---|
| 688 | def cb(form): |
---|
| 689 | self.assertIdentical(None, form) |
---|
| 690 | |
---|
| 691 | d = self.protocol.getConfiguration(self.roomJID) |
---|
| 692 | d.addCallback(cb) |
---|
| 693 | |
---|
| 694 | iq = self.stub.output[-1] |
---|
| 695 | |
---|
| 696 | xml = u""" |
---|
| 697 | <iq from='%s' id='%s' to='%s' type='result'> |
---|
| 698 | <query xmlns='http://jabber.org/protocol/muc#owner'/> |
---|
| 699 | </iq> |
---|
| 700 | """ % (self.roomJID, iq['id'], self.userJID) |
---|
| 701 | self.stub.send(parseXml(xml)) |
---|
| 702 | |
---|
| 703 | return d |
---|
| 704 | |
---|
| 705 | |
---|
[144] | 706 | def test_destroy(self): |
---|
[130] | 707 | """ |
---|
| 708 | Destroy a room. |
---|
[127] | 709 | """ |
---|
[147] | 710 | d = self.protocol.destroy(self.occupantJID, reason='Time to leave', |
---|
| 711 | alternate=JID('other@%s' % self.service), |
---|
| 712 | password='secret') |
---|
[127] | 713 | |
---|
| 714 | iq = self.stub.output[-1] |
---|
[147] | 715 | |
---|
| 716 | query = ("/iq/query[@xmlns='%s']/destroy[@xmlns='%s']" % |
---|
| 717 | (muc.NS_MUC_OWNER, muc.NS_MUC_OWNER)) |
---|
| 718 | |
---|
| 719 | nodes = xpath.queryForNodes(query, iq) |
---|
| 720 | self.assertNotIdentical(None, nodes, 'Bad configure request') |
---|
| 721 | destroy = nodes[0] |
---|
| 722 | self.assertEquals('Time to leave', unicode(destroy.reason)) |
---|
[129] | 723 | |
---|
[127] | 724 | response = toResponse(iq, 'result') |
---|
| 725 | self.stub.send(response) |
---|
| 726 | return d |
---|
| 727 | |
---|
| 728 | |
---|
[144] | 729 | def test_subject(self): |
---|
| 730 | """ |
---|
| 731 | Change subject of the room. |
---|
| 732 | """ |
---|
| 733 | self.protocol.subject(self.roomJID, u'This is a test') |
---|
| 734 | |
---|
| 735 | message = self.stub.output[-1] |
---|
| 736 | |
---|
| 737 | self.assertEquals('message', message.name) |
---|
| 738 | self.assertEquals(self.roomJID.full(), message.getAttribute('to')) |
---|
| 739 | self.assertEquals('groupchat', message.getAttribute('type')) |
---|
| 740 | self.assertEquals(u'This is a test', unicode(message.subject)) |
---|
| 741 | |
---|
| 742 | |
---|
[145] | 743 | def test_nick(self): |
---|
[130] | 744 | """ |
---|
| 745 | Send a nick change to the server. |
---|
[117] | 746 | """ |
---|
[145] | 747 | newNick = 'newNick' |
---|
[129] | 748 | |
---|
[117] | 749 | self._createRoom() |
---|
| 750 | |
---|
| 751 | def cb(room): |
---|
[144] | 752 | self.assertEquals(self.roomIdentifier, room.roomIdentifier) |
---|
[145] | 753 | self.assertEquals(newNick, room.nick) |
---|
[117] | 754 | |
---|
[145] | 755 | d = self.protocol.nick(self.roomJID, newNick) |
---|
[117] | 756 | d.addCallback(cb) |
---|
| 757 | |
---|
[145] | 758 | element = self.stub.output[-1] |
---|
| 759 | self.assertEquals('presence', element.name, "Need to be presence") |
---|
| 760 | self.assertNotIdentical(None, element.x, 'No muc x element') |
---|
[117] | 761 | |
---|
[135] | 762 | # send back user presence, nick changed |
---|
[145] | 763 | xml = u""" |
---|
| 764 | <presence from='%s/%s'> |
---|
[135] | 765 | <x xmlns='http://jabber.org/protocol/muc#user'> |
---|
| 766 | <item affiliation='member' role='participant'/> |
---|
| 767 | </x> |
---|
| 768 | </presence> |
---|
[145] | 769 | """ % (self.roomJID, newNick) |
---|
| 770 | self.stub.send(parseXml(xml)) |
---|
| 771 | return d |
---|
| 772 | |
---|
| 773 | |
---|
| 774 | def test_nickConflict(self): |
---|
| 775 | """ |
---|
| 776 | If the server finds the new nick in conflict, the errback is called. |
---|
| 777 | """ |
---|
| 778 | newNick = 'newNick' |
---|
| 779 | |
---|
| 780 | self._createRoom() |
---|
| 781 | |
---|
| 782 | d = self.protocol.nick(self.roomJID, newNick) |
---|
| 783 | self.assertFailure(d, StanzaError) |
---|
| 784 | |
---|
| 785 | element = self.stub.output[-1] |
---|
| 786 | self.assertEquals('presence', element.name, "Need to be presence") |
---|
| 787 | self.assertNotIdentical(None, element.x, 'No muc x element') |
---|
| 788 | |
---|
| 789 | # send back user presence, nick changed |
---|
| 790 | xml = u""" |
---|
| 791 | <presence from='%s/%s' type='error'> |
---|
| 792 | <x xmlns='http://jabber.org/protocol/muc'/> |
---|
| 793 | <error type='cancel'> |
---|
| 794 | <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> |
---|
| 795 | </error> |
---|
| 796 | </presence> |
---|
| 797 | """ % (self.roomJID, newNick) |
---|
[135] | 798 | self.stub.send(parseXml(xml)) |
---|
[117] | 799 | return d |
---|
| 800 | |
---|
[129] | 801 | |
---|
[125] | 802 | def test_grantVoice(self): |
---|
[130] | 803 | """ |
---|
| 804 | Test granting voice to a user. |
---|
[125] | 805 | |
---|
| 806 | """ |
---|
[135] | 807 | nick = 'TroubleMaker' |
---|
[125] | 808 | def cb(give_voice): |
---|
[135] | 809 | self.assertTrue(give_voice, 'Did not give voice user') |
---|
[129] | 810 | |
---|
[145] | 811 | d = self.protocol.grantVoice(self.occupantJID, nick, |
---|
| 812 | sender=self.userJID) |
---|
[125] | 813 | d.addCallback(cb) |
---|
| 814 | |
---|
| 815 | iq = self.stub.output[-1] |
---|
[129] | 816 | |
---|
[144] | 817 | query = (u"/iq[@type='set' and @to='%s']/query/item" |
---|
| 818 | "[@role='participant']") % self.roomJID |
---|
[135] | 819 | self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza') |
---|
[125] | 820 | |
---|
| 821 | response = toResponse(iq, 'result') |
---|
| 822 | self.stub.send(response) |
---|
| 823 | return d |
---|
| 824 | |
---|
[126] | 825 | |
---|
[145] | 826 | def test_status(self): |
---|
[130] | 827 | """ |
---|
| 828 | Change status |
---|
[126] | 829 | """ |
---|
| 830 | self._createRoom() |
---|
[145] | 831 | room = self.protocol._getRoom(self.roomJID) |
---|
[144] | 832 | user = muc.User(self.nick) |
---|
[131] | 833 | room.addUser(user) |
---|
[126] | 834 | |
---|
| 835 | def cb(room): |
---|
[144] | 836 | self.assertEquals(self.roomIdentifier, room.roomIdentifier) |
---|
| 837 | user = room.getUser(self.nick) |
---|
[132] | 838 | self.assertNotIdentical(None, user, 'User not found') |
---|
[136] | 839 | self.assertEquals('testing MUC', user.status, 'Wrong status') |
---|
| 840 | self.assertEquals('xa', user.show, 'Wrong show') |
---|
[129] | 841 | |
---|
[145] | 842 | d = self.protocol.status(self.roomJID, 'xa', 'testing MUC') |
---|
[126] | 843 | d.addCallback(cb) |
---|
| 844 | |
---|
[145] | 845 | element = self.stub.output[-1] |
---|
[126] | 846 | |
---|
[145] | 847 | self.assertEquals('presence', element.name, "Need to be presence") |
---|
| 848 | self.assertTrue(getattr(element, 'x', None), 'No muc x element') |
---|
[126] | 849 | |
---|
[135] | 850 | # send back user presence, status changed |
---|
[144] | 851 | xml = u""" |
---|
[135] | 852 | <presence from='%s'> |
---|
| 853 | <x xmlns='http://jabber.org/protocol/muc#user'> |
---|
| 854 | <item affiliation='member' role='participant'/> |
---|
| 855 | </x> |
---|
| 856 | <show>xa</show> |
---|
| 857 | <status>testing MUC</status> |
---|
| 858 | </presence> |
---|
[144] | 859 | """ % self.occupantJID |
---|
[135] | 860 | self.stub.send(parseXml(xml)) |
---|
[129] | 861 | return d |
---|
[130] | 862 | |
---|
| 863 | |
---|
| 864 | def test_getMemberList(self): |
---|
| 865 | def cb(room): |
---|
| 866 | members = room.members |
---|
[136] | 867 | self.assertEquals(1, len(members)) |
---|
[130] | 868 | user = members[0] |
---|
[136] | 869 | self.assertEquals(JID(u'hag66@shakespeare.lit'), user.entity) |
---|
| 870 | self.assertEquals(u'thirdwitch', user.nick) |
---|
| 871 | self.assertEquals(u'participant', user.role) |
---|
[130] | 872 | |
---|
| 873 | self._createRoom() |
---|
[144] | 874 | d = self.protocol.getMemberList(self.roomJID) |
---|
[130] | 875 | d.addCallback(cb) |
---|
| 876 | |
---|
| 877 | iq = self.stub.output[-1] |
---|
| 878 | query = iq.query |
---|
| 879 | self.assertNotIdentical(None, query) |
---|
[136] | 880 | self.assertEquals(NS_MUC_ADMIN, query.uri) |
---|
[130] | 881 | |
---|
| 882 | response = toResponse(iq, 'result') |
---|
| 883 | query = response.addElement((NS_MUC_ADMIN, 'query')) |
---|
| 884 | item = query.addElement('item') |
---|
| 885 | item['affiliation'] ='member' |
---|
| 886 | item['jid'] = 'hag66@shakespeare.lit' |
---|
| 887 | item['nick'] = 'thirdwitch' |
---|
| 888 | item['role'] = 'participant' |
---|
| 889 | self.stub.send(response) |
---|
| 890 | |
---|
| 891 | return d |
---|