Repository: cdent/gabbi

Issue:
magical variables enforce single quotes
One of my tests contained the following line:

```yaml
url: $RESPONSE["$._links.next"]
```

That refused to work - it took me quite a while to figure out that's because gabbi seems to only accept single quotes there, as this worked just fine.

```yaml
url: $RESPONSE['$._links.next']
```

I would argue that users' expectation is that single and double quotes are equivalent.


Discussion:

Patch:
diff --git a/gabbi/case.py b/gabbi/case.py
index 50b8815..2be6949 100644
--- a/gabbi/case.py
+++ b/gabbi/case.py
@@ -153,7 +153,7 @@ class HTTPTestCase(testcase.TestCase):
 
     def _environ_replace(self, message):
         """Replace an indicator in a message with the environment value."""
-        value = re.sub(r"\$ENVIRON\['([^']+)'\]",
+        value = re.sub(self._replacer_regex('ENVIRON'),
                        self._environ_replacer, message)
         if value == "False":
             return False
@@ -167,7 +167,7 @@ class HTTPTestCase(testcase.TestCase):
 
         Let KeyError raise if variable not present.
         """
-        environ_name = match.group(1)
+        environ_name = match.group('arg')
         return os.environ[environ_name]
 
     @staticmethod
@@ -188,17 +188,17 @@ class HTTPTestCase(testcase.TestCase):
         """Replace a header indicator in a message with that headers value from
         the prior request.
         """
-        return re.sub(r"\$HEADERS\['([^']+)'\]",
+        return re.sub(self._replacer_regex('HEADERS'),
                       self._header_replacer, message)
 
     def _header_replacer(self, match):
         """Replace a regex match with the value of a prior header."""
-        header_key = match.group(1)
+        header_key = match.group('arg')
         return self.prior.response[header_key.lower()]
 
     def _json_replacer(self, match):
         """Replace a regex match with the value of a JSON Path."""
-        path = match.group(1)
+        path = match.group('arg')
         return str(self.extract_json_path_value(self.prior.json_data, path))
 
     def _location_replace(self, message):
@@ -253,9 +253,14 @@ class HTTPTestCase(testcase.TestCase):
 
         return full_url
 
+    @staticmethod
+    def _replacer_regex(key):
+        message = r"\$%s\[(?P<quote>['\"])(?P<arg>.+?)(?P=quote)\]" % key
+        return message
+
     def _response_replace(self, message):
         """Replace a JSON Path from the prior request with a value."""
-        return re.sub(r"\$RESPONSE\['([^']+)'\]",
+        return re.sub(self._replacer_regex('RESPONSE'),
                       self._json_replacer, message)
 
     def _run_request(self, url, method, headers, body):
diff --git a/gabbi/gabbits_intercept/backref.yaml b/gabbi/gabbits_intercept/backref.yaml
index 4ae1168..e0cec5b 100644
--- a/gabbi/gabbits_intercept/backref.yaml
+++ b/gabbi/gabbits_intercept/backref.yaml
@@ -16,7 +16,7 @@ tests:
       location: $SCHEME://$NETLOC/posterchild
 
 - name: post some more json
-  url: $RESPONSE['link']
+  url: $RESPONSE["link"]
   request_headers:
       content-type: application/json
   method: POST
@@ -26,7 +26,7 @@ tests:
      d:
        z: $RESPONSE['b']
   response_json_paths:
-     a: $RESPONSE['a']
+     a: $RESPONSE["a"]
      c: /v2
      d:
        z: $RESPONSE['b']
@@ -48,3 +48,19 @@ tests:
      location: $SCHEME://$NETLOC$RESPONSE['c']
      x-gabbi-url: $SCHEME://$NETLOC/v2
      content-type: $HEADERS['content-type']
+
+- name: post even more json quote different
+  url: $RESPONSE["c"]
+  request_headers:
+      content-type: application/json
+  method: POST
+  data: |
+      {"a": "$RESPONSE["a"]",
+       "c": "$RESPONSE["c"]"}
+  response_strings:
+      - '"a": "$RESPONSE["a"]"'
+      - '"c": "/v2"'
+  response_headers:
+     location: $SCHEME://$NETLOC$RESPONSE['c']
+     x-gabbi-url: $SCHEME://$NETLOC/v2
+     content-type: $HEADERS['content-type']
diff --git a/gabbi/handlers.py b/gabbi/handlers.py
index d39a987..f754d94 100644
--- a/gabbi/handlers.py
+++ b/gabbi/handlers.py
@@ -120,8 +120,6 @@ class HeadersResponseHandler(ResponseHandler):
     test_key_value = {}
 
     def action(self, test, header, value):
-        header = header.lower()  # case-insensitive comparison
-
         response = test.response
         header_value = test.replace_template(value)
 
diff --git a/gabbi/reporter.py b/gabbi/reporter.py
index 9776b80..0479588 100644
--- a/gabbi/reporter.py
+++ b/gabbi/reporter.py
@@ -81,7 +81,7 @@ class ConciseTestResult(TextTestResult):
             details = details.strip().splitlines()[-1]  # traceback's last line
             if ':' in details:
                 details = details.split(':', 1)[1]  # discard exception name
-            self.stream.writeln('\t%s' % details.strip())
+            self.stream.writeln('\t%s' % details)
 
 
 class ConciseTestRunner(TextTestRunner):


Test patch:
diff --git a/gabbi/tests/test_handlers.py b/gabbi/tests/test_handlers.py
index b647e80..b18e8ec 100644
--- a/gabbi/tests/test_handlers.py
+++ b/gabbi/tests/test_handlers.py
@@ -94,16 +94,10 @@ class HandlersTest(unittest.TestCase):
 
     def test_response_headers(self):
         handler = handlers.HeadersResponseHandler(self.test_class)
-        self.test.response = {'content-type': 'text/plain'}
-
         self.test.test_data = {'response_headers': {
             'content-type': 'text/plain',
         }}
-        self._assert_handler(handler)
-
-        self.test.test_data = {'response_headers': {
-            'Content-Type': 'text/plain',
-        }}
+        self.test.response = {'content-type': 'text/plain'}
         self._assert_handler(handler)
 
     def test_response_headers_regex(self):
diff --git a/gabbi/tests/test_replacers.py b/gabbi/tests/test_replacers.py
index f32fe01..11628c1 100644
--- a/gabbi/tests/test_replacers.py
+++ b/gabbi/tests/test_replacers.py
@@ -44,3 +44,8 @@ class EnvironReplaceTest(testtools.TestCase):
 
         os.environ['moo'] = "cow"
         self.assertEqual("cow", http_case._environ_replace(message))
+
+        message = '$ENVIRON["moo"]'
+
+        os.environ['moo'] = "True"
+        self.assertEqual(True, http_case._environ_replace(message))