Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for case insensitve header names #398

Merged
merged 6 commits into from
May 21, 2018
Merged

Allow for case insensitve header names #398

merged 6 commits into from
May 21, 2018

Conversation

koblas
Copy link
Contributor

@koblas koblas commented May 10, 2018

In testing with a local served files, I noted that Content-Type headers were being compared in a case-sensitive way. Which means that for an express application which is serving headers up in lowercase, sam local was adding a second 'Content-Type: application/json header.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Copy link
Contributor

@jfuss jfuss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for submitting a patch! Generally things look great, just a couple comments on the CaseInsensitiveDict and some tests we should add with this.

Have you run make integ-test and make func-test? We haven't yet enabled those in Travis.

Also, we should be adding at least some functional tests to cover this case. Could you add functional tests to cover thess cases?

@@ -14,6 +14,18 @@
LOG = logging.getLogger(__name__)


class CaseInsensitiveDict(dict):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for this class. It will help me (and others) understanding the application of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a test in tests/functional/local/apigw/test_service.py

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have both unit and functional. We need to make sure we are unit testing everything. The CodeCoverage show branch 22-23 missing and we are not really testing the other paths, it just so happens we execute those paths by creating the object. Ideally, we would mock out this class in the other tests, and then add explicit tests for this class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added unit tests to tests/functional/local/apigw/test_service.py you should now have full code coverage of this class.


def __getitem__(self, key):
try:
return [v for k, v in self.items() if k.lower() == key.lower()][0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't really wrap my brain around what is going on here and seems complex for what it should be doing.

Why not have a __setitem__ where you .lower on insert?

Copy link
Contributor Author

@koblas koblas May 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally did that (a __setitem__ method that lowercases). That ended up generating a response with only lowercase headers, which after some thought I decided was a "bigger" change than a minimal change necessary. Since while headers are case-insensitive for comparisons us humans typically look at the case and think that a returned header of "content-type" looks wrong.

So, after some careful consideration realizing that this is only used for local testing, went with find the header as slightly more complicated (it's used only once in the server). While preserving the case of headers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I hadn't considered that. I do agree it would be a strange experience if the Header names magically changed in the request.

Can you refactor this to not use a list comprehension though? I am ok with simple ones but this is fairly complex and hard to parse/understand. We favor readability where we can.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry adding one more thing here that I forgot to mention in the comment above.

Could you also add a comment about why we are only implementing the ‘getitem’? This will help future people not be like, what is this madness. You should totally just use ‘setitem’ with this as well. :)

@jfuss jfuss added the stage/in-progress A fix is being worked on label May 14, 2018
@@ -450,6 +502,33 @@ def test_post_binary_with_incorrect_content_type(self):
self.assertEquals(response.headers.get('Content-Type'), "application/json")


class TestService_CaseInsensiveDict(TestCase):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unit tests should go in the tests/unit/local/apigw/test_service.py not in the tests/functional. Can you move this to the correct folder/file?

self.assertTrue('Content-Type' in data)
self.assertTrue('CONTENT-TYPE' in data)
self.assertTrue('Browser' in data)
self.assertTrue('Dog-Food' not in data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We try and keep unit tests to only one assert as much as possible. There are always exceptions but generally if you are testing two different cases, even if they are related, you should split them out into different tests.

Unit tests are cheep and fast. While this seems very much copy-paste, it leads to better isolated tests in overall. The main advantage here is when you make code changes that impact multiple asserts. When chaining asserts like this in one test, the test will fail when the first assert fails. This gives no indication of the bigger picture and state of your system. Devs following TDD constantly run unit test and rely heavily on this idea. I should be able to run the tests and see/understand everything that went wrong.

You can very easily parameterize this and the following function to achieve this. Here is an example how we are doing this already: https://github.com/awslabs/aws-sam-cli/blob/develop/tests/unit/local/apigw/test_service.py#L520

@koblas
Copy link
Contributor Author

koblas commented May 16, 2018

@jfuss Moved the CaseInsensitiveDict test to the unit test directory (didn't see it before).

@jfuss
Copy link
Contributor

jfuss commented May 18, 2018

@koblas No worries. :)

Copy link
Contributor

@jfuss jfuss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉🎉🎉🎉🎉

@jfuss jfuss merged commit 0d39e43 into aws:develop May 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stage/in-progress A fix is being worked on
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants