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