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