PageRenderTime 55ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/unit/test_core.py

http://github.com/gabrielfalcao/HTTPretty
Python | 668 lines | 549 code | 63 blank | 56 comment | 0 complexity | 35c628d4f570c3edb85d7f0a397932e4 MD5 | raw file
Possible License(s): JSON
  1. import io
  2. import json
  3. import errno
  4. from freezegun import freeze_time
  5. from sure import expect
  6. from httpretty.core import HTTPrettyRequest, FakeSSLSocket, fakesock, httpretty
  7. from httpretty.core import URIMatcher, URIInfo
  8. from tests.compat import Mock, patch, call
  9. class SocketErrorStub(Exception):
  10. def __init__(self, errno):
  11. self.errno = errno
  12. def test_request_stubs_internals():
  13. ("HTTPrettyRequest is a BaseHTTPRequestHandler that replaces "
  14. "real socket file descriptors with in-memory ones")
  15. # Given a valid HTTP request header string
  16. headers = "\r\n".join([
  17. 'POST /somewhere/?name=foo&age=bar HTTP/1.1',
  18. 'accept-encoding: identity',
  19. 'host: github.com',
  20. 'content-type: application/json',
  21. 'connection: close',
  22. 'user-agent: Python-urllib/2.7',
  23. ])
  24. # When I create a HTTPrettyRequest with an empty body
  25. request = HTTPrettyRequest(headers, body='')
  26. # Then it should have parsed the headers
  27. dict(request.headers).should.equal({
  28. 'accept-encoding': 'identity',
  29. 'connection': 'close',
  30. 'content-type': 'application/json',
  31. 'host': 'github.com',
  32. 'user-agent': 'Python-urllib/2.7'
  33. })
  34. # And the `rfile` should be a io.BytesIO
  35. type_as_str = io.BytesIO.__module__ + '.' + io.BytesIO.__name__
  36. request.should.have.property('rfile').being.a(type_as_str)
  37. # And the `wfile` should be a io.BytesIO
  38. request.should.have.property('wfile').being.a(type_as_str)
  39. # And the `method` should be available
  40. request.should.have.property('method').being.equal('POST')
  41. def test_request_parse_querystring():
  42. ("HTTPrettyRequest#parse_querystring should parse unicode data")
  43. # Given a request string containing a unicode encoded querystring
  44. headers = "\r\n".join([
  45. 'POST /create?name=Gabriel+Falcão HTTP/1.1',
  46. 'Content-Type: multipart/form-data',
  47. ])
  48. # When I create a HTTPrettyRequest with an empty body
  49. request = HTTPrettyRequest(headers, body='')
  50. # Then it should have a parsed querystring
  51. request.querystring.should.equal({'name': ['Gabriel Falcão']})
  52. def test_request_parse_body_when_it_is_application_json():
  53. ("HTTPrettyRequest#parse_request_body recognizes the "
  54. "content-type `application/json` and parses it")
  55. # Given a request string containing a unicode encoded querystring
  56. headers = "\r\n".join([
  57. 'POST /create HTTP/1.1',
  58. 'Content-Type: application/json',
  59. ])
  60. # And a valid json body
  61. body = json.dumps({'name': 'Gabriel Falcão'})
  62. # When I create a HTTPrettyRequest with that data
  63. request = HTTPrettyRequest(headers, body)
  64. # Then it should have a parsed body
  65. request.parsed_body.should.equal({'name': 'Gabriel Falcão'})
  66. def test_request_parse_body_when_it_is_text_json():
  67. ("HTTPrettyRequest#parse_request_body recognizes the "
  68. "content-type `text/json` and parses it")
  69. # Given a request string containing a unicode encoded querystring
  70. headers = "\r\n".join([
  71. 'POST /create HTTP/1.1',
  72. 'Content-Type: text/json',
  73. ])
  74. # And a valid json body
  75. body = json.dumps({'name': 'Gabriel Falcão'})
  76. # When I create a HTTPrettyRequest with that data
  77. request = HTTPrettyRequest(headers, body)
  78. # Then it should have a parsed body
  79. request.parsed_body.should.equal({'name': 'Gabriel Falcão'})
  80. def test_request_parse_body_when_it_is_urlencoded():
  81. ("HTTPrettyRequest#parse_request_body recognizes the "
  82. "content-type `application/x-www-form-urlencoded` and parses it")
  83. # Given a request string containing a unicode encoded querystring
  84. headers = "\r\n".join([
  85. 'POST /create HTTP/1.1',
  86. 'Content-Type: application/x-www-form-urlencoded',
  87. ])
  88. # And a valid urlencoded body
  89. body = "name=Gabriel+Falcão&age=25&projects=httpretty&projects=sure&projects=lettuce"
  90. # When I create a HTTPrettyRequest with that data
  91. request = HTTPrettyRequest(headers, body)
  92. # Then it should have a parsed body
  93. request.parsed_body.should.equal({
  94. 'name': ['Gabriel Falcão'],
  95. 'age': ["25"],
  96. 'projects': ["httpretty", "sure", "lettuce"]
  97. })
  98. def test_request_parse_body_when_unrecognized():
  99. ("HTTPrettyRequest#parse_request_body returns the value as "
  100. "is if the Content-Type is not recognized")
  101. # Given a request string containing a unicode encoded querystring
  102. headers = "\r\n".join([
  103. 'POST /create HTTP/1.1',
  104. 'Content-Type: whatever',
  105. ])
  106. # And a valid urlencoded body
  107. body = "foobar:\nlalala"
  108. # When I create a HTTPrettyRequest with that data
  109. request = HTTPrettyRequest(headers, body)
  110. # Then it should have a parsed body
  111. request.parsed_body.should.equal("foobar:\nlalala")
  112. def test_request_string_representation():
  113. ("HTTPrettyRequest should have a forward_and_trace-friendly "
  114. "string representation")
  115. # Given a request string containing a unicode encoded querystring
  116. headers = "\r\n".join([
  117. 'POST /create HTTP/1.1',
  118. 'Content-Type: JPEG-baby',
  119. 'Host: blog.falcao.it'
  120. ])
  121. # And a valid urlencoded body
  122. body = "foobar:\nlalala"
  123. # When I create a HTTPrettyRequest with that data
  124. request = HTTPrettyRequest(headers, body, sock=Mock(is_https=True))
  125. # Then its string representation should show the headers and the body
  126. str(request).should.equal('<HTTPrettyRequest("POST", "https://blog.falcao.it/create", headers={\'Content-Type\': \'JPEG-baby\', \'Host\': \'blog.falcao.it\'}, body=14)>')
  127. def test_fake_ssl_socket_proxies_its_ow_socket():
  128. ("FakeSSLSocket is a simpel wrapper around its own socket, "
  129. "which was designed to be a HTTPretty fake socket")
  130. # Given a sentinel mock object
  131. socket = Mock()
  132. # And a FakeSSLSocket wrapping it
  133. ssl = FakeSSLSocket(socket)
  134. # When I make a method call
  135. ssl.send("FOO")
  136. # Then it should bypass any method calls to its own socket
  137. socket.send.assert_called_once_with("FOO")
  138. @freeze_time("2013-10-04 04:20:00")
  139. def test_fakesock_socket_getpeercert():
  140. ("fakesock.socket#getpeercert should return a hardcoded fake certificate")
  141. # Given a fake socket instance
  142. socket = fakesock.socket()
  143. # And that it's bound to some host
  144. socket._host = 'somewhere.com'
  145. # When I retrieve the peer certificate
  146. certificate = socket.getpeercert()
  147. # Then it should return a hardcoded value
  148. certificate.should.equal({
  149. u'notAfter': 'Sep 29 04:20:00 GMT',
  150. u'subject': (
  151. ((u'organizationName', u'*.somewhere.com'),),
  152. ((u'organizationalUnitName', u'Domain Control Validated'),),
  153. ((u'commonName', u'*.somewhere.com'),)),
  154. u'subjectAltName': (
  155. (u'DNS', u'*.somewhere.com'),
  156. (u'DNS', u'somewhere.com'),
  157. (u'DNS', u'*')
  158. )
  159. })
  160. def test_fakesock_socket_ssl():
  161. ("fakesock.socket#ssl should take a socket instance and return itself")
  162. # Given a fake socket instance
  163. socket = fakesock.socket()
  164. # And a stubbed socket sentinel
  165. sentinel = Mock()
  166. # When I call `ssl` on that mock
  167. result = socket.ssl(sentinel)
  168. # Then it should have returned its first argument
  169. result.should.equal(sentinel)
  170. @patch('httpretty.core.old_socket')
  171. @patch('httpretty.core.POTENTIAL_HTTP_PORTS')
  172. def test_fakesock_socket_connect_fallback(POTENTIAL_HTTP_PORTS, old_socket):
  173. ("fakesock.socket#connect should open a real connection if the "
  174. "given port is not a potential http port")
  175. # Background: the potential http ports are 80 and 443
  176. POTENTIAL_HTTP_PORTS.__contains__.side_effect = lambda other: int(other) in (80, 443)
  177. # Given a fake socket instance
  178. socket = fakesock.socket()
  179. # When it is connected to a remote server in a port that isn't 80 nor 443
  180. socket.connect(('somewhere.com', 42))
  181. # Then it should have open a real connection in the background
  182. old_socket.return_value.connect.assert_called_once_with(('somewhere.com', 42))
  183. @patch('httpretty.core.old_socket')
  184. def test_fakesock_socket_close(old_socket):
  185. ("fakesock.socket#close should close the actual socket in case "
  186. "it's not http and __truesock_is_connected__ is True")
  187. # Given a fake socket instance that is synthetically open
  188. socket = fakesock.socket()
  189. socket.__truesock_is_connected__ = True
  190. # When I close it
  191. socket.close()
  192. # Then its real socket should have been closed
  193. old_socket.return_value.close.assert_called_once_with()
  194. socket.__truesock_is_connected__.should.be.false
  195. @patch('httpretty.core.old_socket')
  196. def test_fakesock_socket_makefile(old_socket):
  197. ("fakesock.socket#makefile should set the mode, "
  198. "bufsize and return its mocked file descriptor")
  199. # Given a fake socket that has a mocked Entry associated with it
  200. socket = fakesock.socket()
  201. socket._entry = Mock()
  202. # When I call makefile()
  203. fd = socket.makefile(mode='rw', bufsize=512)
  204. # Then it should have returned the socket's own filedescriptor
  205. expect(fd).to.equal(socket.fd)
  206. # And the mode should have been set in the socket instance
  207. socket._mode.should.equal('rw')
  208. # And the bufsize should have been set in the socket instance
  209. socket._bufsize.should.equal(512)
  210. # And the entry should have been filled with that filedescriptor
  211. socket._entry.fill_filekind.assert_called_once_with(fd)
  212. @patch('httpretty.core.old_socket')
  213. def test_fakesock_socket_real_sendall(old_socket):
  214. ("fakesock.socket#real_sendall calls truesock#connect and bails "
  215. "out when not http")
  216. # Background: the real socket will stop returning bytes after the
  217. # first call
  218. real_socket = old_socket.return_value
  219. real_socket.recv.side_effect = [b'response from server', b""]
  220. # Given a fake socket
  221. socket = fakesock.socket()
  222. socket._address = ('1.2.3.4', 42)
  223. # When I call real_sendall with data, some args and kwargs
  224. socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar')
  225. # Then it should have called sendall in the real socket
  226. real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar')
  227. # # And setblocking was never called
  228. # real_socket.setblocking.called.should.be.false
  229. # And recv was never called
  230. real_socket.recv.called.should.be.false
  231. # And the buffer is empty
  232. socket.fd.read().should.equal(b'')
  233. # And connect was never called
  234. real_socket.connect.called.should.be.false
  235. @patch('httpretty.core.old_socket')
  236. def test_fakesock_socket_real_sendall_when_http(old_socket):
  237. ("fakesock.socket#real_sendall sends data and buffers "
  238. "the response in the file descriptor")
  239. # Background: the real socket will stop returning bytes after the
  240. # first call
  241. real_socket = old_socket.return_value
  242. real_socket.recv.side_effect = [b'response from server', b""]
  243. # Given a fake socket
  244. socket = fakesock.socket()
  245. socket._address = ('1.2.3.4', 42)
  246. socket.is_http = True
  247. # When I call real_sendall with data, some args and kwargs
  248. socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar')
  249. # Then it should have called sendall in the real socket
  250. real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar')
  251. # And the socket was set to blocking
  252. real_socket.setblocking.assert_called_once_with(1)
  253. # And recv was called with the bufsize
  254. real_socket.recv.assert_has_calls([
  255. call(socket._bufsize)
  256. ])
  257. # And the buffer should contain the data from the server
  258. socket.fd.read().should.equal(b"response from server")
  259. # And connect was called
  260. real_socket.connect.called.should.be.true
  261. @patch('httpretty.core.old_socket')
  262. @patch('httpretty.core.socket')
  263. def test_fakesock_socket_real_sendall_continue_eagain_when_http(socket, old_socket):
  264. ("fakesock.socket#real_sendall should continue if the socket error was EAGAIN")
  265. socket.error = SocketErrorStub
  266. # Background: the real socket will stop returning bytes after the
  267. # first call
  268. real_socket = old_socket.return_value
  269. real_socket.recv.side_effect = [SocketErrorStub(errno.EAGAIN), b'after error', b""]
  270. # Given a fake socket
  271. socket = fakesock.socket()
  272. socket._address = ('1.2.3.4', 42)
  273. socket.is_http = True
  274. # When I call real_sendall with data, some args and kwargs
  275. socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar')
  276. # Then it should have called sendall in the real socket
  277. real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar')
  278. # And the socket was set to blocking
  279. real_socket.setblocking.assert_called_once_with(1)
  280. # And recv was called with the bufsize
  281. real_socket.recv.assert_has_calls([
  282. call(socket._bufsize)
  283. ])
  284. # And the buffer should contain the data from the server
  285. socket.fd.read().should.equal(b"after error")
  286. # And connect was called
  287. real_socket.connect.called.should.be.true
  288. @patch('httpretty.core.old_socket')
  289. @patch('httpretty.core.socket')
  290. def test_fakesock_socket_real_sendall_socket_error_when_http(socket, old_socket):
  291. ("fakesock.socket#real_sendall should continue if the socket error was EAGAIN")
  292. socket.error = SocketErrorStub
  293. # Background: the real socket will stop returning bytes after the
  294. # first call
  295. real_socket = old_socket.return_value
  296. real_socket.recv.side_effect = [SocketErrorStub(42), b'after error', ""]
  297. # Given a fake socket
  298. socket = fakesock.socket()
  299. socket._address = ('1.2.3.4', 42)
  300. socket.is_http = True
  301. # When I call real_sendall with data, some args and kwargs
  302. socket.real_sendall(b"SOMEDATA", b'some extra args...', foo=b'bar')
  303. # Then it should have called sendall in the real socket
  304. real_socket.sendall.assert_called_once_with(b"SOMEDATA", b'some extra args...', foo=b'bar')
  305. # And the socket was set to blocking
  306. real_socket.setblocking.assert_called_once_with(1)
  307. # And recv was called with the bufsize
  308. real_socket.recv.assert_called_once_with(socket._bufsize)
  309. # And the buffer should contain the data from the server
  310. socket.fd.read().should.equal(b"")
  311. # And connect was called
  312. real_socket.connect.called.should.be.true
  313. @patch('httpretty.core.old_socket')
  314. @patch('httpretty.core.POTENTIAL_HTTP_PORTS')
  315. def test_fakesock_socket_real_sendall_when_sending_data(POTENTIAL_HTTP_PORTS, old_socket):
  316. ("fakesock.socket#real_sendall should connect before sending data")
  317. # Background: the real socket will stop returning bytes after the
  318. # first call
  319. real_socket = old_socket.return_value
  320. real_socket.recv.side_effect = [b'response from foobar :)', b""]
  321. # And the potential http port is 4000
  322. POTENTIAL_HTTP_PORTS.__contains__.side_effect = lambda other: int(other) == 4000
  323. POTENTIAL_HTTP_PORTS.union.side_effect = lambda other: POTENTIAL_HTTP_PORTS
  324. # Given a fake socket
  325. socket = fakesock.socket()
  326. # When I call connect to a server in a port that is considered HTTP
  327. socket.connect(('foobar.com', 4000))
  328. # And send some data
  329. socket.real_sendall(b"SOMEDATA")
  330. # Then connect should have been called
  331. real_socket.connect.assert_called_once_with(('foobar.com', 4000))
  332. # And the socket was set to blocking
  333. real_socket.setblocking.assert_called_once_with(1)
  334. # And recv was called with the bufsize
  335. real_socket.recv.assert_has_calls([
  336. call(socket._bufsize)
  337. ])
  338. # And the buffer should contain the data from the server
  339. socket.fd.read().should.equal(b"response from foobar :)")
  340. @patch('httpretty.core.old_socket')
  341. @patch('httpretty.core.httpretty')
  342. @patch('httpretty.core.POTENTIAL_HTTP_PORTS')
  343. def test_fakesock_socket_sendall_with_valid_requestline(POTENTIAL_HTTP_PORTS, httpretty, old_socket):
  344. ("fakesock.socket#sendall should create an entry if it's given a valid request line")
  345. matcher = Mock(name='matcher')
  346. info = Mock(name='info')
  347. httpretty.match_uriinfo.return_value = (matcher, info)
  348. httpretty.register_uri(httpretty.GET, 'http://foo.com/foobar')
  349. # Background:
  350. # using a subclass of socket that mocks out real_sendall
  351. class MySocket(fakesock.socket):
  352. def real_sendall(self, data, *args, **kw):
  353. raise AssertionError('should never call this...')
  354. # Given an instance of that socket
  355. socket = MySocket()
  356. # And that is is considered http
  357. socket.connect(('foo.com', 80))
  358. # When I try to send data
  359. socket.sendall(b"GET /foobar HTTP/1.1\r\nContent-Type: application/json\r\n\r\n")
  360. @patch('httpretty.core.old_socket')
  361. @patch('httpretty.core.httpretty')
  362. @patch('httpretty.core.POTENTIAL_HTTP_PORTS')
  363. def test_fakesock_socket_sendall_with_valid_requestline_2(POTENTIAL_HTTP_PORTS, httpretty, old_socket):
  364. ("fakesock.socket#sendall should create an entry if it's given a valid request line")
  365. matcher = Mock(name='matcher')
  366. info = Mock(name='info')
  367. httpretty.match_uriinfo.return_value = (matcher, info)
  368. httpretty.register_uri(httpretty.GET, 'http://foo.com/foobar')
  369. # Background:
  370. # using a subclass of socket that mocks out real_sendall
  371. class MySocket(fakesock.socket):
  372. def real_sendall(self, data, *args, **kw):
  373. raise AssertionError('should never call this...')
  374. # Given an instance of that socket
  375. socket = MySocket()
  376. # And that is is considered http
  377. socket.connect(('foo.com', 80))
  378. # When I try to send data
  379. socket.sendall(b"GET /foobar HTTP/1.1\r\nContent-Type: application/json\r\n\r\n")
  380. @patch('httpretty.core.old_socket')
  381. def test_fakesock_socket_sendall_with_body_data_no_entry(old_socket):
  382. ("fakesock.socket#sendall should call real_sendall when not parsing headers and there is no entry")
  383. # Background:
  384. # Using a subclass of socket that mocks out real_sendall
  385. class MySocket(fakesock.socket):
  386. def real_sendall(self, data):
  387. data.should.equal(b'BLABLABLABLA')
  388. return 'cool'
  389. # Given an instance of that socket
  390. socket = MySocket()
  391. socket._entry = None
  392. # And that is is considered http
  393. socket.connect(('foo.com', 80))
  394. # When I try to send data
  395. result = socket.sendall(b"BLABLABLABLA")
  396. # Then the result should be the return value from real_sendall
  397. result.should.equal('cool')
  398. @patch('httpretty.core.old_socket')
  399. @patch('httpretty.core.POTENTIAL_HTTP_PORTS')
  400. def test_fakesock_socket_sendall_with_body_data_with_entry(POTENTIAL_HTTP_PORTS, old_socket):
  401. ("fakesock.socket#sendall should call real_sendall when there is no entry")
  402. # Background:
  403. # Using a subclass of socket that mocks out real_sendall
  404. data_sent = []
  405. class MySocket(fakesock.socket):
  406. def real_sendall(self, data):
  407. data_sent.append(data)
  408. # Given an instance of that socket
  409. socket = MySocket()
  410. # And that is is considered http
  411. socket.connect(('foo.com', 80))
  412. # When I try to send data
  413. socket.sendall(b"BLABLABLABLA")
  414. # Then it should have called real_sendall
  415. data_sent.should.equal([b'BLABLABLABLA'])
  416. @patch('httpretty.core.httpretty.match_uriinfo')
  417. @patch('httpretty.core.old_socket')
  418. @patch('httpretty.core.POTENTIAL_HTTP_PORTS')
  419. def test_fakesock_socket_sendall_with_body_data_with_chunked_entry(POTENTIAL_HTTP_PORTS, old_socket, match_uriinfo):
  420. ("fakesock.socket#sendall should call real_sendall when not ")
  421. # Background:
  422. # Using a subclass of socket that mocks out real_sendall
  423. class MySocket(fakesock.socket):
  424. def real_sendall(self, data):
  425. raise AssertionError('should have never been called')
  426. matcher = Mock(name='matcher')
  427. info = Mock(name='info')
  428. httpretty.match_uriinfo.return_value = (matcher, info)
  429. # Using a mocked entry
  430. entry = Mock()
  431. entry.method = 'GET'
  432. entry.info.path = '/foo'
  433. entry.request.headers = {
  434. 'transfer-encoding': 'chunked',
  435. }
  436. entry.request.body = b''
  437. # Given an instance of that socket
  438. socket = MySocket()
  439. socket._entry = entry
  440. # And that is is considered http
  441. socket.connect(('foo.com', 80))
  442. # When I try to send data
  443. socket.sendall(b"BLABLABLABLA")
  444. # Then the entry should have that body
  445. httpretty.last_request.body.should.equal(b'BLABLABLABLA')
  446. def test_fakesock_socket_sendall_with_path_starting_with_two_slashes():
  447. ("fakesock.socket#sendall handles paths starting with // well")
  448. httpretty.register_uri(httpretty.GET, 'http://example.com//foo')
  449. class MySocket(fakesock.socket):
  450. def real_sendall(self, data, *args, **kw):
  451. raise AssertionError('should never call this...')
  452. # Given an instance of that socket
  453. socket = MySocket()
  454. # And that is is considered http
  455. socket.connect(('example.com', 80))
  456. # When I try to send data
  457. socket.sendall(b"GET //foo HTTP/1.1\r\nContent-Type: application/json\r\n\r\n")
  458. def test_URIMatcher_respects_querystring():
  459. ("URIMatcher response querystring")
  460. matcher = URIMatcher('http://www.foo.com/?query=true', None)
  461. info = URIInfo.from_uri('http://www.foo.com/', None)
  462. assert matcher.matches(info)
  463. matcher = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True)
  464. info = URIInfo.from_uri('http://www.foo.com/', None)
  465. assert not matcher.matches(info)
  466. matcher = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True)
  467. info = URIInfo.from_uri('http://www.foo.com/?query=true', None)
  468. assert matcher.matches(info)
  469. matcher = URIMatcher('http://www.foo.com/?query=true&unquery=false', None, match_querystring=True)
  470. info = URIInfo.from_uri('http://www.foo.com/?unquery=false&query=true', None)
  471. assert matcher.matches(info)
  472. matcher = URIMatcher('http://www.foo.com/?unquery=false&query=true', None, match_querystring=True)
  473. info = URIInfo.from_uri('http://www.foo.com/?query=true&unquery=false', None)
  474. assert matcher.matches(info)
  475. def test_URIMatcher_equality_respects_querystring():
  476. ("URIMatcher equality check should check querystring")
  477. matcher_a = URIMatcher('http://www.foo.com/?query=true', None)
  478. matcher_b = URIMatcher('http://www.foo.com/?query=false', None)
  479. assert matcher_a == matcher_b
  480. matcher_a = URIMatcher('http://www.foo.com/?query=true', None)
  481. matcher_b = URIMatcher('http://www.foo.com/', None)
  482. assert matcher_a == matcher_b
  483. matcher_a = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True)
  484. matcher_b = URIMatcher('http://www.foo.com/?query=false', None, match_querystring=True)
  485. assert not matcher_a == matcher_b
  486. matcher_a = URIMatcher('http://www.foo.com/?query=true', None, match_querystring=True)
  487. matcher_b = URIMatcher('http://www.foo.com/', None, match_querystring=True)
  488. assert not matcher_a == matcher_b
  489. matcher_a = URIMatcher('http://www.foo.com/?query=true&unquery=false', None, match_querystring=True)
  490. matcher_b = URIMatcher('http://www.foo.com/?unquery=false&query=true', None, match_querystring=True)
  491. assert matcher_a == matcher_b