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