PageRenderTime 57ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/desktop/core/ext-py/Twisted/twisted/words/test/test_msn.py

https://github.com/jcrobak/hue
Python | 468 lines | 408 code | 37 blank | 23 comment | 34 complexity | ee3419f5143bd098988b65ec716d9edd MD5 | raw file
  1. # Copyright (c) 2001-2005 Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Test cases for twisted.words.protocols.msn
  5. """
  6. # System imports
  7. import StringIO, sys
  8. # Twisted imports
  9. # t.w.p.msn requires an HTTP client
  10. try:
  11. # So try to get one - do it directly instead of catching an ImportError
  12. # from t.w.p.msn so that other problems which cause that module to fail
  13. # to import don't cause the tests to be skipped.
  14. from twisted.web import client
  15. except ImportError:
  16. # If there isn't one, we're going to skip all the tests.
  17. msn = None
  18. else:
  19. # Otherwise importing it should work, so do it.
  20. from twisted.words.protocols import msn
  21. from twisted.protocols import loopback
  22. from twisted.internet.defer import Deferred
  23. from twisted.trial import unittest
  24. def printError(f):
  25. print f
  26. class StringIOWithoutClosing(StringIO.StringIO):
  27. disconnecting = 0
  28. def close(self): pass
  29. def loseConnection(self): pass
  30. class PassportTests(unittest.TestCase):
  31. def setUp(self):
  32. self.result = []
  33. self.deferred = Deferred()
  34. self.deferred.addCallback(lambda r: self.result.append(r))
  35. self.deferred.addErrback(printError)
  36. def testNexus(self):
  37. protocol = msn.PassportNexus(self.deferred, 'https://foobar.com/somepage.quux')
  38. headers = {
  39. 'Content-Length' : '0',
  40. 'Content-Type' : 'text/html',
  41. 'PassportURLs' : 'DARealm=Passport.Net,DALogin=login.myserver.com/,DAReg=reg.myserver.com'
  42. }
  43. transport = StringIOWithoutClosing()
  44. protocol.makeConnection(transport)
  45. protocol.dataReceived('HTTP/1.0 200 OK\r\n')
  46. for (h,v) in headers.items(): protocol.dataReceived('%s: %s\r\n' % (h,v))
  47. protocol.dataReceived('\r\n')
  48. self.failUnless(self.result[0] == "https://login.myserver.com/")
  49. def _doLoginTest(self, response, headers):
  50. protocol = msn.PassportLogin(self.deferred,'foo@foo.com','testpass','https://foo.com/', 'a')
  51. protocol.makeConnection(StringIOWithoutClosing())
  52. protocol.dataReceived(response)
  53. for (h,v) in headers.items(): protocol.dataReceived('%s: %s\r\n' % (h,v))
  54. protocol.dataReceived('\r\n')
  55. def testPassportLoginSuccess(self):
  56. headers = {
  57. 'Content-Length' : '0',
  58. 'Content-Type' : 'text/html',
  59. 'Authentication-Info' : "Passport1.4 da-status=success,tname=MSPAuth," +
  60. "tname=MSPProf,tname=MSPSec,from-PP='somekey'," +
  61. "ru=http://messenger.msn.com"
  62. }
  63. self._doLoginTest('HTTP/1.1 200 OK\r\n', headers)
  64. self.failUnless(self.result[0] == (msn.LOGIN_SUCCESS, 'somekey'))
  65. def testPassportLoginFailure(self):
  66. headers = {
  67. 'Content-Type' : 'text/html',
  68. 'WWW-Authenticate' : 'Passport1.4 da-status=failed,' +
  69. 'srealm=Passport.NET,ts=-3,prompt,cburl=http://host.com,' +
  70. 'cbtxt=the%20error%20message'
  71. }
  72. self._doLoginTest('HTTP/1.1 401 Unauthorized\r\n', headers)
  73. self.failUnless(self.result[0] == (msn.LOGIN_FAILURE, 'the error message'))
  74. def testPassportLoginRedirect(self):
  75. headers = {
  76. 'Content-Type' : 'text/html',
  77. 'Authentication-Info' : 'Passport1.4 da-status=redir',
  78. 'Location' : 'https://newlogin.host.com/'
  79. }
  80. self._doLoginTest('HTTP/1.1 302 Found\r\n', headers)
  81. self.failUnless(self.result[0] == (msn.LOGIN_REDIRECT, 'https://newlogin.host.com/', 'a'))
  82. if msn is not None:
  83. class DummySwitchboardClient(msn.SwitchboardClient):
  84. def userTyping(self, message):
  85. self.state = 'TYPING'
  86. def gotSendRequest(self, fileName, fileSize, cookie, message):
  87. if fileName == 'foobar.ext' and fileSize == 31337 and cookie == 1234: self.state = 'INVITATION'
  88. class DummyNotificationClient(msn.NotificationClient):
  89. def loggedIn(self, userHandle, screenName, verified):
  90. if userHandle == 'foo@bar.com' and screenName == 'Test Screen Name' and verified:
  91. self.state = 'LOGIN'
  92. def gotProfile(self, message):
  93. self.state = 'PROFILE'
  94. def gotContactStatus(self, code, userHandle, screenName):
  95. if code == msn.STATUS_AWAY and userHandle == "foo@bar.com" and screenName == "Test Screen Name":
  96. self.state = 'INITSTATUS'
  97. def contactStatusChanged(self, code, userHandle, screenName):
  98. if code == msn.STATUS_LUNCH and userHandle == "foo@bar.com" and screenName == "Test Name":
  99. self.state = 'NEWSTATUS'
  100. def contactOffline(self, userHandle):
  101. if userHandle == "foo@bar.com": self.state = 'OFFLINE'
  102. def statusChanged(self, code):
  103. if code == msn.STATUS_HIDDEN: self.state = 'MYSTATUS'
  104. def listSynchronized(self, *args):
  105. self.state = 'GOTLIST'
  106. def gotPhoneNumber(self, listVersion, userHandle, phoneType, number):
  107. msn.NotificationClient.gotPhoneNumber(self, listVersion, userHandle, phoneType, number)
  108. self.state = 'GOTPHONE'
  109. def userRemovedMe(self, userHandle, listVersion):
  110. msn.NotificationClient.userRemovedMe(self, userHandle, listVersion)
  111. c = self.factory.contacts.getContact(userHandle)
  112. if not c and self.factory.contacts.version == listVersion: self.state = 'USERREMOVEDME'
  113. def userAddedMe(self, userHandle, screenName, listVersion):
  114. msn.NotificationClient.userAddedMe(self, userHandle, screenName, listVersion)
  115. c = self.factory.contacts.getContact(userHandle)
  116. if c and (c.lists | msn.REVERSE_LIST) and (self.factory.contacts.version == listVersion) and \
  117. (screenName == 'Screen Name'):
  118. self.state = 'USERADDEDME'
  119. def gotSwitchboardInvitation(self, sessionID, host, port, key, userHandle, screenName):
  120. if sessionID == 1234 and \
  121. host == '192.168.1.1' and \
  122. port == 1863 and \
  123. key == '123.456' and \
  124. userHandle == 'foo@foo.com' and \
  125. screenName == 'Screen Name':
  126. self.state = 'SBINVITED'
  127. class DispatchTests(unittest.TestCase):
  128. """
  129. Tests for L{DispatchClient}.
  130. """
  131. def _versionTest(self, serverVersionResponse):
  132. """
  133. Test L{DispatchClient} version negotiation.
  134. """
  135. client = msn.DispatchClient()
  136. client.userHandle = "foo"
  137. transport = StringIOWithoutClosing()
  138. client.makeConnection(transport)
  139. self.assertEqual(
  140. transport.getvalue(), "VER 1 MSNP8 CVR0\r\n")
  141. transport.seek(0)
  142. transport.truncate()
  143. client.dataReceived(serverVersionResponse)
  144. self.assertEqual(
  145. transport.getvalue(),
  146. "CVR 2 0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS foo\r\n")
  147. def test_version(self):
  148. """
  149. L{DispatchClient.connectionMade} greets the server with a I{VER}
  150. (version) message and then L{NotificationClient.dataReceived}
  151. handles the server's I{VER} response by sending a I{CVR} (client
  152. version) message.
  153. """
  154. self._versionTest("VER 1 MSNP8 CVR0\r\n")
  155. def test_versionWithoutCVR0(self):
  156. """
  157. If the server responds to a I{VER} command without including the
  158. I{CVR0} protocol, L{DispatchClient} behaves in the same way as if
  159. that protocol were included.
  160. Starting in August 2008, CVR0 disappeared from the I{VER} response.
  161. """
  162. self._versionTest("VER 1 MSNP8\r\n")
  163. class NotificationTests(unittest.TestCase):
  164. """ testing the various events in NotificationClient """
  165. def setUp(self):
  166. self.client = DummyNotificationClient()
  167. self.client.factory = msn.NotificationFactory()
  168. self.client.state = 'START'
  169. def tearDown(self):
  170. self.client = None
  171. def _versionTest(self, serverVersionResponse):
  172. """
  173. Test L{NotificationClient} version negotiation.
  174. """
  175. self.client.factory.userHandle = "foo"
  176. transport = StringIOWithoutClosing()
  177. self.client.makeConnection(transport)
  178. self.assertEqual(
  179. transport.getvalue(), "VER 1 MSNP8 CVR0\r\n")
  180. transport.seek(0)
  181. transport.truncate()
  182. self.client.dataReceived(serverVersionResponse)
  183. self.assertEqual(
  184. transport.getvalue(),
  185. "CVR 2 0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS foo\r\n")
  186. def test_version(self):
  187. """
  188. L{NotificationClient.connectionMade} greets the server with a I{VER}
  189. (version) message and then L{NotificationClient.dataReceived}
  190. handles the server's I{VER} response by sending a I{CVR} (client
  191. version) message.
  192. """
  193. self._versionTest("VER 1 MSNP8 CVR0\r\n")
  194. def test_versionWithoutCVR0(self):
  195. """
  196. If the server responds to a I{VER} command without including the
  197. I{CVR0} protocol, L{NotificationClient} behaves in the same way as
  198. if that protocol were included.
  199. Starting in August 2008, CVR0 disappeared from the I{VER} response.
  200. """
  201. self._versionTest("VER 1 MSNP8\r\n")
  202. def testLogin(self):
  203. self.client.lineReceived('USR 1 OK foo@bar.com Test%20Screen%20Name 1 0')
  204. self.failUnless((self.client.state == 'LOGIN'), msg='Failed to detect successful login')
  205. def testProfile(self):
  206. m = 'MSG Hotmail Hotmail 353\r\nMIME-Version: 1.0\r\nContent-Type: text/x-msmsgsprofile; charset=UTF-8\r\n'
  207. m += 'LoginTime: 1016941010\r\nEmailEnabled: 1\r\nMemberIdHigh: 40000\r\nMemberIdLow: -600000000\r\nlang_preference: 1033\r\n'
  208. m += 'preferredEmail: foo@bar.com\r\ncountry: AU\r\nPostalCode: 90210\r\nGender: M\r\nKid: 0\r\nAge:\r\nsid: 400\r\n'
  209. m += 'kv: 2\r\nMSPAuth: 2CACCBCCADMoV8ORoz64BVwmjtksIg!kmR!Rj5tBBqEaW9hc4YnPHSOQ$$\r\n\r\n'
  210. map(self.client.lineReceived, m.split('\r\n')[:-1])
  211. self.failUnless((self.client.state == 'PROFILE'), msg='Failed to detect initial profile')
  212. def testStatus(self):
  213. t = [('ILN 1 AWY foo@bar.com Test%20Screen%20Name 0', 'INITSTATUS', 'Failed to detect initial status report'),
  214. ('NLN LUN foo@bar.com Test%20Name 0', 'NEWSTATUS', 'Failed to detect contact status change'),
  215. ('FLN foo@bar.com', 'OFFLINE', 'Failed to detect contact signing off'),
  216. ('CHG 1 HDN 0', 'MYSTATUS', 'Failed to detect my status changing')]
  217. for i in t:
  218. self.client.lineReceived(i[0])
  219. self.failUnless((self.client.state == i[1]), msg=i[2])
  220. def testListSync(self):
  221. # currently this test does not take into account the fact
  222. # that BPRs sent as part of the SYN reply may not be interpreted
  223. # as such if they are for the last LST -- maybe I should
  224. # factor this in later.
  225. self.client.makeConnection(StringIOWithoutClosing())
  226. msn.NotificationClient.loggedIn(self.client, 'foo@foo.com', 'foobar', 1)
  227. lines = [
  228. "SYN %s 100 1 1" % self.client.currentID,
  229. "GTC A",
  230. "BLP AL",
  231. "LSG 0 Other%20Contacts 0",
  232. "LST userHandle@email.com Some%20Name 11 0"
  233. ]
  234. map(self.client.lineReceived, lines)
  235. contacts = self.client.factory.contacts
  236. contact = contacts.getContact('userHandle@email.com')
  237. self.failUnless(contacts.version == 100, "Invalid contact list version")
  238. self.failUnless(contact.screenName == 'Some Name', "Invalid screen-name for user")
  239. self.failUnless(contacts.groups == {0 : 'Other Contacts'}, "Did not get proper group list")
  240. self.failUnless(contact.groups == [0] and contact.lists == 11, "Invalid contact list/group info")
  241. self.failUnless(self.client.state == 'GOTLIST', "Failed to call list sync handler")
  242. def testAsyncPhoneChange(self):
  243. c = msn.MSNContact(userHandle='userHandle@email.com')
  244. self.client.factory.contacts = msn.MSNContactList()
  245. self.client.factory.contacts.addContact(c)
  246. self.client.makeConnection(StringIOWithoutClosing())
  247. self.client.lineReceived("BPR 101 userHandle@email.com PHH 123%20456")
  248. c = self.client.factory.contacts.getContact('userHandle@email.com')
  249. self.failUnless(self.client.state == 'GOTPHONE', "Did not fire phone change callback")
  250. self.failUnless(c.homePhone == '123 456', "Did not update the contact's phone number")
  251. self.failUnless(self.client.factory.contacts.version == 101, "Did not update list version")
  252. def testLateBPR(self):
  253. """
  254. This test makes sure that if a BPR response that was meant
  255. to be part of a SYN response (but came after the last LST)
  256. is received, the correct contact is updated and all is well
  257. """
  258. self.client.makeConnection(StringIOWithoutClosing())
  259. msn.NotificationClient.loggedIn(self.client, 'foo@foo.com', 'foo', 1)
  260. lines = [
  261. "SYN %s 100 1 1" % self.client.currentID,
  262. "GTC A",
  263. "BLP AL",
  264. "LSG 0 Other%20Contacts 0",
  265. "LST userHandle@email.com Some%20Name 11 0",
  266. "BPR PHH 123%20456"
  267. ]
  268. map(self.client.lineReceived, lines)
  269. contact = self.client.factory.contacts.getContact('userHandle@email.com')
  270. self.failUnless(contact.homePhone == '123 456', "Did not update contact's phone number")
  271. def testUserRemovedMe(self):
  272. self.client.factory.contacts = msn.MSNContactList()
  273. contact = msn.MSNContact(userHandle='foo@foo.com')
  274. contact.addToList(msn.REVERSE_LIST)
  275. self.client.factory.contacts.addContact(contact)
  276. self.client.lineReceived("REM 0 RL 100 foo@foo.com")
  277. self.failUnless(self.client.state == 'USERREMOVEDME', "Failed to remove user from reverse list")
  278. def testUserAddedMe(self):
  279. self.client.factory.contacts = msn.MSNContactList()
  280. self.client.lineReceived("ADD 0 RL 100 foo@foo.com Screen%20Name")
  281. self.failUnless(self.client.state == 'USERADDEDME', "Failed to add user to reverse lise")
  282. def testAsyncSwitchboardInvitation(self):
  283. self.client.lineReceived("RNG 1234 192.168.1.1:1863 CKI 123.456 foo@foo.com Screen%20Name")
  284. self.failUnless(self.client.state == "SBINVITED")
  285. def testCommandFailed(self):
  286. """
  287. Ensures that error responses from the server fires an errback with
  288. MSNCommandFailed.
  289. """
  290. id, d = self.client._createIDMapping()
  291. self.client.lineReceived("201 %s" % id)
  292. d = self.assertFailure(d, msn.MSNCommandFailed)
  293. def assertErrorCode(exception):
  294. self.assertEqual(201, exception.errorCode)
  295. return d.addCallback(assertErrorCode)
  296. class MessageHandlingTests(unittest.TestCase):
  297. """ testing various message handling methods from SwichboardClient """
  298. def setUp(self):
  299. self.client = DummySwitchboardClient()
  300. self.client.state = 'START'
  301. def tearDown(self):
  302. self.client = None
  303. def testClientCapabilitiesCheck(self):
  304. m = msn.MSNMessage()
  305. m.setHeader('Content-Type', 'text/x-clientcaps')
  306. self.assertEquals(self.client.checkMessage(m), 0, 'Failed to detect client capability message')
  307. def testTypingCheck(self):
  308. m = msn.MSNMessage()
  309. m.setHeader('Content-Type', 'text/x-msmsgscontrol')
  310. m.setHeader('TypingUser', 'foo@bar')
  311. self.client.checkMessage(m)
  312. self.failUnless((self.client.state == 'TYPING'), msg='Failed to detect typing notification')
  313. def testFileInvitation(self, lazyClient=False):
  314. m = msn.MSNMessage()
  315. m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
  316. m.message += 'Application-Name: File Transfer\r\n'
  317. if not lazyClient:
  318. m.message += 'Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n'
  319. m.message += 'Invitation-Command: Invite\r\n'
  320. m.message += 'Invitation-Cookie: 1234\r\n'
  321. m.message += 'Application-File: foobar.ext\r\n'
  322. m.message += 'Application-FileSize: 31337\r\n\r\n'
  323. self.client.checkMessage(m)
  324. self.failUnless((self.client.state == 'INVITATION'), msg='Failed to detect file transfer invitation')
  325. def testFileInvitationMissingGUID(self):
  326. return self.testFileInvitation(True)
  327. def testFileResponse(self):
  328. d = Deferred()
  329. d.addCallback(self.fileResponse)
  330. self.client.cookies['iCookies'][1234] = (d, None)
  331. m = msn.MSNMessage()
  332. m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
  333. m.message += 'Invitation-Command: ACCEPT\r\n'
  334. m.message += 'Invitation-Cookie: 1234\r\n\r\n'
  335. self.client.checkMessage(m)
  336. self.failUnless((self.client.state == 'RESPONSE'), msg='Failed to detect file transfer response')
  337. def testFileInfo(self):
  338. d = Deferred()
  339. d.addCallback(self.fileInfo)
  340. self.client.cookies['external'][1234] = (d, None)
  341. m = msn.MSNMessage()
  342. m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
  343. m.message += 'Invitation-Command: ACCEPT\r\n'
  344. m.message += 'Invitation-Cookie: 1234\r\n'
  345. m.message += 'IP-Address: 192.168.0.1\r\n'
  346. m.message += 'Port: 6891\r\n'
  347. m.message += 'AuthCookie: 4321\r\n\r\n'
  348. self.client.checkMessage(m)
  349. self.failUnless((self.client.state == 'INFO'), msg='Failed to detect file transfer info')
  350. def fileResponse(self, (accept, cookie, info)):
  351. if accept and cookie == 1234: self.client.state = 'RESPONSE'
  352. def fileInfo(self, (accept, ip, port, aCookie, info)):
  353. if accept and ip == '192.168.0.1' and port == 6891 and aCookie == 4321: self.client.state = 'INFO'
  354. class FileTransferTestCase(unittest.TestCase):
  355. """ test FileSend against FileReceive """
  356. def setUp(self):
  357. self.input = StringIOWithoutClosing()
  358. self.input.writelines(['a'] * 7000)
  359. self.input.seek(0)
  360. self.output = StringIOWithoutClosing()
  361. def tearDown(self):
  362. self.input = None
  363. self.output = None
  364. def testFileTransfer(self):
  365. auth = 1234
  366. sender = msn.FileSend(self.input)
  367. sender.auth = auth
  368. sender.fileSize = 7000
  369. client = msn.FileReceive(auth, "foo@bar.com", self.output)
  370. client.fileSize = 7000
  371. def check(ignored):
  372. self.failUnless((client.completed and sender.completed),
  373. msg="send failed to complete")
  374. self.failUnless((self.input.getvalue() == self.output.getvalue()),
  375. msg="saved file does not match original")
  376. d = loopback.loopbackAsync(sender, client)
  377. d.addCallback(check)
  378. return d
  379. if msn is None:
  380. for testClass in [PassportTests, NotificationTests,
  381. MessageHandlingTests, FileTransferTestCase]:
  382. testClass.skip = (
  383. "MSN requires an HTTP client but none is available, "
  384. "skipping tests.")