]>
code.delx.au - pymsnt/blob - src/tlib/msn/test_msn.py
1 # Copyright (c) 2001-2005 Twisted Matrix Laboratories.
2 # Copyright (c) 2005 James Bunton
3 # See LICENSE for details.
10 from twisted
.protocols
import loopback
11 from twisted
.protocols
.basic
import LineReceiver
12 from twisted
.internet
.defer
import Deferred
13 from twisted
.internet
import reactor
14 from twisted
.trial
import unittest
17 import StringIO
, sys
, urllib
25 class StringIOWithoutClosing(StringIO
.StringIO
):
28 def loseConnection(self
): pass
35 class PassportTests(unittest
.TestCase
):
39 self
.deferred
= Deferred()
40 self
.deferred
.addCallback(lambda r
: self
.result
.append(r
))
41 self
.deferred
.addErrback(printError
)
44 protocol
= msn
.PassportNexus(self
.deferred
, 'https://foobar.com/somepage.quux')
46 'Content-Length' : '0',
47 'Content-Type' : 'text/html',
48 'PassportURLs' : 'DARealm=Passport.Net,DALogin=login.myserver.com/,DAReg=reg.myserver.com'
50 transport
= StringIOWithoutClosing()
51 protocol
.makeConnection(transport
)
52 protocol
.dataReceived('HTTP/1.0 200 OK\r\n')
53 for (h
,v
) in headers
.items(): protocol
.dataReceived('%s: %s\r\n' % (h
,v
))
54 protocol
.dataReceived('\r\n')
55 self
.failUnless(self
.result
[0] == "https://login.myserver.com/")
57 def _doLoginTest(self
, response
, headers
):
58 protocol
= msn
.PassportLogin(self
.deferred
,'foo@foo.com','testpass','https://foo.com/', 'a')
59 protocol
.makeConnection(StringIOWithoutClosing())
60 protocol
.dataReceived(response
)
61 for (h
,v
) in headers
.items(): protocol
.dataReceived('%s: %s\r\n' % (h
,v
))
62 protocol
.dataReceived('\r\n')
64 def testPassportLoginSuccess(self
):
66 'Content-Length' : '0',
67 'Content-Type' : 'text/html',
68 'Authentication-Info' : "Passport1.4 da-status=success,tname=MSPAuth," +
69 "tname=MSPProf,tname=MSPSec,from-PP='somekey'," +
70 "ru=http://messenger.msn.com"
72 self
._doLoginTest
('HTTP/1.1 200 OK\r\n', headers
)
73 self
.failUnless(self
.result
[0] == (msn
.LOGIN_SUCCESS
, 'somekey'))
75 def testPassportLoginFailure(self
):
77 'Content-Type' : 'text/html',
78 'WWW-Authenticate' : 'Passport1.4 da-status=failed,' +
79 'srealm=Passport.NET,ts=-3,prompt,cburl=http://host.com,' +
80 'cbtxt=the%20error%20message'
82 self
._doLoginTest
('HTTP/1.1 401 Unauthorized\r\n', headers
)
83 self
.failUnless(self
.result
[0] == (msn
.LOGIN_FAILURE
, 'the error message'))
85 def testPassportLoginRedirect(self
):
87 'Content-Type' : 'text/html',
88 'Authentication-Info' : 'Passport1.4 da-status=redir',
89 'Location' : 'https://newlogin.host.com/'
91 self
._doLoginTest
('HTTP/1.1 302 Found\r\n', headers
)
92 self
.failUnless(self
.result
[0] == (msn
.LOGIN_REDIRECT
, 'https://newlogin.host.com/', 'a'))
96 ######################
97 # Notification tests #
98 ######################
100 class DummyNotificationClient(msn
.NotificationClient
):
101 def loggedIn(self
, userHandle
, verified
):
102 if userHandle
== 'foo@bar.com' and verified
:
105 def gotProfile(self
, message
):
106 self
.state
= 'PROFILE'
108 def gotContactStatus(self
, code
, userHandle
, screenName
):
109 if code
== msn
.STATUS_AWAY
and userHandle
== "foo@bar.com" and screenName
== "Test Screen Name":
110 c
= self
.factory
.contacts
.getContact(userHandle
)
111 if c
.caps
& msn
.MSNContact
.MSNC1
and c
.msnobj
:
112 self
.state
= 'INITSTATUS'
114 def contactStatusChanged(self
, code
, userHandle
, screenName
):
115 if code
== msn
.STATUS_LUNCH
and userHandle
== "foo@bar.com" and screenName
== "Test Name":
116 self
.state
= 'NEWSTATUS'
118 def contactAvatarChanged(self
, userHandle
, hash):
119 if userHandle
== "foo@bar.com" and hash == "trC8SlFx2sWQxZMIBAWSEnXc8oQ=":
120 self
.state
= 'NEWAVATAR'
121 elif self
.state
== 'NEWAVATAR' and hash == "":
122 self
.state
= 'AVATARGONE'
124 def contactPersonalChanged(self
, userHandle
, personal
):
125 if userHandle
== 'foo@bar.com' and personal
== 'My Personal Message':
126 self
.state
= 'GOTPERSONAL'
127 elif userHandle
== 'foo@bar.com' and personal
== '':
128 self
.state
= 'PERSONALGONE'
130 def contactOffline(self
, userHandle
):
131 if userHandle
== "foo@bar.com": self
.state
= 'OFFLINE'
133 def statusChanged(self
, code
):
134 if code
== msn
.STATUS_HIDDEN
: self
.state
= 'MYSTATUS'
136 def listSynchronized(self
, *args
):
137 self
.state
= 'GOTLIST'
139 def gotPhoneNumber(self
, userHandle
, phoneType
, number
):
140 self
.state
= 'GOTPHONE'
142 def userRemovedMe(self
, userHandle
):
143 c
= self
.factory
.contacts
.getContact(userHandle
)
144 if not c
: self
.state
= 'USERREMOVEDME'
146 def userAddedMe(self
, userGuid
, userHandle
, screenName
):
147 c
= self
.factory
.contacts
.getContact(userHandle
)
148 if c
and (c
.lists | msn
.PENDING_LIST
) and (screenName
== 'Screen Name'):
149 self
.state
= 'USERADDEDME'
151 def gotSwitchboardInvitation(self
, sessionID
, host
, port
, key
, userHandle
, screenName
):
152 if sessionID
== 1234 and \
153 host
== '192.168.1.1' and \
155 key
== '123.456' and \
156 userHandle
== 'foo@foo.com' and \
157 screenName
== 'Screen Name':
158 self
.state
= 'SBINVITED'
160 def gotMSNAlert(self
, body
, action
, subscr
):
161 self
.state
= 'NOTIFICATION'
163 def gotInitialEmailNotification(self
, inboxunread
, foldersunread
):
164 if inboxunread
== 1 and foldersunread
== 0:
165 self
.state
= 'INITEMAIL1'
167 self
.state
= 'INITEMAIL2'
169 def gotRealtimeEmailNotification(self
, mailfrom
, fromaddr
, subject
):
170 if mailfrom
== 'Some Person' and fromaddr
== 'example@passport.com' and subject
== 'newsubject':
171 self
.state
= 'REALTIMEEMAIL'
173 class NotificationTests(unittest
.TestCase
):
174 """ testing the various events in NotificationClient """
177 self
.client
= DummyNotificationClient()
178 self
.client
.factory
= msn
.NotificationFactory()
179 self
.client
.state
= 'START'
185 self
.client
.lineReceived('USR 1 OK foo@bar.com 1')
186 self
.failUnless((self
.client
.state
== 'LOGIN'), 'Failed to detect successful login')
188 def testProfile(self
):
189 m
= 'MSG Hotmail Hotmail 353\r\nMIME-Version: 1.0\r\nContent-Type: text/x-msmsgsprofile; charset=UTF-8\r\n'
190 m
+= 'LoginTime: 1016941010\r\nEmailEnabled: 1\r\nMemberIdHigh: 40000\r\nMemberIdLow: -600000000\r\nlang_preference: 1033\r\n'
191 m
+= 'preferredEmail: foo@bar.com\r\ncountry: AU\r\nPostalCode: 90210\r\nGender: M\r\nKid: 0\r\nAge:\r\nsid: 400\r\n'
192 m
+= 'kv: 2\r\nMSPAuth: 2CACCBCCADMoV8ORoz64BVwmjtksIg!kmR!Rj5tBBqEaW9hc4YnPHSOQ$$\r\n\r\n'
193 self
.client
.dataReceived(m
)
194 self
.failUnless((self
.client
.state
== 'PROFILE'), 'Failed to detect initial profile')
196 def testInitialEmailNotification(self
):
197 m
= 'MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinitialemailnotification; charset=UTF-8\r\n'
198 m
+= '\r\nInbox-Unread: 1\r\nFolders-Unread: 0\r\nInbox-URL: /cgi-bin/HoTMaiL\r\n'
199 m
+= 'Folders-URL: /cgi-bin/folders\r\nPost-URL: http://www.hotmail.com\r\n\r\n'
200 m
= 'MSG Hotmail Hotmail %s\r\n' % (str(len(m
))) + m
201 self
.client
.dataReceived(m
)
202 self
.failUnless((self
.client
.state
== 'INITEMAIL1'), 'Failed to detect initial email notification')
204 def testNoInitialEmailNotification(self
):
205 m
= 'MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinitialemailnotification; charset=UTF-8\r\n'
206 m
+= '\r\nInbox-Unread: 0\r\nFolders-Unread: 0\r\nInbox-URL: /cgi-bin/HoTMaiL\r\n'
207 m
+= 'Folders-URL: /cgi-bin/folders\r\nPost-URL: http://www.hotmail.com\r\n\r\n'
208 m
= 'MSG Hotmail Hotmail %s\r\n' % (str(len(m
))) + m
209 self
.client
.dataReceived(m
)
210 self
.failUnless((self
.client
.state
!= 'INITEMAIL2'), 'Detected initial email notification when I should not have')
212 def testRealtimeEmailNotification(self
):
213 m
= 'MSG Hotmail Hotmail 356\r\nMIME-Version: 1.0\r\nContent-Type: text/x-msmsgsemailnotification; charset=UTF-8\r\n'
214 m
+= '\r\nFrom: Some Person\r\nMessage-URL: /cgi-bin/getmsg?msg=MSG1050451140.21&start=2310&len=2059&curmbox=ACTIVE\r\n'
215 m
+= 'Post-URL: https://loginnet.passport.com/ppsecure/md5auth.srf?lc=1038\r\n'
216 m
+= 'Subject: =?"us-ascii"?Q?newsubject?=\r\nDest-Folder: ACTIVE\r\nFrom-Addr: example@passport.com\r\nid: 2\r\n'
217 self
.client
.dataReceived(m
)
218 self
.failUnless((self
.client
.state
== 'REALTIMEEMAIL'), 'Failed to detect realtime email notification')
220 def testMSNAlert(self
):
221 m
= '<NOTIFICATION ver="2" id="1342902633" siteid="199999999" siteurl="http://alerts.msn.com">\r\n'
222 m
+= '<TO pid="0x0006BFFD:0x8582C0FB" name="example@passport.com"/>\r\n'
223 m
+= '<MSG pri="1" id="1342902633">\r\n'
224 m
+= '<SUBSCR url="http://g.msn.com/3ALMSNTRACKING/199999999ToastChange?http://alerts.msn.com/Alerts/MyAlerts.aspx?strela=1"/>\r\n'
225 m
+= '<ACTION url="http://g.msn.com/3ALMSNTRACKING/199999999ToastAction?http://alerts.msn.com/Alerts/MyAlerts.aspx?strela=1"/>\r\n'
226 m
+= '<BODY lang="3076" icon="">\r\n'
227 m
+= '<TEXT>utf8-encoded text</TEXT></BODY></MSG>\r\n'
228 m
+= '</NOTIFICATION>\r\n'
229 cmd
= 'NOT %s\r\n' % str(len(m
))
231 # Whee, lots of fun to test that lineReceived & dataReceived work well with input coming
232 # in in (fairly) arbitrary chunks.
233 map(self
.client
.dataReceived
, [x
+'\r\n' for x
in m
.split('\r\n')[:-1]])
234 self
.failUnless((self
.client
.state
== 'NOTIFICATION'), 'Failed to detect MSN Alert message')
236 def testListSync(self
):
237 self
.client
.makeConnection(StringIOWithoutClosing())
238 msn
.NotificationClient
.loggedIn(self
.client
, 'foo@foo.com', 1)
240 "SYN %s 2005-04-23T18:57:44.8130000-07:00 2005-04-23T18:57:54.2070000-07:00 1 3" % self
.client
.currentID
,
243 "LSG Friends yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
244 "LSG Other%20Friends yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyz",
245 "LSG More%20Other%20Friends yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyya",
246 "LST N=userHandle@email.com F=Some%20Name C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 13 yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy,yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyz",
248 map(self
.client
.lineReceived
, lines
)
249 contacts
= self
.client
.factory
.contacts
250 contact
= contacts
.getContact('userHandle@email.com')
251 #self.failUnless(contacts.version == 100, "Invalid contact list version")
252 self
.failUnless(contact
.screenName
== 'Some Name', "Invalid screen-name for user")
253 self
.failUnless(contacts
.groups
== {'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy': 'Friends', \
254 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyz': 'Other Friends', \
255 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyya': 'More Other Friends'} \
256 , "Did not get proper group list")
257 self
.failUnless(contact
.groups
== ['yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy', \
258 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyz'] and \
259 contact
.lists
== 13, "Invalid contact list/group info")
260 self
.failUnless(self
.client
.state
== 'GOTLIST', "Failed to call list sync handler")
263 def testStatus(self
):
264 # Set up the contact list
265 self
.client
.makeConnection(StringIOWithoutClosing())
266 msn
.NotificationClient
.loggedIn(self
.client
, 'foo@foo.com', 1)
268 "SYN %s 2005-04-23T18:57:44.8130000-07:00 2005-04-23T18:57:54.2070000-07:00 1 0" % self
.client
.currentID
,
271 "LST N=foo@bar.com F=Some%20Name C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 13 yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy,yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyz",
273 map(self
.client
.lineReceived
, lines
)
275 msnobj
= urllib
.quote('<msnobj Creator="buddy1@hotmail.com" Size="24539" Type="3" Location="TFR2C.tmp" Friendly="AAA=" SHA1D="trC8SlFx2sWQxZMIBAWSEnXc8oQ=" SHA1C="U32o6bosZzluJq82eAtMpx5dIEI="/>')
276 t
= [('ILN 1 AWY foo@bar.com Test%20Screen%20Name 268435456 ' + msnobj
, 'INITSTATUS', 'Failed to detect initial status report'),
277 ('NLN LUN foo@bar.com Test%20Name 0', 'NEWSTATUS', 'Failed to detect contact status change'),
278 ('NLN AWY foo@bar.com Test%20Name 0 ' + msnobj
, 'NEWAVATAR', 'Failed to detect contact avatar change'),
279 ('NLN AWY foo@bar.com Test%20Name 0', 'AVATARGONE', 'Failed to detect contact avatar disappearing'),
280 ('FLN foo@bar.com', 'OFFLINE', 'Failed to detect contact signing off'),
281 ('CHG 1 HDN 0 ' + msnobj
, 'MYSTATUS', 'Failed to detect my status changing')]
283 self
.client
.lineReceived(i
[0])
284 self
.failUnless((self
.client
.state
== i
[1]), i
[2])
287 self
.client
.dataReceived('UBX foo@bar.com 72\r\n<Data><PSM>My Personal Message</PSM><CurrentMedia></CurrentMedia></Data>')
288 self
.failUnless((self
.client
.state
== 'GOTPERSONAL'), 'Failed to detect new personal message')
289 self
.client
.dataReceived('UBX foo@bar.com 0\r\n')
290 self
.failUnless((self
.client
.state
== 'PERSONALGONE'), 'Failed to detect personal message disappearing')
293 def testAsyncPhoneChange(self
):
294 c
= msn
.MSNContact(userHandle
='userHandle@email.com')
295 self
.client
.factory
.contacts
= msn
.MSNContactList()
296 self
.client
.factory
.contacts
.addContact(c
)
297 self
.client
.makeConnection(StringIOWithoutClosing())
298 self
.client
.lineReceived("BPR 101 userHandle@email.com PHH 123%20456")
299 c
= self
.client
.factory
.contacts
.getContact('userHandle@email.com')
300 self
.failUnless(self
.client
.state
== 'GOTPHONE', "Did not fire phone change callback")
301 self
.failUnless(c
.homePhone
== '123 456', "Did not update the contact's phone number")
302 self
.failUnless(self
.client
.factory
.contacts
.version
== 101, "Did not update list version")
304 def testLateBPR(self
):
306 This test makes sure that if a BPR response that was meant
307 to be part of a SYN response (but came after the last LST)
308 is received, the correct contact is updated and all is well
310 self
.client
.makeConnection(StringIOWithoutClosing())
311 msn
.NotificationClient
.loggedIn(self
.client
, 'foo@foo.com', 1)
313 "SYN %s 2005-04-23T18:57:44.8130000-07:00 2005-04-23T18:57:54.2070000-07:00 1 0" % self
.client
.currentID
,
316 "LSG Friends yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
317 "LST N=userHandle@email.com F=Some%20Name C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 13 yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
320 map(self
.client
.lineReceived
, lines
)
321 contact
= self
.client
.factory
.contacts
.getContact('userHandle@email.com')
322 self
.failUnless(contact
.homePhone
== '123 456', "Did not update contact's phone number")
325 def testUserRemovedMe(self
):
326 self
.client
.factory
.contacts
= msn
.MSNContactList()
327 contact
= msn
.MSNContact(userHandle
='foo@foo.com')
328 contact
.addToList(msn
.REVERSE_LIST
)
329 self
.client
.factory
.contacts
.addContact(contact
)
330 self
.client
.lineReceived("REM 0 RL foo@foo.com")
331 self
.failUnless(self
.client
.state
== 'USERREMOVEDME', "Failed to remove user from reverse list")
333 def testUserAddedMe(self
):
334 self
.client
.factory
.contacts
= msn
.MSNContactList()
335 self
.client
.lineReceived("ADC 0 RL N=foo@foo.com F=Screen%20Name")
336 self
.failUnless(self
.client
.state
== 'USERADDEDME', "Failed to add user to reverse lise")
338 def testAsyncSwitchboardInvitation(self
):
339 self
.client
.lineReceived("RNG 1234 192.168.1.1:1863 CKI 123.456 foo@foo.com Screen%20Name")
340 self
.failUnless((self
.client
.state
== 'SBINVITED'), 'Failed to detect switchboard invitation')
343 #######################################
344 # Notification with fake server tests #
345 #######################################
347 class FakeNotificationServer(msn
.MSNEventBase
):
348 def handle_CHG(self
, params
):
351 self
.sendLine("CHG %s %s %s %s" % (params
[0], params
[1], params
[2], params
[3]))
353 def handle_BLP(self
, params
):
354 self
.sendLine("BLP %s %s 100" % (params
[0], params
[1]))
356 def handle_ADC(self
, params
):
358 list = msn
.listCodeToID
[params
[1].lower()]
359 if list == msn
.FORWARD_LIST
:
373 if userHandle
and userGuid
:
374 self
.transport
.loseConnection()
378 self
.transport
.loseConnection()
380 self
.sendLine("ADC %s FL N=%s F=%s C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx %s" % (trid
, userHandle
, screenName
, groups
))
383 raise "NotImplementedError"
386 self
.transport
.loseConnection()
387 if not params
[2].startswith("N=") and params
[2].count('@') == 1:
388 self
.transport
.loseConnection()
389 self
.sendLine("ADC %s %s %s" % (params
[0], params
[1], params
[2]))
391 def handle_REM(self
, params
):
393 self
.transport
.loseConnection()
396 trid
= int(params
[0])
397 listType
= msn
.listCodeToID
[params
[1].lower()]
399 self
.transport
.loseConnection()
400 if listType
== msn
.FORWARD_LIST
and params
[2].count('@') > 0:
401 self
.transport
.loseConnection()
402 elif listType
!= msn
.FORWARD_LIST
and params
[2].count('@') != 1:
403 self
.transport
.loseConnection()
405 self
.sendLine("REM %s %s %s" % (params
[0], params
[1], params
[2]))
407 def handle_PRP(self
, params
):
409 self
.transport
.loseConnection()
410 if params
[1] == "MFN":
411 self
.sendLine("PRP %s MFN %s" % (params
[0], params
[2]))
413 # Only friendly names are implemented
414 self
.transport
.loseConnection()
416 def handle_UUX(self
, params
):
418 self
.transport
.loseConnection()
422 self
.currentMessage
= msn
.MSNMessage(length
=l
, userHandle
=params
[0], screenName
="UUX", specialMessage
=True)
425 self
.sendLine("UUX %s 0" % params
[0])
427 def checkMessage(self
, message
):
428 if message
.specialMessage
:
429 if message
.screenName
== "UUX":
430 self
.sendLine("UUX %s 0" % message
.userHandle
)
434 def handle_XFR(self
, params
):
436 self
.transport
.loseConnection()
438 if params
[1] != "SB":
439 self
.transport
.loseConnection()
441 self
.sendLine("XFR %s SB 129.129.129.129:1234 CKI SomeSecret" % params
[0])
445 class FakeNotificationClient(msn
.NotificationClient
):
446 def doStatusChange(self
):
447 def testcb((status
,)):
448 if status
== msn
.STATUS_AWAY
:
450 self
.transport
.loseConnection()
451 d
= self
.changeStatus(msn
.STATUS_AWAY
)
452 d
.addCallback(testcb
)
454 def doPrivacyMode(self
):
456 if priv
.upper() == 'AL':
458 self
.transport
.loseConnection()
459 d
= self
.setPrivacyMode(True)
460 d
.addCallback(testcb
)
462 def doAddContactFL(self
):
463 def testcb((listType
, userGuid
, userHandle
, screenName
)):
464 if listType
& msn
.FORWARD_LIST
and \
465 userGuid
== "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" and \
466 userHandle
== "foo@bar.com" and \
467 screenName
== "foo@bar.com":
469 self
.transport
.loseConnection()
470 d
= self
.addContact(msn
.FORWARD_LIST
, "foo@bar.com")
471 d
.addCallback(testcb
)
473 def doAddContactAL(self
):
474 def testcb((listType
, userGuid
, userHandle
, screenName
)):
475 if listType
& msn
.ALLOW_LIST
and \
476 userHandle
== "foo@bar.com" and \
477 not userGuid
and not screenName
:
479 self
.transport
.loseConnection()
480 d
= self
.addContact(msn
.ALLOW_LIST
, "foo@bar.com")
481 d
.addCallback(testcb
)
483 def doRemContactFL(self
):
484 def testcb((listType
, userHandle
, groupID
)):
485 if listType
& msn
.FORWARD_LIST
and \
486 userHandle
== "foo@bar.com":
488 self
.transport
.loseConnection()
489 d
= self
.remContact(msn
.FORWARD_LIST
, "foo@bar.com")
490 d
.addCallback(testcb
)
492 def doRemContactAL(self
):
493 def testcb((listType
, userHandle
, groupID
)):
494 if listType
& msn
.ALLOW_LIST
and \
495 userHandle
== "foo@bar.com":
497 self
.transport
.loseConnection()
498 d
= self
.remContact(msn
.ALLOW_LIST
, "foo@bar.com")
499 d
.addCallback(testcb
)
501 def doScreenNameChange(self
):
502 def testcb((screenName
,)):
503 if screenName
== "Some new name":
505 self
.transport
.loseConnection()
506 d
= self
.changeScreenName("Some new name")
507 d
.addCallback(testcb
)
509 def doPersonalChange(self
, personal
):
510 def testcb((checkPersonal
,)):
511 if checkPersonal
== personal
:
513 self
.transport
.loseConnection()
514 d
= self
.changePersonalMessage(personal
)
515 d
.addCallback(testcb
)
517 def doAvatarChange(self
, data
):
520 self
.transport
.loseConnection()
521 d
= self
.changeAvatar(data
, True)
522 d
.addCallback(testcb
)
524 def doRequestSwitchboard(self
):
525 def testcb((host
, port
, key
)):
526 if host
== "129.129.129.129" and port
== 1234 and key
== "SomeSecret":
528 self
.transport
.loseConnection()
529 d
= self
.requestSwitchboardServer()
530 d
.addCallback(testcb
)
532 class FakeServerNotificationTests(unittest
.TestCase
):
533 """ tests the NotificationClient against a fake server. """
536 self
.client
= FakeNotificationClient()
537 self
.client
.factory
= msn
.NotificationFactory()
538 self
.client
.test
= 'FAIL'
539 self
.server
= FakeNotificationServer()
544 def testChangeStatus(self
):
545 reactor
.callLater(0, self
.client
.doStatusChange
)
546 loopback
.loopback(self
.client
, self
.server
)
547 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to change status properly')
549 def testSetPrivacyMode(self
):
550 self
.client
.factory
.contacts
= msn
.MSNContactList()
551 reactor
.callLater(0, self
.client
.doPrivacyMode
)
552 loopback
.loopback(self
.client
, self
.server
)
553 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to change privacy mode')
555 def testSyncList(self
):
556 reactor
.callLater(0, self
.client
.doSyncList
)
557 loopback
.loopback(self
.client
, self
.server
)
558 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to synchronise list')
559 testSyncList
.skip
= "Will do after list versions."
561 def testAddContactFL(self
):
562 self
.client
.factory
.contacts
= msn
.MSNContactList()
563 reactor
.callLater(0, self
.client
.doAddContactFL
)
564 loopback
.loopback(self
.client
, self
.server
)
565 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to add contact to forward list')
567 def testAddContactAL(self
):
568 self
.client
.factory
.contacts
= msn
.MSNContactList()
569 reactor
.callLater(0, self
.client
.doAddContactAL
)
570 loopback
.loopback(self
.client
, self
.server
)
571 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to add contact to allow list')
573 def testRemContactFL(self
):
574 self
.client
.factory
.contacts
= msn
.MSNContactList()
575 self
.client
.factory
.contacts
.addContact(msn
.MSNContact(userGuid
="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", userHandle
="foo@bar.com", screenName
="Some guy", lists
=msn
.FORWARD_LIST
))
576 reactor
.callLater(0, self
.client
.doRemContactFL
)
577 loopback
.loopback(self
.client
, self
.server
)
578 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to remove contact from forward list')
580 def testRemContactAL(self
):
581 self
.client
.factory
.contacts
= msn
.MSNContactList()
582 self
.client
.factory
.contacts
.addContact(msn
.MSNContact(userGuid
="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", userHandle
="foo@bar.com", screenName
="Some guy", lists
=msn
.ALLOW_LIST
))
583 reactor
.callLater(0, self
.client
.doRemContactAL
)
584 loopback
.loopback(self
.client
, self
.server
)
585 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to remove contact from allow list')
587 def testChangedScreenName(self
):
588 reactor
.callLater(0, self
.client
.doScreenNameChange
)
589 loopback
.loopback(self
.client
, self
.server
)
590 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to change screen name properly')
592 def testChangePersonal1(self
):
593 reactor
.callLater(0, lambda: self
.client
.doPersonalChange("Some personal message"))
594 loopback
.loopback(self
.client
, self
.server
)
595 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to change personal message properly')
597 def testChangePersonal2(self
):
598 reactor
.callLater(0, lambda: self
.client
.doPersonalChange(""))
599 loopback
.loopback(self
.client
, self
.server
)
600 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to change personal message properly')
602 def testChangeAvatar(self
):
603 reactor
.callLater(0, lambda: self
.client
.doAvatarChange("DATADATADATADATA"))
604 loopback
.loopback(self
.client
, self
.server
)
605 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to change avatar properly')
607 def testRequestSwitchboard(self
):
608 reactor
.callLater(0, self
.client
.doRequestSwitchboard
)
609 loopback
.loopback(self
.client
, self
.server
)
610 self
.failUnless((self
.client
.test
== 'PASS'), 'Failed to request switchboard')
613 #################################
614 # Notification Challenges tests #
615 #################################
617 class DummyChallengeNotificationServer(msn
.MSNEventBase
):
618 def doChallenge(self
, challenge
, response
):
620 self
.response
= response
621 self
.sendLine("CHL 0 " + challenge
)
623 def checkMessage(self
, message
):
624 if message
.message
== self
.response
:
626 self
.transport
.loseConnection()
629 def handle_QRY(self
, params
):
631 if len(params
) == 3 and params
[1] == "PROD0090YUAUV{2B" and params
[2] == "32":
633 self
.currentMessage
= msn
.MSNMessage(length
=32, userHandle
="QRY", screenName
="QRY", specialMessage
=True)
636 self
.transport
.loseConnection()
638 class DummyChallengeNotificationClient(msn
.NotificationClient
):
639 def connectionMade(self
):
640 msn
.MSNEventBase
.connectionMade(self
)
642 def handle_CHL(self
, params
):
643 msn
.NotificationClient
.handle_CHL(self
, params
)
644 self
.transport
.loseConnection()
647 class NotificationChallengeTests(unittest
.TestCase
):
648 """ tests the responses to the CHLs the server sends """
651 self
.client
= DummyChallengeNotificationClient()
652 self
.server
= DummyChallengeNotificationServer()
657 def testChallenges(self
):
658 challenges
= [('13038318816579321232', 'b01c13020e374d4fa20abfad6981b7a9'),
659 ('23055170411503520698', 'ae906c3f2946d25e7da1b08b0b247659'),
660 ('37819769320541083311', 'db79d37dadd9031bef996893321da480'),
661 ('93662730714769834295', 'd619dfbb1414004d34d0628766636568')]
662 for challenge
, response
in challenges
:
663 reactor
.callLater(0, lambda: self
.server
.doChallenge(challenge
, response
))
664 loopback
.loopback(self
.client
, self
.server
)
665 self
.failUnless((self
.server
.state
== 'PASS'), 'Incorrect challenge response.')
668 ###########################
669 # Notification ping tests #
670 ###########################
672 class DummyPingNotificationServer(LineReceiver
):
673 def lineReceived(self
, line
):
674 if line
.startswith("PNG") and self
.good
:
675 self
.sendLine("QNG 50")
677 class DummyPingNotificationClient(msn
.NotificationClient
):
678 def connectionMade(self
):
679 self
.pingCheckerStart()
681 def sendLine(self
, line
):
682 msn
.NotificationClient
.sendLine(self
, line
)
685 self
.transport
.loseConnection() # But not for real, just to end the test
687 def connectionLost(self
, reason
):
689 self
.state
= 'DISCONNECTED'
691 class NotificationPingTests(unittest
.TestCase
):
692 """ tests pinging in the NotificationClient class """
696 self
.client
= DummyPingNotificationClient()
697 self
.server
= DummyPingNotificationServer()
698 self
.client
.state
= 'CONNECTED'
699 self
.client
.count
= 0
705 def testPingGood(self
):
706 self
.server
.good
= True
707 loopback
.loopback(self
.client
, self
.server
)
708 self
.failUnless((self
.client
.state
== 'CONNECTED'), 'Should be connected.')
710 def testPingBad(self
):
711 self
.server
.good
= False
712 loopback
.loopback(self
.client
, self
.server
)
713 self
.failUnless((self
.client
.state
== 'DISCONNECTED'), 'Should be disconnected.')
718 #############################
719 # Switchboard message tests #
720 #############################
722 class DummySwitchboardClient(msn
.SwitchboardClient
):
723 def userTyping(self
, message
):
724 self
.state
= 'TYPING'
726 def gotSendRequest(self
, fileName
, fileSize
, cookie
, message
):
727 if fileName
== 'foobar.ext' and fileSize
== 31337 and cookie
== 1234: self
.state
= 'INVITATION'
730 class MessageHandlingTests(unittest
.TestCase
):
731 """ testing various message handling methods from SwichboardClient """
735 self
.client
= DummySwitchboardClient()
736 self
.client
.state
= 'START'
741 def testTypingCheck(self
):
743 m
.setHeader('Content-Type', 'text/x-msmsgscontrol')
744 m
.setHeader('TypingUser', 'foo@bar')
745 self
.client
.checkMessage(m
)
746 self
.failUnless((self
.client
.state
== 'TYPING'), 'Failed to detect typing notification')
748 def testFileInvitation(self
, lazyClient
=False):
750 m
.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
751 m
.message
+= 'Application-Name: File Transfer\r\n'
753 m
.message
+= 'Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n'
754 m
.message
+= 'Invitation-Command: Invite\r\n'
755 m
.message
+= 'Invitation-Cookie: 1234\r\n'
756 m
.message
+= 'Application-File: foobar.ext\r\n'
757 m
.message
+= 'Application-FileSize: 31337\r\n\r\n'
758 self
.client
.checkMessage(m
)
759 self
.failUnless((self
.client
.state
== 'INVITATION'), 'Failed to detect file transfer invitation')
761 def testFileInvitationMissingGUID(self
):
762 return self
.testFileInvitation(True)
764 def testFileResponse(self
):
766 d
.addCallback(self
.fileResponse
)
767 self
.client
.cookies
['iCookies'][1234] = (d
, None)
769 m
.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
770 m
.message
+= 'Invitation-Command: ACCEPT\r\n'
771 m
.message
+= 'Invitation-Cookie: 1234\r\n\r\n'
772 self
.client
.checkMessage(m
)
773 self
.failUnless((self
.client
.state
== 'RESPONSE'), 'Failed to detect file transfer response')
775 def testFileInfo(self
):
777 d
.addCallback(self
.fileInfo
)
778 self
.client
.cookies
['external'][1234] = (d
, None)
780 m
.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
781 m
.message
+= 'Invitation-Command: ACCEPT\r\n'
782 m
.message
+= 'Invitation-Cookie: 1234\r\n'
783 m
.message
+= 'IP-Address: 192.168.0.1\r\n'
784 m
.message
+= 'Port: 6891\r\n'
785 m
.message
+= 'AuthCookie: 4321\r\n\r\n'
786 self
.client
.checkMessage(m
)
787 self
.failUnless((self
.client
.state
== 'INFO'), 'Failed to detect file transfer info')
789 def fileResponse(self
, (accept
, cookie
, info
)):
790 if accept
and cookie
== 1234: self
.client
.state
= 'RESPONSE'
792 def fileInfo(self
, (accept
, ip
, port
, aCookie
, info
)):
793 if accept
and ip
== '192.168.0.1' and port
== 6891 and aCookie
== 4321: self
.client
.state
= 'INFO'
801 class FileTransferTestCase(unittest
.TestCase
):
802 """ test FileSend against FileReceive """
806 self
.input = StringIOWithoutClosing()
807 self
.input.writelines(['a'] * 7000)
809 self
.output
= StringIOWithoutClosing()
815 def testFileTransfer(self
):
817 sender
= msn
.FileSend(self
.input)
819 sender
.fileSize
= 7000
820 client
= msn
.FileReceive(auth
, "foo@bar.com", self
.output
)
821 client
.fileSize
= 7000
822 loopback
.loopback(sender
, client
)
823 self
.failUnless((client
.completed
and sender
.completed
), "send failed to complete")
824 self
.failUnless((self
.input.getvalue() == self
.output
.getvalue()), "saved file does not match original")