]> code.delx.au - pymsnt/blob - src/tlib/msn/test_msn.py
Added auto-reconnect.
[pymsnt] / src / tlib / msn / test_msn.py
1 # Copyright (c) 2001-2005 Twisted Matrix Laboratories.
2 # Copyright (c) 2005-2006 James Bunton
3 # See LICENSE for details.
4
5 """
6 Test cases for msn.
7 """
8
9 # Settings
10 LOGGING = False
11
12
13
14
15 # Twisted imports
16 from twisted.protocols import loopback
17 from twisted.protocols.basic import LineReceiver
18 from twisted.internet.defer import Deferred
19 from twisted.internet import reactor, main
20 from twisted.python import failure, log
21 from twisted.trial import unittest
22
23 # System imports
24 import StringIO, sys, urllib, random, struct
25
26 import msn
27
28 if LOGGING:
29 log.startLogging(sys.stdout)
30
31
32 def printError(f):
33 print f
34
35 class StringIOWithoutClosing(StringIO.StringIO):
36 disconnecting = 0
37 def close(self): pass
38 def loseConnection(self): pass
39
40 class LoopbackCon:
41 def __init__(self, con1, con2):
42 self.con1 = con1
43 self.con2 = con2
44 self.con1ToCon2 = loopback.LoopbackRelay(con1)
45 self.con2ToCon1 = loopback.LoopbackRelay(con2)
46 con2.makeConnection(self.con1ToCon2)
47 con1.makeConnection(self.con2ToCon1)
48 self.connected = True
49
50 def doSteps(self, steps=1):
51 """ Returns true if the connection finished """
52 count = 0
53 while count < steps:
54 reactor.iterate(0.01)
55 self.con1ToCon2.clearBuffer()
56 self.con2ToCon1.clearBuffer()
57 if self.con1ToCon2.shouldLose:
58 self.con1ToCon2.clearBuffer()
59 count = -1
60 break
61 elif self.con2ToCon1.shouldLose:
62 count = -1
63 break
64 else:
65 count += 1
66 if count == -1:
67 self.disconnect()
68 return True
69 return False
70
71 def disconnect(self):
72 if self.connected:
73 self.con1.connectionLost(failure.Failure(main.CONNECTION_DONE))
74 self.con2.connectionLost(failure.Failure(main.CONNECTION_DONE))
75 reactor.iterate()
76
77
78
79
80 ##################
81 # Passport tests #
82 ##################
83
84 class PassportTests(unittest.TestCase):
85
86 def setUp(self):
87 self.result = []
88 self.deferred = Deferred()
89 self.deferred.addCallback(lambda r: self.result.append(r))
90 self.deferred.addErrback(printError)
91
92 def testNexus(self):
93 protocol = msn.PassportNexus(self.deferred, 'https://foobar.com/somepage.quux')
94 headers = {
95 'Content-Length' : '0',
96 'Content-Type' : 'text/html',
97 'PassportURLs' : 'DARealm=Passport.Net,DALogin=login.myserver.com/,DAReg=reg.myserver.com'
98 }
99 transport = StringIOWithoutClosing()
100 protocol.makeConnection(transport)
101 protocol.dataReceived('HTTP/1.0 200 OK\r\n')
102 for (h,v) in headers.items(): protocol.dataReceived('%s: %s\r\n' % (h,v))
103 protocol.dataReceived('\r\n')
104 self.failUnless(self.result[0] == "https://login.myserver.com/")
105
106 def _doLoginTest(self, response, headers):
107 protocol = msn.PassportLogin(self.deferred,'foo@foo.com','testpass','https://foo.com/', 'a')
108 protocol.makeConnection(StringIOWithoutClosing())
109 protocol.dataReceived(response)
110 for (h,v) in headers.items(): protocol.dataReceived('%s: %s\r\n' % (h,v))
111 protocol.dataReceived('\r\n')
112
113 def testPassportLoginSuccess(self):
114 headers = {
115 'Content-Length' : '0',
116 'Content-Type' : 'text/html',
117 'Authentication-Info' : "Passport1.4 da-status=success,tname=MSPAuth," +
118 "tname=MSPProf,tname=MSPSec,from-PP='somekey'," +
119 "ru=http://messenger.msn.com"
120 }
121 self._doLoginTest('HTTP/1.1 200 OK\r\n', headers)
122 self.failUnless(self.result[0] == (msn.LOGIN_SUCCESS, 'somekey'))
123
124 def testPassportLoginFailure(self):
125 headers = {
126 'Content-Type' : 'text/html',
127 'WWW-Authenticate' : 'Passport1.4 da-status=failed,' +
128 'srealm=Passport.NET,ts=-3,prompt,cburl=http://host.com,' +
129 'cbtxt=the%20error%20message'
130 }
131 self._doLoginTest('HTTP/1.1 401 Unauthorized\r\n', headers)
132 self.failUnless(self.result[0] == (msn.LOGIN_FAILURE, 'the error message'))
133
134 def testPassportLoginRedirect(self):
135 headers = {
136 'Content-Type' : 'text/html',
137 'Authentication-Info' : 'Passport1.4 da-status=redir',
138 'Location' : 'https://newlogin.host.com/'
139 }
140 self._doLoginTest('HTTP/1.1 302 Found\r\n', headers)
141 self.failUnless(self.result[0] == (msn.LOGIN_REDIRECT, 'https://newlogin.host.com/', 'a'))
142
143
144
145 ######################
146 # Notification tests #
147 ######################
148
149 class DummyNotificationClient(msn.NotificationClient):
150 def loggedIn(self, userHandle, verified):
151 if userHandle == 'foo@bar.com' and verified:
152 self.state = 'LOGIN'
153
154 def gotProfile(self, message):
155 self.state = 'PROFILE'
156
157 def gotContactStatus(self, userHandle, code, screenName):
158 if code == msn.STATUS_AWAY and userHandle == "foo@bar.com" and screenName == "Test Screen Name":
159 c = self.factory.contacts.getContact(userHandle)
160 if c.caps & msn.MSNContact.MSNC1 and c.msnobj:
161 self.state = 'INITSTATUS'
162
163 def contactStatusChanged(self, userHandle, code, screenName):
164 if code == msn.STATUS_LUNCH and userHandle == "foo@bar.com" and screenName == "Test Name":
165 self.state = 'NEWSTATUS'
166
167 def contactAvatarChanged(self, userHandle, hash):
168 if userHandle == "foo@bar.com" and hash == "b6b0bc4a5171dac590c593080405921275dcf284":
169 self.state = 'NEWAVATAR'
170 elif self.state == 'NEWAVATAR' and hash == "":
171 self.state = 'AVATARGONE'
172
173 def contactPersonalChanged(self, userHandle, personal):
174 if userHandle == 'foo@bar.com' and personal == 'My Personal Message':
175 self.state = 'GOTPERSONAL'
176 elif userHandle == 'foo@bar.com' and personal == '':
177 self.state = 'PERSONALGONE'
178
179 def contactOffline(self, userHandle):
180 if userHandle == "foo@bar.com": self.state = 'OFFLINE'
181
182 def statusChanged(self, code):
183 if code == msn.STATUS_HIDDEN: self.state = 'MYSTATUS'
184
185 def listSynchronized(self, *args):
186 self.state = 'GOTLIST'
187
188 def gotPhoneNumber(self, userHandle, phoneType, number):
189 self.state = 'GOTPHONE'
190
191 def userRemovedMe(self, userHandle):
192 c = self.factory.contacts.getContact(userHandle)
193 if not c: self.state = 'USERREMOVEDME'
194
195 def userAddedMe(self, userGuid, userHandle, screenName):
196 c = self.factory.contacts.getContact(userHandle)
197 if c and (c.lists | msn.PENDING_LIST) and (screenName == 'Screen Name'):
198 self.state = 'USERADDEDME'
199
200 def gotSwitchboardInvitation(self, sessionID, host, port, key, userHandle, screenName):
201 if sessionID == 1234 and \
202 host == '192.168.1.1' and \
203 port == 1863 and \
204 key == '123.456' and \
205 userHandle == 'foo@foo.com' and \
206 screenName == 'Screen Name':
207 self.state = 'SBINVITED'
208
209 def gotMSNAlert(self, body, action, subscr):
210 self.state = 'NOTIFICATION'
211
212 def gotInitialEmailNotification(self, inboxunread, foldersunread):
213 if inboxunread == 1 and foldersunread == 0:
214 self.state = 'INITEMAIL1'
215 else:
216 self.state = 'INITEMAIL2'
217
218 def gotRealtimeEmailNotification(self, mailfrom, fromaddr, subject):
219 if mailfrom == 'Some Person' and fromaddr == 'example@passport.com' and subject == 'newsubject':
220 self.state = 'REALTIMEEMAIL'
221
222 class NotificationTests(unittest.TestCase):
223 """ testing the various events in NotificationClient """
224
225 def setUp(self):
226 self.client = DummyNotificationClient()
227 self.client.factory = msn.NotificationFactory()
228 self.client.state = 'START'
229
230 def tearDown(self):
231 self.client = None
232
233 def testLogin(self):
234 self.client.lineReceived('USR 1 OK foo@bar.com 1')
235 self.failUnless((self.client.state == 'LOGIN'), 'Failed to detect successful login')
236
237 def testProfile(self):
238 m = 'MSG Hotmail Hotmail 353\r\nMIME-Version: 1.0\r\nContent-Type: text/x-msmsgsprofile; charset=UTF-8\r\n'
239 m += 'LoginTime: 1016941010\r\nEmailEnabled: 1\r\nMemberIdHigh: 40000\r\nMemberIdLow: -600000000\r\nlang_preference: 1033\r\n'
240 m += 'preferredEmail: foo@bar.com\r\ncountry: AU\r\nPostalCode: 90210\r\nGender: M\r\nKid: 0\r\nAge:\r\nsid: 400\r\n'
241 m += 'kv: 2\r\nMSPAuth: 2CACCBCCADMoV8ORoz64BVwmjtksIg!kmR!Rj5tBBqEaW9hc4YnPHSOQ$$\r\n\r\n'
242 self.client.dataReceived(m)
243 self.failUnless((self.client.state == 'PROFILE'), 'Failed to detect initial profile')
244
245 def testInitialEmailNotification(self):
246 m = 'MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinitialemailnotification; charset=UTF-8\r\n'
247 m += '\r\nInbox-Unread: 1\r\nFolders-Unread: 0\r\nInbox-URL: /cgi-bin/HoTMaiL\r\n'
248 m += 'Folders-URL: /cgi-bin/folders\r\nPost-URL: http://www.hotmail.com\r\n\r\n'
249 m = 'MSG Hotmail Hotmail %s\r\n' % (str(len(m))) + m
250 self.client.dataReceived(m)
251 self.failUnless((self.client.state == 'INITEMAIL1'), 'Failed to detect initial email notification')
252
253 def testNoInitialEmailNotification(self):
254 m = 'MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinitialemailnotification; charset=UTF-8\r\n'
255 m += '\r\nInbox-Unread: 0\r\nFolders-Unread: 0\r\nInbox-URL: /cgi-bin/HoTMaiL\r\n'
256 m += 'Folders-URL: /cgi-bin/folders\r\nPost-URL: http://www.hotmail.com\r\n\r\n'
257 m = 'MSG Hotmail Hotmail %s\r\n' % (str(len(m))) + m
258 self.client.dataReceived(m)
259 self.failUnless((self.client.state != 'INITEMAIL2'), 'Detected initial email notification when I should not have')
260
261 def testRealtimeEmailNotification(self):
262 m = 'MSG Hotmail Hotmail 356\r\nMIME-Version: 1.0\r\nContent-Type: text/x-msmsgsemailnotification; charset=UTF-8\r\n'
263 m += '\r\nFrom: Some Person\r\nMessage-URL: /cgi-bin/getmsg?msg=MSG1050451140.21&start=2310&len=2059&curmbox=ACTIVE\r\n'
264 m += 'Post-URL: https://loginnet.passport.com/ppsecure/md5auth.srf?lc=1038\r\n'
265 m += 'Subject: =?"us-ascii"?Q?newsubject?=\r\nDest-Folder: ACTIVE\r\nFrom-Addr: example@passport.com\r\nid: 2\r\n'
266 self.client.dataReceived(m)
267 self.failUnless((self.client.state == 'REALTIMEEMAIL'), 'Failed to detect realtime email notification')
268
269 def testMSNAlert(self):
270 m = '<NOTIFICATION ver="2" id="1342902633" siteid="199999999" siteurl="http://alerts.msn.com">\r\n'
271 m += '<TO pid="0x0006BFFD:0x8582C0FB" name="example@passport.com"/>\r\n'
272 m += '<MSG pri="1" id="1342902633">\r\n'
273 m += '<SUBSCR url="http://g.msn.com/3ALMSNTRACKING/199999999ToastChange?http://alerts.msn.com/Alerts/MyAlerts.aspx?strela=1"/>\r\n'
274 m += '<ACTION url="http://g.msn.com/3ALMSNTRACKING/199999999ToastAction?http://alerts.msn.com/Alerts/MyAlerts.aspx?strela=1"/>\r\n'
275 m += '<BODY lang="3076" icon="">\r\n'
276 m += '<TEXT>utf8-encoded text</TEXT></BODY></MSG>\r\n'
277 m += '</NOTIFICATION>\r\n'
278 cmd = 'NOT %s\r\n' % str(len(m))
279 m = cmd + m
280 # Whee, lots of fun to test that lineReceived & dataReceived work well with input coming
281 # in in (fairly) arbitrary chunks.
282 map(self.client.dataReceived, [x+'\r\n' for x in m.split('\r\n')[:-1]])
283 self.failUnless((self.client.state == 'NOTIFICATION'), 'Failed to detect MSN Alert message')
284
285 def testListSync(self):
286 self.client.makeConnection(StringIOWithoutClosing())
287 msn.NotificationClient.loggedIn(self.client, 'foo@foo.com', 1)
288 lines = [
289 "SYN %s 2005-04-23T18:57:44.8130000-07:00 2005-04-23T18:57:54.2070000-07:00 1 3" % self.client.currentID,
290 "GTC A",
291 "BLP AL",
292 "LSG Friends yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
293 "LSG Other%20Friends yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyz",
294 "LSG More%20Other%20Friends yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyya",
295 "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",
296 ]
297 map(self.client.lineReceived, lines)
298 contacts = self.client.factory.contacts
299 contact = contacts.getContact('userHandle@email.com')
300 #self.failUnless(contacts.version == 100, "Invalid contact list version")
301 self.failUnless(contact.screenName == 'Some Name', "Invalid screen-name for user")
302 self.failUnless(contacts.groups == {'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy': 'Friends', \
303 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyz': 'Other Friends', \
304 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyya': 'More Other Friends'} \
305 , "Did not get proper group list")
306 self.failUnless(contact.groups == ['yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy', \
307 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyz'] and \
308 contact.lists == 13, "Invalid contact list/group info")
309 self.failUnless(self.client.state == 'GOTLIST', "Failed to call list sync handler")
310 self.client.logOut()
311
312 def testStatus(self):
313 # Set up the contact list
314 self.client.makeConnection(StringIOWithoutClosing())
315 msn.NotificationClient.loggedIn(self.client, 'foo@foo.com', 1)
316 lines = [
317 "SYN %s 2005-04-23T18:57:44.8130000-07:00 2005-04-23T18:57:54.2070000-07:00 1 0" % self.client.currentID,
318 "GTC A",
319 "BLP AL",
320 "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",
321 ]
322 map(self.client.lineReceived, lines)
323 # Now test!
324 msnobj = urllib.quote('<msnobj Creator="buddy1@hotmail.com" Size="24539" Type="3" Location="TFR2C.tmp" Friendly="AAA=" SHA1D="trC8SlFx2sWQxZMIBAWSEnXc8oQ=" SHA1C="U32o6bosZzluJq82eAtMpx5dIEI="/>')
325 t = [('ILN 1 AWY foo@bar.com Test%20Screen%20Name 268435456 ' + msnobj, 'INITSTATUS', 'Failed to detect initial status report'),
326 ('NLN LUN foo@bar.com Test%20Name 0', 'NEWSTATUS', 'Failed to detect contact status change'),
327 ('NLN AWY foo@bar.com Test%20Name 0 ' + msnobj, 'NEWAVATAR', 'Failed to detect contact avatar change'),
328 ('NLN AWY foo@bar.com Test%20Name 0', 'AVATARGONE', 'Failed to detect contact avatar disappearing'),
329 ('FLN foo@bar.com', 'OFFLINE', 'Failed to detect contact signing off'),
330 ('CHG 1 HDN 0 ' + msnobj, 'MYSTATUS', 'Failed to detect my status changing')]
331 for i in t:
332 self.client.lineReceived(i[0])
333 self.failUnless((self.client.state == i[1]), i[2])
334
335 # Test UBX
336 self.client.dataReceived('UBX foo@bar.com 72\r\n<Data><PSM>My Personal Message</PSM><CurrentMedia></CurrentMedia></Data>')
337 self.failUnless((self.client.state == 'GOTPERSONAL'), 'Failed to detect new personal message')
338 self.client.dataReceived('UBX foo@bar.com 0\r\n')
339 self.failUnless((self.client.state == 'PERSONALGONE'), 'Failed to detect personal message disappearing')
340 self.client.logOut()
341
342 def testAsyncPhoneChange(self):
343 c = msn.MSNContact(userHandle='userHandle@email.com')
344 self.client.factory.contacts = msn.MSNContactList()
345 self.client.factory.contacts.addContact(c)
346 self.client.makeConnection(StringIOWithoutClosing())
347 self.client.lineReceived("BPR 101 userHandle@email.com PHH 123%20456")
348 c = self.client.factory.contacts.getContact('userHandle@email.com')
349 self.failUnless(self.client.state == 'GOTPHONE', "Did not fire phone change callback")
350 self.failUnless(c.homePhone == '123 456', "Did not update the contact's phone number")
351 self.failUnless(self.client.factory.contacts.version == 101, "Did not update list version")
352
353 def testLateBPR(self):
354 """
355 This test makes sure that if a BPR response that was meant
356 to be part of a SYN response (but came after the last LST)
357 is received, the correct contact is updated and all is well
358 """
359 self.client.makeConnection(StringIOWithoutClosing())
360 msn.NotificationClient.loggedIn(self.client, 'foo@foo.com', 1)
361 lines = [
362 "SYN %s 2005-04-23T18:57:44.8130000-07:00 2005-04-23T18:57:54.2070000-07:00 1 0" % self.client.currentID,
363 "GTC A",
364 "BLP AL",
365 "LSG Friends yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
366 "LST N=userHandle@email.com F=Some%20Name C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 13 yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
367 "BPR PHH 123%20456"
368 ]
369 map(self.client.lineReceived, lines)
370 contact = self.client.factory.contacts.getContact('userHandle@email.com')
371 self.failUnless(contact.homePhone == '123 456', "Did not update contact's phone number")
372 self.client.logOut()
373
374 def testUserRemovedMe(self):
375 self.client.factory.contacts = msn.MSNContactList()
376 contact = msn.MSNContact(userHandle='foo@foo.com')
377 contact.addToList(msn.REVERSE_LIST)
378 self.client.factory.contacts.addContact(contact)
379 self.client.lineReceived("REM 0 RL foo@foo.com")
380 self.failUnless(self.client.state == 'USERREMOVEDME', "Failed to remove user from reverse list")
381
382 def testUserAddedMe(self):
383 self.client.factory.contacts = msn.MSNContactList()
384 self.client.lineReceived("ADC 0 RL N=foo@foo.com F=Screen%20Name")
385 self.failUnless(self.client.state == 'USERADDEDME', "Failed to add user to reverse lise")
386
387 def testAsyncSwitchboardInvitation(self):
388 self.client.lineReceived("RNG 1234 192.168.1.1:1863 CKI 123.456 foo@foo.com Screen%20Name")
389 self.failUnless((self.client.state == 'SBINVITED'), 'Failed to detect switchboard invitation')
390
391
392 #######################################
393 # Notification with fake server tests #
394 #######################################
395
396 class FakeNotificationServer(msn.MSNEventBase):
397 def handle_CHG(self, params):
398 if len(params) < 4:
399 params.append('')
400 self.sendLine("CHG %s %s %s %s" % (params[0], params[1], params[2], params[3]))
401
402 def handle_BLP(self, params):
403 self.sendLine("BLP %s %s 100" % (params[0], params[1]))
404
405 def handle_ADC(self, params):
406 trid = params[0]
407 list = msn.listCodeToID[params[1].lower()]
408 if list == msn.FORWARD_LIST:
409 userHandle = ""
410 screenName = ""
411 userGuid = ""
412 groups = ""
413 for p in params[2:]:
414 if p[0] == 'N':
415 userHandle = p[2:]
416 elif p[0] == 'F':
417 screenName = p[2:]
418 elif p[0] == 'C':
419 userGuid = p[2:]
420 else:
421 groups = p
422 if userHandle and userGuid:
423 self.transport.loseConnection()
424 return
425 if userHandle:
426 if not screenName:
427 self.transport.loseConnection()
428 else:
429 self.sendLine("ADC %s FL N=%s F=%s C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx %s" % (trid, userHandle, screenName, groups))
430 return
431 if userGuid:
432 raise "NotImplementedError"
433 else:
434 if len(params) != 3:
435 self.transport.loseConnection()
436 if not params[2].startswith("N=") and params[2].count('@') == 1:
437 self.transport.loseConnection()
438 self.sendLine("ADC %s %s %s" % (params[0], params[1], params[2]))
439
440 def handle_REM(self, params):
441 if len(params) != 3:
442 self.transport.loseConnection()
443 return
444 try:
445 trid = int(params[0])
446 listType = msn.listCodeToID[params[1].lower()]
447 except:
448 self.transport.loseConnection()
449 if listType == msn.FORWARD_LIST and params[2].count('@') > 0:
450 self.transport.loseConnection()
451 elif listType != msn.FORWARD_LIST and params[2].count('@') != 1:
452 self.transport.loseConnection()
453 else:
454 self.sendLine("REM %s %s %s" % (params[0], params[1], params[2]))
455
456 def handle_PRP(self, params):
457 if len(params) != 3:
458 self.transport.loseConnection()
459 if params[1] == "MFN":
460 self.sendLine("PRP %s MFN %s" % (params[0], params[2]))
461 else:
462 # Only friendly names are implemented
463 self.transport.loseConnection()
464
465 def handle_UUX(self, params):
466 if len(params) != 2:
467 self.transport.loseConnection()
468 return
469 l = int(params[1])
470 if l > 0:
471 self.currentMessage = msn.MSNMessage(length=l, userHandle=params[0], screenName="UUX", specialMessage=True)
472 self.setRawMode()
473 else:
474 self.sendLine("UUX %s 0" % params[0])
475
476 def checkMessage(self, message):
477 if message.specialMessage:
478 if message.screenName == "UUX":
479 self.sendLine("UUX %s 0" % message.userHandle)
480 return 0
481 return 1
482
483 def handle_XFR(self, params):
484 if len(params) != 2:
485 self.transport.loseConnection()
486 return
487 if params[1] != "SB":
488 self.transport.loseConnection()
489 return
490 self.sendLine("XFR %s SB 129.129.129.129:1234 CKI SomeSecret" % params[0])
491
492
493
494 class FakeNotificationClient(msn.NotificationClient):
495 def doStatusChange(self):
496 def testcb((status,)):
497 if status == msn.STATUS_AWAY:
498 self.test = 'PASS'
499 self.transport.loseConnection()
500 d = self.changeStatus(msn.STATUS_AWAY)
501 d.addCallback(testcb)
502
503 def doPrivacyMode(self):
504 def testcb((priv,)):
505 if priv.upper() == 'AL':
506 self.test = 'PASS'
507 self.transport.loseConnection()
508 d = self.setPrivacyMode(True)
509 d.addCallback(testcb)
510
511 def doAddContactFL(self):
512 def testcb((listType, userGuid, userHandle, screenName)):
513 if listType & msn.FORWARD_LIST and \
514 userGuid == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" and \
515 userHandle == "foo@bar.com" and \
516 screenName == "foo@bar.com" and \
517 self.factory.contacts.getContact(userHandle):
518 self.test = 'PASS'
519 self.transport.loseConnection()
520 d = self.addContact(msn.FORWARD_LIST, "foo@bar.com")
521 d.addCallback(testcb)
522
523 def doAddContactAL(self):
524 def testcb((listType, userGuid, userHandle, screenName)):
525 if listType & msn.ALLOW_LIST and \
526 userHandle == "foo@bar.com" and \
527 not userGuid and not screenName and \
528 self.factory.contacts.getContact(userHandle):
529 self.test = 'PASS'
530 self.transport.loseConnection()
531 d = self.addContact(msn.ALLOW_LIST, "foo@bar.com")
532 d.addCallback(testcb)
533
534 def doRemContactFL(self):
535 def testcb((listType, userHandle, groupID)):
536 if listType & msn.FORWARD_LIST and \
537 userHandle == "foo@bar.com":
538 self.test = 'PASS'
539 self.transport.loseConnection()
540 d = self.remContact(msn.FORWARD_LIST, "foo@bar.com")
541 d.addCallback(testcb)
542
543 def doRemContactAL(self):
544 def testcb((listType, userHandle, groupID)):
545 if listType & msn.ALLOW_LIST and \
546 userHandle == "foo@bar.com":
547 self.test = 'PASS'
548 self.transport.loseConnection()
549 d = self.remContact(msn.ALLOW_LIST, "foo@bar.com")
550 d.addCallback(testcb)
551
552 def doScreenNameChange(self):
553 def testcb(*args):
554 self.test = 'PASS'
555 self.transport.loseConnection()
556 d = self.changeScreenName("Some new name")
557 d.addCallback(testcb)
558
559 def doPersonalChange(self, personal):
560 def testcb((checkPersonal,)):
561 if checkPersonal == personal:
562 self.test = 'PASS'
563 self.transport.loseConnection()
564 d = self.changePersonalMessage(personal)
565 d.addCallback(testcb)
566
567 def doAvatarChange(self, data):
568 def testcb(ignored):
569 self.test = 'PASS'
570 self.transport.loseConnection()
571 d = self.changeAvatar(data, True)
572 d.addCallback(testcb)
573
574 def doRequestSwitchboard(self):
575 def testcb((host, port, key)):
576 if host == "129.129.129.129" and port == 1234 and key == "SomeSecret":
577 self.test = 'PASS'
578 self.transport.loseConnection()
579 d = self.requestSwitchboardServer()
580 d.addCallback(testcb)
581
582 class FakeServerNotificationTests(unittest.TestCase):
583 """ tests the NotificationClient against a fake server. """
584
585 def setUp(self):
586 self.client = FakeNotificationClient()
587 self.client.factory = msn.NotificationFactory()
588 self.client.test = 'FAIL'
589 self.server = FakeNotificationServer()
590 self.loop = LoopbackCon(self.client, self.server)
591
592 def tearDown(self):
593 self.loop.disconnect()
594
595 def testChangeStatus(self):
596 self.client.doStatusChange()
597 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
598 self.failUnless((self.client.test == 'PASS'), 'Failed to change status properly')
599
600 def testSetPrivacyMode(self):
601 self.client.factory.contacts = msn.MSNContactList()
602 self.client.doPrivacyMode()
603 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
604 self.failUnless((self.client.test == 'PASS'), 'Failed to change privacy mode')
605
606 def testSyncList(self):
607 self.client.doSyncList()
608 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
609 self.failUnless((self.client.test == 'PASS'), 'Failed to synchronise list')
610 testSyncList.skip = "Will do after list versions."
611
612 def testAddContactFL(self):
613 self.client.factory.contacts = msn.MSNContactList()
614 self.client.doAddContactFL()
615 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
616 self.failUnless((self.client.test == 'PASS'), 'Failed to add contact to forward list')
617
618 def testAddContactAL(self):
619 self.client.factory.contacts = msn.MSNContactList()
620 self.client.doAddContactAL()
621 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
622 self.failUnless((self.client.test == 'PASS'), 'Failed to add contact to allow list')
623
624 def testRemContactFL(self):
625 self.client.factory.contacts = msn.MSNContactList()
626 self.client.factory.contacts.addContact(msn.MSNContact(userGuid="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", userHandle="foo@bar.com", screenName="Some guy", lists=msn.FORWARD_LIST))
627 self.client.doRemContactFL()
628 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
629 self.failUnless((self.client.test == 'PASS'), 'Failed to remove contact from forward list')
630
631 def testRemContactAL(self):
632 self.client.factory.contacts = msn.MSNContactList()
633 self.client.factory.contacts.addContact(msn.MSNContact(userGuid="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", userHandle="foo@bar.com", screenName="Some guy", lists=msn.ALLOW_LIST))
634 self.client.doRemContactAL()
635 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
636 self.failUnless((self.client.test == 'PASS'), 'Failed to remove contact from allow list')
637
638 def testChangedScreenName(self):
639 self.client.doScreenNameChange()
640 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
641 self.failUnless((self.client.test == 'PASS'), 'Failed to change screen name properly')
642
643 def testChangePersonal1(self):
644 self.client.doPersonalChange("Some personal message")
645 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
646 self.failUnless((self.client.test == 'PASS'), 'Failed to change personal message properly')
647
648 def testChangePersonal2(self):
649 self.client.doPersonalChange("")
650 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
651 self.failUnless((self.client.test == 'PASS'), 'Failed to change personal message properly')
652
653 def testChangeAvatar(self):
654 self.client.doAvatarChange("DATADATADATADATA")
655 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
656 self.failUnless((self.client.test == 'PASS'), 'Failed to change avatar properly')
657
658 def testRequestSwitchboard(self):
659 self.client.doRequestSwitchboard()
660 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
661 self.failUnless((self.client.test == 'PASS'), 'Failed to request switchboard')
662
663
664 #################################
665 # Notification challenges tests #
666 #################################
667
668 class DummyChallengeNotificationServer(msn.MSNEventBase):
669 def doChallenge(self, challenge, response):
670 self.state = 0
671 self.response = response
672 self.sendLine("CHL 0 " + challenge)
673
674 def checkMessage(self, message):
675 if message.message == self.response:
676 self.state = "PASS"
677 self.transport.loseConnection()
678 return 0
679
680 def handle_QRY(self, params):
681 self.state = 1
682 if len(params) == 3 and params[1] == "PROD0090YUAUV{2B" and params[2] == "32":
683 self.state = 2
684 self.currentMessage = msn.MSNMessage(length=32, userHandle="QRY", screenName="QRY", specialMessage=True)
685 self.setRawMode()
686 else:
687 self.transport.loseConnection()
688
689 class DummyChallengeNotificationClient(msn.NotificationClient):
690 def connectionMade(self):
691 msn.MSNEventBase.connectionMade(self)
692
693 def handle_CHL(self, params):
694 msn.NotificationClient.handle_CHL(self, params)
695 self.transport.loseConnection()
696
697
698 class NotificationChallengeTests(unittest.TestCase):
699 """ tests the responses to the CHLs the server sends """
700
701 def setUp(self):
702 self.client = DummyChallengeNotificationClient()
703 self.server = DummyChallengeNotificationServer()
704 self.loop = LoopbackCon(self.client, self.server)
705
706 def tearDown(self):
707 self.loop.disconnect()
708
709 def testChallenges(self):
710 challenges = [('13038318816579321232', 'b01c13020e374d4fa20abfad6981b7a9'),
711 ('23055170411503520698', 'ae906c3f2946d25e7da1b08b0b247659'),
712 ('37819769320541083311', 'db79d37dadd9031bef996893321da480'),
713 ('93662730714769834295', 'd619dfbb1414004d34d0628766636568'),
714 ('31154116582196216093', '95e96c4f8cfdba6f065c8869b5e984e9')]
715 for challenge, response in challenges:
716 self.server.doChallenge(challenge, response)
717 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
718 self.failUnless((self.server.state == 'PASS'), 'Incorrect challenge response.')
719
720
721 ###########################
722 # Notification ping tests #
723 ###########################
724
725 class DummyPingNotificationServer(LineReceiver):
726 def lineReceived(self, line):
727 if line.startswith("PNG") and self.good:
728 self.sendLine("QNG 50")
729
730 class DummyPingNotificationClient(msn.NotificationClient):
731 def connectionMade(self):
732 self.pingCheckerStart()
733
734 def sendLine(self, line):
735 msn.NotificationClient.sendLine(self, line)
736 self.count += 1
737 if self.count > 10:
738 self.transport.loseConnection() # But not for real, just to end the test
739
740 def connectionLost(self, reason):
741 if self.count <= 10:
742 self.state = 'DISCONNECTED'
743
744 class NotificationPingTests(unittest.TestCase):
745 """ tests pinging in the NotificationClient class """
746
747 def setUp(self):
748 msn.PINGSPEED = 0.1
749 self.client = DummyPingNotificationClient()
750 self.server = DummyPingNotificationServer()
751 self.client.factory = msn.NotificationFactory()
752 self.server.factory = msn.NotificationFactory()
753 self.client.state = 'CONNECTED'
754 self.client.count = 0
755 self.loop = LoopbackCon(self.client, self.server)
756
757 def tearDown(self):
758 msn.PINGSPEED = 50.0
759 self.client.logOut()
760 self.loop.disconnect()
761
762 def testPingGood(self):
763 self.server.good = True
764 self.loop.doSteps(100)
765 self.failUnless((self.client.state == 'CONNECTED'), 'Should be connected.')
766
767 def testPingBad(self):
768 self.server.good = False
769 self.loop.doSteps(100)
770 self.failUnless((self.client.state == 'DISCONNECTED'), 'Should be disconnected.')
771
772
773
774
775 ###########################
776 # Switchboard basic tests #
777 ###########################
778
779 class DummySwitchboardServer(msn.MSNEventBase):
780 def handle_USR(self, params):
781 if len(params) != 3:
782 self.transport.loseConnection()
783 if params[1] == 'foo@bar.com' and params[2] == 'somekey':
784 self.sendLine("USR %s OK %s %s" % (params[0], params[1], params[1]))
785
786 def handle_ANS(self, params):
787 if len(params) != 4:
788 self.transport.loseConnection()
789 if params[1] == 'foo@bar.com' and params[2] == 'somekey' and params[3] == 'someSID':
790 self.sendLine("ANS %s OK" % params[0])
791
792 def handle_CAL(self, params):
793 if len(params) != 2:
794 self.transport.loseConnection()
795 if params[1] == 'friend@hotmail.com':
796 self.sendLine("CAL %s RINGING 1111122" % params[0])
797 else:
798 self.transport.loseConnection()
799
800 def checkMessage(self, message):
801 if message.message == 'Hi how are you today?':
802 self.sendLine("ACK " + message.userHandle) # Relies on TRID getting stored in userHandle trick
803 else:
804 self.transport.loseConnection()
805 return 0
806
807 class DummySwitchboardClient(msn.SwitchboardClient):
808 def loggedIn(self):
809 self.state = 'LOGGEDIN'
810 self.transport.loseConnection()
811
812 def gotChattingUsers(self, users):
813 if users == {'fred@hotmail.com': 'fred', 'jack@email.com': 'jack has a nickname!'}:
814 self.state = 'GOTCHATTINGUSERS'
815
816 def userJoined(self, userHandle, screenName):
817 if userHandle == "friend@hotmail.com" and screenName == "friend nickname":
818 self.state = 'USERJOINED'
819
820 def userLeft(self, userHandle):
821 if userHandle == "friend@hotmail.com":
822 self.state = 'USERLEFT'
823
824 def gotContactTyping(self, message):
825 if message.userHandle == 'foo@bar.com':
826 self.state = 'USERTYPING'
827
828 def gotMessage(self, message):
829 if message.userHandle == 'friend@hotmail.com' and \
830 message.screenName == 'Friend Nickname' and \
831 message.message == 'Hello.':
832 self.state = 'GOTMESSAGE'
833
834 def doSendInvite(self):
835 def testcb((sid,)):
836 if sid == 1111122:
837 self.state = 'INVITESUCCESS'
838 self.transport.loseConnection()
839 d = self.inviteUser('friend@hotmail.com')
840 d.addCallback(testcb)
841
842 def doSendMessage(self):
843 def testcb(ignored):
844 self.state = 'MESSAGESUCCESS'
845 self.transport.loseConnection()
846 m = msn.MSNMessage()
847 m.setHeader("Content-Type", "text/plain; charset=UTF-8")
848 m.message = 'Hi how are you today?'
849 m.ack = msn.MSNMessage.MESSAGE_ACK
850 d = self.sendMessage(m)
851 d.addCallback(testcb)
852
853
854 class SwitchboardBasicTests(unittest.TestCase):
855 """ Tests basic functionality of switchboard sessions """
856 def setUp(self):
857 self.client = DummySwitchboardClient()
858 self.client.state = 'START'
859 self.client.userHandle = 'foo@bar.com'
860 self.client.key = 'somekey'
861 self.client.sessionID = 'someSID'
862 self.server = DummySwitchboardServer()
863 self.loop = LoopbackCon(self.client, self.server)
864
865 def tearDown(self):
866 self.loop.disconnect()
867
868 def _testSB(self, reply):
869 self.client.reply = reply
870 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
871 self.failUnless((self.client.state == 'LOGGEDIN'), 'Failed to login with reply='+str(reply))
872
873 def testReply(self):
874 self._testSB(True)
875
876 def testAsync(self):
877 self._testSB(False)
878
879 def testChattingUsers(self):
880 lines = ["IRO 1 1 2 fred@hotmail.com fred",
881 "IRO 1 2 2 jack@email.com jack%20has%20a%20nickname%21"]
882 for line in lines:
883 self.client.lineReceived(line)
884 self.failUnless((self.client.state == 'GOTCHATTINGUSERS'), 'Failed to get chatting users')
885
886 def testUserJoined(self):
887 self.client.lineReceived("JOI friend@hotmail.com friend%20nickname")
888 self.failUnless((self.client.state == 'USERJOINED'), 'Failed to notice user joining')
889
890 def testUserLeft(self):
891 self.client.lineReceived("BYE friend@hotmail.com")
892 self.failUnless((self.client.state == 'USERLEFT'), 'Failed to notice user leaving')
893
894 def testTypingCheck(self):
895 m = 'MSG foo@bar.com Foo 80\r\n'
896 m += 'MIME-Version: 1.0\r\n'
897 m += 'Content-Type: text/x-msmsgscontrol\r\n'
898 m += 'TypingUser: foo@bar\r\n'
899 m += '\r\n\r\n'
900 self.client.dataReceived(m)
901 self.failUnless((self.client.state == 'USERTYPING'), 'Failed to detect typing notification')
902
903 def testGotMessage(self):
904 m = 'MSG friend@hotmail.com Friend%20Nickname 68\r\n'
905 m += 'MIME-Version: 1.0\r\n'
906 m += 'Content-Type: text/plain; charset=UTF-8\r\n'
907 m += '\r\nHello.'
908 self.client.dataReceived(m)
909 self.failUnless((self.client.state == 'GOTMESSAGE'), 'Failed to detect message')
910
911 def testInviteUser(self):
912 self.client.connectionMade = lambda: None
913 self.client.doSendInvite()
914 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
915 self.failUnless((self.client.state == 'INVITESUCCESS'), 'Failed to invite user')
916
917 def testSendMessage(self):
918 self.client.connectionMade = lambda: None
919 self.client.doSendMessage()
920 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
921 self.failUnless((self.client.state == 'MESSAGESUCCESS'), 'Failed to send message')
922
923
924 ################
925 # MSNP2P tests #
926 ################
927
928 class DummySwitchboardP2PServerHelper(msn.MSNEventBase):
929 def __init__(self, server):
930 msn.MSNEventBase.__init__(self)
931 self.server = server
932
933 def handle_USR(self, params):
934 if len(params) != 3:
935 self.transport.loseConnection()
936 self.userHandle = params[1]
937 if params[1] == 'foo1@bar.com' and params[2] == 'somekey1':
938 self.sendLine("USR %s OK %s %s" % (params[0], params[1], params[1]))
939 if params[1] == 'foo2@bar.com' and params[2] == 'somekey2':
940 self.sendLine("USR %s OK %s %s" % (params[0], params[1], params[1]))
941
942 def checkMessage(self, message):
943 return 1
944
945 def gotMessage(self, message):
946 message.userHandle = self.userHandle
947 message.screenName = self.userHandle
948 self.server.gotMessage(message, self)
949
950 def sendMessage(self, message):
951 if message.length == 0: message.length = message._calcMessageLen()
952 self.sendLine("MSG %s %s %s" % (message.userHandle, message.screenName, message.length))
953 self.sendLine('MIME-Version: %s' % message.getHeader('MIME-Version'))
954 self.sendLine('Content-Type: %s' % message.getHeader('Content-Type'))
955 for header in [h for h in message.headers.items() if h[0].lower() not in ('mime-version','content-type')]:
956 self.sendLine("%s: %s" % (header[0], header[1]))
957 self.transport.write("\r\n")
958 self.transport.write(message.message)
959
960
961 class DummySwitchboardP2PServer:
962 def __init__(self):
963 self.clients = []
964
965 def newClient(self):
966 c = DummySwitchboardP2PServerHelper(self)
967 self.clients.append(c)
968 return c
969
970 def gotMessage(self, message, sender):
971 for c in self.clients:
972 if c != sender:
973 c.sendMessage(message)
974
975 class DummySwitchboardP2PClient(msn.SwitchboardClient):
976 def gotMessage(self, message):
977 if message.message == "Test Message" and message.userHandle == "foo1@bar.com":
978 self.status = "GOTMESSAGE"
979
980 def gotFileReceive(self, fileReceive):
981 self.fileReceive = fileReceive
982
983 class SwitchboardP2PTests(unittest.TestCase):
984 def setUp(self):
985 self.server = DummySwitchboardP2PServer()
986 self.client1 = DummySwitchboardP2PClient()
987 self.client1.key = 'somekey1'
988 self.client1.userHandle = 'foo1@bar.com'
989 self.client2 = DummySwitchboardP2PClient()
990 self.client2.key = 'somekey2'
991 self.client2.userHandle = 'foo2@bar.com'
992 self.client2.status = "INIT"
993 self.loop1 = LoopbackCon(self.client1, self.server.newClient())
994 self.loop2 = LoopbackCon(self.client2, self.server.newClient())
995
996 def tearDown(self):
997 self.loop1.disconnect()
998 self.loop2.disconnect()
999
1000 def _loop(self, steps=1):
1001 for i in xrange(steps):
1002 self.loop1.doSteps(1)
1003 self.loop2.doSteps(1)
1004
1005 def testMessage(self):
1006 self.client1.sendMessage(msn.MSNMessage(message='Test Message'))
1007 self._loop()
1008 self.failUnless((self.client2.status == "GOTMESSAGE"), "Fake switchboard server not working.")
1009
1010 def _generateData(self):
1011 data = ''
1012 for i in xrange(3000):
1013 data += struct.pack("<L", random.randint(0, sys.maxint))
1014 return data
1015
1016 def testAvatars(self):
1017 self.gotAvatar = False
1018
1019 # Set up the avatar for client1
1020 imageData = self._generateData()
1021 self.client1.msnobj = msn.MSNObject()
1022 self.client1.msnobj.setData('foo1@bar.com', imageData)
1023 self.client1.msnobj.makeText()
1024
1025 # Make client2 request the avatar
1026 def avatarCallback((data,)):
1027 self.gotAvatar = (data == imageData)
1028 msnContact = msn.MSNContact(userHandle='foo1@bar.com', msnobj=self.client1.msnobj)
1029 d = self.client2.sendAvatarRequest(msnContact)
1030 d.addCallback(avatarCallback)
1031
1032 # Let them do their thing
1033 self._loop(10)
1034
1035 # Check that client2 got the avatar
1036 self.failUnless((self.gotAvatar), "Failed to transfer avatar")
1037
1038 def testFilesHappyPath(self):
1039 fileData = self._generateData()
1040 self.gotFile = False
1041
1042 # Send the file (client2->client1)
1043 msnContact = msn.MSNContact(userHandle='foo1@bar.com', caps=msn.MSNContact.MSNC1)
1044 fileSend, d = self.client2.sendFile(msnContact, "myfile.txt", len(fileData))
1045 def accepted((yes,)):
1046 if yes:
1047 fileSend.write(fileData)
1048 fileSend.close()
1049 else:
1050 raise "TransferDeclined"
1051 def failed():
1052 raise "TransferError"
1053 d.addCallback(accepted)
1054 d.addErrback(failed)
1055
1056 # Let the request get pushed to client1
1057 self._loop(10)
1058
1059 # Receive the file
1060 def finished(data):
1061 self.gotFile = (data == fileData)
1062 fileBuffer = msn.StringBuffer(finished)
1063 fileReceive = self.client1.fileReceive
1064 self.failUnless((fileReceive.filename == "myfile.txt" and fileReceive.filesize == len(fileData)), "Filename or length wrong.")
1065 fileReceive.accept(fileBuffer)
1066
1067 # Lets transfer!!
1068 self._loop(10)
1069
1070 self.failUnless((self.gotFile), "Failed to transfer file")
1071
1072 def testFilesHappyChunkedPath(self):
1073 fileData = self._generateData()
1074 self.gotFile = False
1075
1076 # Send the file (client2->client1)
1077 msnContact = msn.MSNContact(userHandle='foo1@bar.com', caps=msn.MSNContact.MSNC1)
1078 fileSend, d = self.client2.sendFile(msnContact, "myfile.txt", len(fileData))
1079 def accepted((yes,)):
1080 if yes:
1081 fileSend.write(fileData[:len(fileData)/2])
1082 fileSend.write(fileData[len(fileData)/2:])
1083 fileSend.close()
1084 else:
1085 raise "TransferDeclined"
1086 def failed():
1087 raise "TransferError"
1088 d.addCallback(accepted)
1089 d.addErrback(failed)
1090
1091 # Let the request get pushed to client1
1092 self._loop(10)
1093
1094 # Receive the file
1095 def finished(data):
1096 self.gotFile = (data == fileData)
1097 fileBuffer = msn.StringBuffer(finished)
1098 fileReceive = self.client1.fileReceive
1099 self.failUnless((fileReceive.filename == "myfile.txt" and fileReceive.filesize == len(fileData)), "Filename or length wrong.")
1100 fileReceive.accept(fileBuffer)
1101
1102 # Lets transfer!!
1103 self._loop(10)
1104
1105 self.failUnless((self.gotFile), "Failed to transfer file")
1106
1107 def testTwoFilesSequential(self):
1108 self.testFilesHappyPath()
1109 self.testFilesHappyPath()
1110
1111 def testFilesDeclinePath(self):
1112 fileData = self._generateData()
1113 self.gotDecline = False
1114
1115 # Send the file (client2->client1)
1116 msnContact = msn.MSNContact(userHandle='foo1@bar.com', caps=msn.MSNContact.MSNC1)
1117 fileSend, d = self.client2.sendFile(msnContact, "myfile.txt", len(fileData))
1118 def accepted((yes,)):
1119 self.failUnless((not yes), "Failed to understand a decline.")
1120 self.gotDecline = True
1121 def failed():
1122 raise "TransferError"
1123 d.addCallback(accepted)
1124 d.addErrback(failed)
1125
1126 # Let the request get pushed to client1
1127 self._loop(10)
1128
1129 # Decline the file
1130 fileReceive = self.client1.fileReceive
1131 fileReceive.reject()
1132
1133 # Let the decline get pushed to client2
1134 self._loop(10)
1135
1136 self.failUnless((self.gotDecline), "Failed to understand a decline, ignored.")
1137
1138
1139 ################
1140 # MSNFTP tests #
1141 ################
1142
1143 class FileTransferTestCase(unittest.TestCase):
1144 """ test FileSend against FileReceive """
1145 skip = "Not implemented"
1146
1147 def setUp(self):
1148 self.input = StringIOWithoutClosing()
1149 self.input.writelines(['a'] * 7000)
1150 self.input.seek(0)
1151 self.output = StringIOWithoutClosing()
1152
1153 def tearDown(self):
1154 self.input = None
1155 self.output = None
1156
1157 def testFileTransfer(self):
1158 auth = 1234
1159 sender = msnft.MSNFTP_FileSend(self.input)
1160 sender.auth = auth
1161 sender.fileSize = 7000
1162 client = msnft.MSNFTP_FileReceive(auth, "foo@bar.com", self.output)
1163 client.fileSize = 7000
1164 loop = LoopbackCon(client, sender)
1165 loop.doSteps(100)
1166 self.failUnless((client.completed and sender.completed), "send failed to complete")
1167 self.failUnless((self.input.getvalue() == self.output.getvalue()), "saved file does not match original")
1168
1169