Repository: kevin1024/vcrpy

Issue:
Proxy tunnel connect was broken by #389
In #389 there was a change in the stubs __init__.py:
https://github.com/kevin1024/vcrpy/pull/389/files#diff-06abc5fc1e45f59da669d9294b92a168R139

This change has broken tunnel CONNECT processing.
The client code that calls this (from pyvmomi) is:
```
  /home/jking/pyvmomi/pyVmomi/SoapAdapter.py(1087)__call__()
   1086       tunnel = http_client.HTTPConnection(path, **tmpKwargs)
-> 1087       tunnel.request('CONNECT', self.proxyPath)
   1088       resp = tunnel.getresponse()

ipdb> p path
'vcsa:80'
ipdb> p self.proxyPath
'sdkTunnel:8089'
```

It is acceptable to issue a CONNECT request with a string of "host:port":
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT

Now in the cassette file you get something that looks like this:
```
jking@dvm:~/pyvmomi$ cat tests/fixtures/ssl_tunnel.yaml
interactions:
- request:
    body: null
    headers: {}
    method: CONNECT
    uri: sdkTunnel:8089
  response:
    ...
```

When the cassette player tries to match the requests in the cassette to what's happening on replay, it fails:
```
Traceback (most recent call last):
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/cassette.py", line 109, in __call__
    function, args, kwargs
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/cassette.py", line 124, in _execute_function
    return self._handle_function(fn=handle_function)
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/cassette.py", line 148, in _handle_function
    return fn(cassette)
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/cassette.py", line 117, in handle_function
    return function(*args, **kwargs)
  File "/home/jking/pyvmomi/tests/test_connect.py", line 99, in test_ssl_tunnel_http_failure
    self.assertRaises(http_client.HTTPException, should_fail)
  File "/usr/lib/python3.6/unittest/case.py", line 733, in assertRaises
    return context.handle('assertRaises', args, kwargs)
  File "/usr/lib/python3.6/unittest/case.py", line 178, in handle
    callable_obj(*args, **kwargs)
  File "/home/jking/pyvmomi/tests/test_connect.py", line 98, in should_fail
    connect.SoapStubAdapter('sdkTunnel', 8089, httpProxyHost='localhost', httpProxyPort=8899).GetConnection()
  File "/home/jking/pyvmomi/pyVmomi/SoapAdapter.py", line 1405, in GetConnection
    result = self.scheme(self.host, **self.schemeArgs)
  File "/home/jking/pyvmomi/pyVmomi/SoapAdapter.py", line 1088, in __call__
    resp = tunnel.getresponse()
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/stubs/__init__.py", line 220, in getresponse
    if self.cassette.can_play_response_for(self._vcr_request):
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/cassette.py", line 255, in can_play_response_for
    return request and request in self and \
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/cassette.py", line 326, in __contains__
    for index, response in self._responses(request):
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/cassette.py", line 250, in _responses
    if requests_match(request, stored_request, self._match_on):
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/matchers.py", line 99, in requests_match
    matches = [(m(r1, r2), m) for m in matchers]
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/matchers.py", line 99, in <listcomp>
    matches = [(m(r1, r2), m) for m in matchers]
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/matchers.py", line 27, in port
    return r1.port == r2.port
  File "/home/jking/pyvmomi/.eggs/vcrpy-2.0.1-py3.6.egg/vcr/request.py", line 61, in port
    port = {'https': 443, 'http': 80}[parse_uri.scheme]
KeyError: ''
```

The uri that goes into the cassette is no longer a valid URI.  I would assume that the URI would have to be in this case:
```
    ...
    method: CONNECT
    uri: http://vcsa:80/sdkTunnel:8089
```

Here's what it was in vcrpy 1.13 and earlier:
```
    uri: http://vcsasdkTunnel:8089
```

So I think the fix in #389 was wrong; instead of returning the URL if the path does not start with slash, it should insert the slash when the method is CONNECT.

Thoughts?

Comments:

Patch:
diff --git a/vcr/request.py b/vcr/request.py
index 09c83f4..b4b3f71 100644
--- a/vcr/request.py
+++ b/vcr/request.py
@@ -58,7 +58,10 @@ class Request(object):
         parse_uri = urlparse(self.uri)
         port = parse_uri.port
         if port is None:
-            port = {'https': 443, 'http': 80}[parse_uri.scheme]
+            try:
+                port = {'https': 443, 'http': 80}[parse_uri.scheme]
+            except KeyError:
+                pass
         return port
 
     @property


Test patch:
diff --git a/tests/unit/test_request.py b/tests/unit/test_request.py
index dfd68d7..00793f8 100644
--- a/tests/unit/test_request.py
+++ b/tests/unit/test_request.py
@@ -3,9 +3,13 @@ import pytest
 from vcr.request import Request, HeadersDict
 
 
-def test_str():
-    req = Request('GET', 'http://www.google.com/', '', {})
-    str(req) == '<Request (GET) http://www.google.com/>'
+@pytest.mark.parametrize("method, uri, expected_str", [
+    ('GET', 'http://www.google.com/', '<Request (GET) http://www.google.com/>'),
+    ('OPTIONS', '*', '<Request (OPTIONS) *>'),
+    ('CONNECT', 'host.some.where:1234', '<Request (CONNECT) host.some.where:1234>')
+])
+def test_str(method, uri, expected_str):
+    assert str(Request(method, uri, '', {})) == expected_str
 
 
 def test_headers():
@@ -29,18 +33,21 @@ def test_add_header_deprecated():
     ('https://go.com/', 443),
     ('https://go.com:443/', 443),
     ('https://go.com:3000/', 3000),
+    ('*', None)
 ])
 def test_port(uri, expected_port):
     req = Request('GET', uri, '', {})
     assert req.port == expected_port
 
 
-def test_uri():
-    req = Request('GET', 'http://go.com/', '', {})
-    assert req.uri == 'http://go.com/'
-
-    req = Request('GET', 'http://go.com:80/', '', {})
-    assert req.uri == 'http://go.com:80/'
+@pytest.mark.parametrize("method, uri", [
+    ('GET', 'http://go.com/'),
+    ('GET', 'http://go.com:80/'),
+    ('CONNECT', 'localhost:1234'),
+    ('OPTIONS', '*')
+])
+def test_uri(method, uri):
+    assert Request(method, uri, '', {}).uri == uri
 
 
 def test_HeadersDict():