]> code.delx.au - pymsnt/blob - src/tlib/msn/test_msn.py
Removed skipped tests
[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 testAddContactFL(self):
607 self.client.factory.contacts = msn.MSNContactList()
608 self.client.doAddContactFL()
609 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
610 self.failUnless((self.client.test == 'PASS'), 'Failed to add contact to forward list')
611
612 def testAddContactAL(self):
613 self.client.factory.contacts = msn.MSNContactList()
614 self.client.doAddContactAL()
615 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
616 self.failUnless((self.client.test == 'PASS'), 'Failed to add contact to allow list')
617
618 def testRemContactFL(self):
619 self.client.factory.contacts = msn.MSNContactList()
620 self.client.factory.contacts.addContact(msn.MSNContact(userGuid="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", userHandle="foo@bar.com", screenName="Some guy", lists=msn.FORWARD_LIST))
621 self.client.doRemContactFL()
622 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
623 self.failUnless((self.client.test == 'PASS'), 'Failed to remove contact from forward list')
624
625 def testRemContactAL(self):
626 self.client.factory.contacts = msn.MSNContactList()
627 self.client.factory.contacts.addContact(msn.MSNContact(userGuid="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", userHandle="foo@bar.com", screenName="Some guy", lists=msn.ALLOW_LIST))
628 self.client.doRemContactAL()
629 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
630 self.failUnless((self.client.test == 'PASS'), 'Failed to remove contact from allow list')
631
632 def testChangedScreenName(self):
633 self.client.doScreenNameChange()
634 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
635 self.failUnless((self.client.test == 'PASS'), 'Failed to change screen name properly')
636
637 def testChangePersonal1(self):
638 self.client.doPersonalChange("Some personal message")
639 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
640 self.failUnless((self.client.test == 'PASS'), 'Failed to change personal message properly')
641
642 def testChangePersonal2(self):
643 self.client.doPersonalChange("")
644 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
645 self.failUnless((self.client.test == 'PASS'), 'Failed to change personal message properly')
646
647 def testChangeAvatar(self):
648 self.client.doAvatarChange("DATADATADATADATA")
649 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
650 self.failUnless((self.client.test == 'PASS'), 'Failed to change avatar properly')
651
652 def testRequestSwitchboard(self):
653 self.client.doRequestSwitchboard()
654 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
655 self.failUnless((self.client.test == 'PASS'), 'Failed to request switchboard')
656
657
658 #################################
659 # Notification challenges tests #
660 #################################
661
662 class DummyChallengeNotificationServer(msn.MSNEventBase):
663 def doChallenge(self, challenge, response):
664 self.state = 0
665 self.response = response
666 self.sendLine("CHL 0 " + challenge)
667
668 def checkMessage(self, message):
669 if message.message == self.response:
670 self.state = "PASS"
671 self.transport.loseConnection()
672 return 0
673
674 def handle_QRY(self, params):
675 self.state = 1
676 if len(params) == 3 and params[1] == "PROD0090YUAUV{2B" and params[2] == "32":
677 self.state = 2
678 self.currentMessage = msn.MSNMessage(length=32, userHandle="QRY", screenName="QRY", specialMessage=True)
679 self.setRawMode()
680 else:
681 self.transport.loseConnection()
682
683 class DummyChallengeNotificationClient(msn.NotificationClient):
684 def connectionMade(self):
685 msn.MSNEventBase.connectionMade(self)
686
687 def handle_CHL(self, params):
688 msn.NotificationClient.handle_CHL(self, params)
689 self.transport.loseConnection()
690
691
692 class NotificationChallengeTests(unittest.TestCase):
693 """ tests the responses to the CHLs the server sends """
694
695 def setUp(self):
696 self.client = DummyChallengeNotificationClient()
697 self.server = DummyChallengeNotificationServer()
698 self.loop = LoopbackCon(self.client, self.server)
699
700 def tearDown(self):
701 self.loop.disconnect()
702
703 def testChallenges(self):
704 challenges = [('13038318816579321232', 'b01c13020e374d4fa20abfad6981b7a9'),
705 ('23055170411503520698', 'ae906c3f2946d25e7da1b08b0b247659'),
706 ('37819769320541083311', 'db79d37dadd9031bef996893321da480'),
707 ('93662730714769834295', 'd619dfbb1414004d34d0628766636568'),
708 ('31154116582196216093', '95e96c4f8cfdba6f065c8869b5e984e9')]
709 for challenge, response in challenges:
710 self.server.doChallenge(challenge, response)
711 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
712 self.failUnless((self.server.state == 'PASS'), 'Incorrect challenge response.')
713
714
715 ###########################
716 # Notification ping tests #
717 ###########################
718
719 class DummyPingNotificationServer(LineReceiver):
720 def lineReceived(self, line):
721 if line.startswith("PNG") and self.good:
722 self.sendLine("QNG 50")
723
724 class DummyPingNotificationClient(msn.NotificationClient):
725 def connectionMade(self):
726 self.pingCheckerStart()
727
728 def sendLine(self, line):
729 msn.NotificationClient.sendLine(self, line)
730 self.count += 1
731 if self.count > 10:
732 self.transport.loseConnection() # But not for real, just to end the test
733
734 def connectionLost(self, reason):
735 if self.count <= 10:
736 self.state = 'DISCONNECTED'
737
738 class NotificationPingTests(unittest.TestCase):
739 """ tests pinging in the NotificationClient class """
740
741 def setUp(self):
742 msn.PINGSPEED = 0.1
743 self.client = DummyPingNotificationClient()
744 self.server = DummyPingNotificationServer()
745 self.client.factory = msn.NotificationFactory()
746 self.server.factory = msn.NotificationFactory()
747 self.client.state = 'CONNECTED'
748 self.client.count = 0
749 self.loop = LoopbackCon(self.client, self.server)
750
751 def tearDown(self):
752 msn.PINGSPEED = 50.0
753 self.client.logOut()
754 self.loop.disconnect()
755
756 def testPingGood(self):
757 self.server.good = True
758 self.loop.doSteps(100)
759 self.failUnless((self.client.state == 'CONNECTED'), 'Should be connected.')
760
761 def testPingBad(self):
762 self.server.good = False
763 self.loop.doSteps(100)
764 self.failUnless((self.client.state == 'DISCONNECTED'), 'Should be disconnected.')
765
766
767
768
769 ###########################
770 # Switchboard basic tests #
771 ###########################
772
773 class DummySwitchboardServer(msn.MSNEventBase):
774 def handle_USR(self, params):
775 if len(params) != 3:
776 self.transport.loseConnection()
777 if params[1] == 'foo@bar.com' and params[2] == 'somekey':
778 self.sendLine("USR %s OK %s %s" % (params[0], params[1], params[1]))
779
780 def handle_ANS(self, params):
781 if len(params) != 4:
782 self.transport.loseConnection()
783 if params[1] == 'foo@bar.com' and params[2] == 'somekey' and params[3] == 'someSID':
784 self.sendLine("ANS %s OK" % params[0])
785
786 def handle_CAL(self, params):
787 if len(params) != 2:
788 self.transport.loseConnection()
789 if params[1] == 'friend@hotmail.com':
790 self.sendLine("CAL %s RINGING 1111122" % params[0])
791 else:
792 self.transport.loseConnection()
793
794 def checkMessage(self, message):
795 if message.message == 'Hi how are you today?':
796 self.sendLine("ACK " + message.userHandle) # Relies on TRID getting stored in userHandle trick
797 else:
798 self.transport.loseConnection()
799 return 0
800
801 class DummySwitchboardClient(msn.SwitchboardClient):
802 def loggedIn(self):
803 self.state = 'LOGGEDIN'
804 self.transport.loseConnection()
805
806 def gotChattingUsers(self, users):
807 if users == {'fred@hotmail.com': 'fred', 'jack@email.com': 'jack has a nickname!'}:
808 self.state = 'GOTCHATTINGUSERS'
809
810 def userJoined(self, userHandle, screenName):
811 if userHandle == "friend@hotmail.com" and screenName == "friend nickname":
812 self.state = 'USERJOINED'
813
814 def userLeft(self, userHandle):
815 if userHandle == "friend@hotmail.com":
816 self.state = 'USERLEFT'
817
818 def gotContactTyping(self, message):
819 if message.userHandle == 'foo@bar.com':
820 self.state = 'USERTYPING'
821
822 def gotMessage(self, message):
823 if message.userHandle == 'friend@hotmail.com' and \
824 message.screenName == 'Friend Nickname' and \
825 message.message == 'Hello.':
826 self.state = 'GOTMESSAGE'
827
828 def doSendInvite(self):
829 def testcb((sid,)):
830 if sid == 1111122:
831 self.state = 'INVITESUCCESS'
832 self.transport.loseConnection()
833 d = self.inviteUser('friend@hotmail.com')
834 d.addCallback(testcb)
835
836 def doSendMessage(self):
837 def testcb(ignored):
838 self.state = 'MESSAGESUCCESS'
839 self.transport.loseConnection()
840 m = msn.MSNMessage()
841 m.setHeader("Content-Type", "text/plain; charset=UTF-8")
842 m.message = 'Hi how are you today?'
843 m.ack = msn.MSNMessage.MESSAGE_ACK
844 d = self.sendMessage(m)
845 d.addCallback(testcb)
846
847
848 class SwitchboardBasicTests(unittest.TestCase):
849 """ Tests basic functionality of switchboard sessions """
850 def setUp(self):
851 self.client = DummySwitchboardClient()
852 self.client.state = 'START'
853 self.client.userHandle = 'foo@bar.com'
854 self.client.key = 'somekey'
855 self.client.sessionID = 'someSID'
856 self.server = DummySwitchboardServer()
857 self.loop = LoopbackCon(self.client, self.server)
858
859 def tearDown(self):
860 self.loop.disconnect()
861
862 def _testSB(self, reply):
863 self.client.reply = reply
864 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
865 self.failUnless((self.client.state == 'LOGGEDIN'), 'Failed to login with reply='+str(reply))
866
867 def testReply(self):
868 self._testSB(True)
869
870 def testAsync(self):
871 self._testSB(False)
872
873 def testChattingUsers(self):
874 lines = ["IRO 1 1 2 fred@hotmail.com fred",
875 "IRO 1 2 2 jack@email.com jack%20has%20a%20nickname%21"]
876 for line in lines:
877 self.client.lineReceived(line)
878 self.failUnless((self.client.state == 'GOTCHATTINGUSERS'), 'Failed to get chatting users')
879
880 def testUserJoined(self):
881 self.client.lineReceived("JOI friend@hotmail.com friend%20nickname")
882 self.failUnless((self.client.state == 'USERJOINED'), 'Failed to notice user joining')
883
884 def testUserLeft(self):
885 self.client.lineReceived("BYE friend@hotmail.com")
886 self.failUnless((self.client.state == 'USERLEFT'), 'Failed to notice user leaving')
887
888 def testTypingCheck(self):
889 m = 'MSG foo@bar.com Foo 80\r\n'
890 m += 'MIME-Version: 1.0\r\n'
891 m += 'Content-Type: text/x-msmsgscontrol\r\n'
892 m += 'TypingUser: foo@bar\r\n'
893 m += '\r\n\r\n'
894 self.client.dataReceived(m)
895 self.failUnless((self.client.state == 'USERTYPING'), 'Failed to detect typing notification')
896
897 def testGotMessage(self):
898 m = 'MSG friend@hotmail.com Friend%20Nickname 68\r\n'
899 m += 'MIME-Version: 1.0\r\n'
900 m += 'Content-Type: text/plain; charset=UTF-8\r\n'
901 m += '\r\nHello.'
902 self.client.dataReceived(m)
903 self.failUnless((self.client.state == 'GOTMESSAGE'), 'Failed to detect message')
904
905 def testInviteUser(self):
906 self.client.connectionMade = lambda: None
907 self.client.doSendInvite()
908 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
909 self.failUnless((self.client.state == 'INVITESUCCESS'), 'Failed to invite user')
910
911 def testSendMessage(self):
912 self.client.connectionMade = lambda: None
913 self.client.doSendMessage()
914 self.failUnless(self.loop.doSteps(10), 'Failed to disconnect')
915 self.failUnless((self.client.state == 'MESSAGESUCCESS'), 'Failed to send message')
916
917
918 ################
919 # MSNP2P tests #
920 ################
921
922 class DummySwitchboardP2PServerHelper(msn.MSNEventBase):
923 def __init__(self, server):
924 msn.MSNEventBase.__init__(self)
925 self.server = server
926
927 def handle_USR(self, params):
928 if len(params) != 3:
929 self.transport.loseConnection()
930 self.userHandle = params[1]
931 if params[1] == 'foo1@bar.com' and params[2] == 'somekey1':
932 self.sendLine("USR %s OK %s %s" % (params[0], params[1], params[1]))
933 if params[1] == 'foo2@bar.com' and params[2] == 'somekey2':
934 self.sendLine("USR %s OK %s %s" % (params[0], params[1], params[1]))
935
936 def checkMessage(self, message):
937 return 1
938
939 def gotMessage(self, message):
940 message.userHandle = self.userHandle
941 message.screenName = self.userHandle
942 self.server.gotMessage(message, self)
943
944 def sendMessage(self, message):
945 if message.length == 0: message.length = message._calcMessageLen()
946 self.sendLine("MSG %s %s %s" % (message.userHandle, message.screenName, message.length))
947 self.sendLine('MIME-Version: %s' % message.getHeader('MIME-Version'))
948 self.sendLine('Content-Type: %s' % message.getHeader('Content-Type'))
949 for header in [h for h in message.headers.items() if h[0].lower() not in ('mime-version','content-type')]:
950 self.sendLine("%s: %s" % (header[0], header[1]))
951 self.transport.write("\r\n")
952 self.transport.write(message.message)
953
954
955 class DummySwitchboardP2PServer:
956 def __init__(self):
957 self.clients = []
958
959 def newClient(self):
960 c = DummySwitchboardP2PServerHelper(self)
961 self.clients.append(c)
962 return c
963
964 def gotMessage(self, message, sender):
965 for c in self.clients:
966 if c != sender:
967 c.sendMessage(message)
968
969 class DummySwitchboardP2PClient(msn.SwitchboardClient):
970 def gotMessage(self, message):
971 if message.message == "Test Message" and message.userHandle == "foo1@bar.com":
972 self.status = "GOTMESSAGE"
973
974 def gotFileReceive(self, fileReceive):
975 self.fileReceive = fileReceive
976
977 class SwitchboardP2PTests(unittest.TestCase):
978 def setUp(self):
979 self.server = DummySwitchboardP2PServer()
980 self.client1 = DummySwitchboardP2PClient()
981 self.client1.key = 'somekey1'
982 self.client1.userHandle = 'foo1@bar.com'
983 self.client2 = DummySwitchboardP2PClient()
984 self.client2.key = 'somekey2'
985 self.client2.userHandle = 'foo2@bar.com'
986 self.client2.status = "INIT"
987 self.loop1 = LoopbackCon(self.client1, self.server.newClient())
988 self.loop2 = LoopbackCon(self.client2, self.server.newClient())
989
990 def tearDown(self):
991 self.loop1.disconnect()
992 self.loop2.disconnect()
993
994 def _loop(self, steps=1):
995 for i in xrange(steps):
996 self.loop1.doSteps(1)
997 self.loop2.doSteps(1)
998
999 def testMessage(self):
1000 self.client1.sendMessage(msn.MSNMessage(message='Test Message'))
1001 self._loop()
1002 self.failUnless((self.client2.status == "GOTMESSAGE"), "Fake switchboard server not working.")
1003
1004 def _generateData(self):
1005 data = ''
1006 for i in xrange(3000):
1007 data += struct.pack("<L", random.randint(0, sys.maxint))
1008 return data
1009
1010 def testAvatars(self):
1011 self.gotAvatar = False
1012
1013 # Set up the avatar for client1
1014 imageData = self._generateData()
1015 self.client1.msnobj = msn.MSNObject()
1016 self.client1.msnobj.setData('foo1@bar.com', imageData)
1017 self.client1.msnobj.makeText()
1018
1019 # Make client2 request the avatar
1020 def avatarCallback((data,)):
1021 self.gotAvatar = (data == imageData)
1022 msnContact = msn.MSNContact(userHandle='foo1@bar.com', msnobj=self.client1.msnobj)
1023 d = self.client2.sendAvatarRequest(msnContact)
1024 d.addCallback(avatarCallback)
1025
1026 # Let them do their thing
1027 self._loop(10)
1028
1029 # Check that client2 got the avatar
1030 self.failUnless((self.gotAvatar), "Failed to transfer avatar")
1031
1032 def testFilesHappyPath(self):
1033 fileData = self._generateData()
1034 self.gotFile = False
1035
1036 # Send the file (client2->client1)
1037 msnContact = msn.MSNContact(userHandle='foo1@bar.com', caps=msn.MSNContact.MSNC1)
1038 fileSend, d = self.client2.sendFile(msnContact, "myfile.txt", len(fileData))
1039 def accepted((yes,)):
1040 if yes:
1041 fileSend.write(fileData)
1042 fileSend.close()
1043 else:
1044 raise "TransferDeclined"
1045 def failed():
1046 raise "TransferError"
1047 d.addCallback(accepted)
1048 d.addErrback(failed)
1049
1050 # Let the request get pushed to client1
1051 self._loop(10)
1052
1053 # Receive the file
1054 def finished(data):
1055 self.gotFile = (data == fileData)
1056 fileBuffer = msn.StringBuffer(finished)
1057 fileReceive = self.client1.fileReceive
1058 self.failUnless((fileReceive.filename == "myfile.txt" and fileReceive.filesize == len(fileData)), "Filename or length wrong.")
1059 fileReceive.accept(fileBuffer)
1060
1061 # Lets transfer!!
1062 self._loop(10)
1063
1064 self.failUnless((self.gotFile), "Failed to transfer file")
1065
1066 def testFilesHappyChunkedPath(self):
1067 fileData = self._generateData()
1068 self.gotFile = False
1069
1070 # Send the file (client2->client1)
1071 msnContact = msn.MSNContact(userHandle='foo1@bar.com', caps=msn.MSNContact.MSNC1)
1072 fileSend, d = self.client2.sendFile(msnContact, "myfile.txt", len(fileData))
1073 def accepted((yes,)):
1074 if yes:
1075 fileSend.write(fileData[:len(fileData)/2])
1076 fileSend.write(fileData[len(fileData)/2:])
1077 fileSend.close()
1078 else:
1079 raise "TransferDeclined"
1080 def failed():
1081 raise "TransferError"
1082 d.addCallback(accepted)
1083 d.addErrback(failed)
1084
1085 # Let the request get pushed to client1
1086 self._loop(10)
1087
1088 # Receive the file
1089 def finished(data):
1090 self.gotFile = (data == fileData)
1091 fileBuffer = msn.StringBuffer(finished)
1092 fileReceive = self.client1.fileReceive
1093 self.failUnless((fileReceive.filename == "myfile.txt" and fileReceive.filesize == len(fileData)), "Filename or length wrong.")
1094 fileReceive.accept(fileBuffer)
1095
1096 # Lets transfer!!
1097 self._loop(10)
1098
1099 self.failUnless((self.gotFile), "Failed to transfer file")
1100
1101 def testTwoFilesSequential(self):
1102 self.testFilesHappyPath()
1103 self.testFilesHappyPath()
1104
1105 def testFilesDeclinePath(self):
1106 fileData = self._generateData()
1107 self.gotDecline = False
1108
1109 # Send the file (client2->client1)
1110 msnContact = msn.MSNContact(userHandle='foo1@bar.com', caps=msn.MSNContact.MSNC1)
1111 fileSend, d = self.client2.sendFile(msnContact, "myfile.txt", len(fileData))
1112 def accepted((yes,)):
1113 self.failUnless((not yes), "Failed to understand a decline.")
1114 self.gotDecline = True
1115 def failed():
1116 raise "TransferError"
1117 d.addCallback(accepted)
1118 d.addErrback(failed)
1119
1120 # Let the request get pushed to client1
1121 self._loop(10)
1122
1123 # Decline the file
1124 fileReceive = self.client1.fileReceive
1125 fileReceive.reject()
1126
1127 # Let the decline get pushed to client2
1128 self._loop(10)
1129
1130 self.failUnless((self.gotDecline), "Failed to understand a decline, ignored.")
1131
1132
1133 ################
1134 # MSNFTP tests #
1135 ################
1136
1137 #class FileTransferTestCase(unittest.TestCase):
1138 # """ test FileSend against FileReceive """
1139 # skip = "Not implemented"
1140 #
1141 # def setUp(self):
1142 # self.input = StringIOWithoutClosing()
1143 # self.input.writelines(['a'] * 7000)
1144 # self.input.seek(0)
1145 # self.output = StringIOWithoutClosing()
1146 #
1147 # def tearDown(self):
1148 # self.input = None
1149 # self.output = None
1150 #
1151 # def testFileTransfer(self):
1152 # auth = 1234
1153 # sender = msnft.MSNFTP_FileSend(self.input)
1154 # sender.auth = auth
1155 # sender.fileSize = 7000
1156 # client = msnft.MSNFTP_FileReceive(auth, "foo@bar.com", self.output)
1157 # client.fileSize = 7000
1158 # loop = LoopbackCon(client, sender)
1159 # loop.doSteps(100)
1160 # self.failUnless((client.completed and sender.completed), "send failed to complete")
1161 # self.failUnless((self.input.getvalue() == self.output.getvalue()), "saved file does not match original")
1162
1163