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