Repository: falconry/falcon

Issue:
Additional options to the internal cors middleware
The current cors middleware is a bit too limited for use in anything other than the most simple setups.

As discussed on gitter, I think we could add a bit more options, without complicating it too much.
In particular I think that the `App.cors_enable` flag should still add the middleware configured as is, but when manually adding it to the application more options should be exposed.
Options:
- `Access-Control-Allow-Origin`: default to '*', a str or iterable of strings. 
- `Access-Control-Expose-Headers`: default to not set, a str or iterable of strings.
- ` Access-Control-Allow-Credentials`: default to not set, True to always enable, an iterable of strings to match against the `Origin`.

Changes to the processing, ref https://www.w3.org/TR/cors/#resource-processing-model:
- Do nothing if the `Origin` header is not present.
- if  `Access-Control-Allow-Origin` is not '*', check if the `Origin` is in it. If not terminate
- If adding credential, set `Access-Control-Allow-Origin` to the origin
- Add `Access-Control-Expose-Headers` if defined

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=h1) Report
> Merging [#1722](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=desc) into [master](https://codecov.io/gh/falconry/falcon/commit/4df46939c2ae11fe0b2625ef371f7eca959e0c2f&el=desc) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/falconry/falcon/pull/1722/graphs/tree.svg?width=650&height=150&src=pr&token=zOtkfNQ3h6)](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##            master     #1722   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           49        49           
  Lines         3911      3939   +28     
  Branches       610       619    +9     
=========================================
+ Hits          3911      3939   +28     
```


| [Impacted Files](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [falcon/asgi/app.py](https://codecov.io/gh/falconry/falcon/pull/1722/diff?src=pr&el=tree#diff-ZmFsY29uL2FzZ2kvYXBwLnB5) | `100.00% <ø> (ø)` | |
| [falcon/middlewares.py](https://codecov.io/gh/falconry/falcon/pull/1722/diff?src=pr&el=tree#diff-ZmFsY29uL21pZGRsZXdhcmVzLnB5) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=footer). Last update [4df4693...7be9e95](https://codecov.io/gh/falconry/falcon/pull/1722?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

CaselIT: I've updated the pr:
- improved the docs for allow_credentials
- rename allow_origin parameter to allow_origins, making it plural like the other parameters
- allow_credentials now can accept None (behaves like false) and a str (behaves like a list of one element), similar to the other parameters.


CaselIT: Hopefully I've not added other typos. Need to go towards the 🛌
vytas7: As someone also situated the CEST timezone, I have to agree wrt heading towards the :sleeping_bed: Same here.
kgriffs: Nice work!

Patch:
diff --git a/docs/api/cors.rst b/docs/api/cors.rst
index 18e0e73..59b85cb 100644
--- a/docs/api/cors.rst
+++ b/docs/api/cors.rst
@@ -16,12 +16,17 @@ By default, Falcon's built-in CORS support is disabled, so that any cross-origin
 requests will be blocked by the browser. Passing ``cors_enable=True`` will
 cause the framework to include the necessary response headers to allow access
 from any origin to any route in the app. Individual responders may override this
-behavior by setting the Access-Control-Allow-Origin header explicitly.
+behavior by setting the ``Access-Control-Allow-Origin`` header explicitly.
 
 Whether or not you implement a CORS policy, we recommend also putting a robust
 AuthN/Z layer in place to authorize individual clients, as needed, to protect
 sensitive resources.
 
+Directly passing the :class:`falcon.CORSMiddleware` middleware to the application
+allows customization of the CORS policy applied. The middleware allows customizing
+the allowed origins, if credentials should be allowed and if additional headers
+can be exposed.
+
 Usage
 -----
 
@@ -36,6 +41,10 @@ Usage
             # Enable a simple CORS policy for all responses
             app = falcon.App(cors_enable=True)
 
+            # Enable CORS policy for example.com and allows credentials
+            app = falcon.App(middleware=falcon.CORSMiddleware(
+                allow_origins='example.com', allow_credentials='*'))
+
     .. tab:: ASGI
 
         .. code:: python
@@ -44,3 +53,12 @@ Usage
 
             # Enable a simple CORS policy for all responses
             app = falcon.asgi.App(cors_enable=True)
+
+            # Enable CORS policy for example.com and allows credentials
+            app = falcon.asgi.App(middleware=falcon.CORSMiddleware(
+                allow_origins='example.com', allow_credentials='*'))
+
+CORSMiddleware
+--------------
+
+.. autoclass:: falcon.CORSMiddleware
diff --git a/falcon/__init__.py b/falcon/__init__.py
index c979777..0596ab4 100644
--- a/falcon/__init__.py
+++ b/falcon/__init__.py
@@ -36,6 +36,7 @@ from falcon.errors import *  # NOQA
 from falcon.redirects import *  # NOQA
 from falcon.http_error import HTTPError  # NOQA
 from falcon.http_status import HTTPStatus  # NOQA
+from falcon.middlewares import CORSMiddleware  # NOQA
 
 # NOTE(kgriffs): Ensure that "from falcon import uri" will import
 # the same front-door module as "import falcon.uri". This works by
diff --git a/falcon/app.py b/falcon/app.py
index 015f638..72ed68d 100644
--- a/falcon/app.py
+++ b/falcon/app.py
@@ -152,7 +152,9 @@ class App:
 
         cors_enable (bool): Set this flag to ``True`` to enable a simple
             CORS policy for all responses, including support for preflighted
-            requests (default ``False``).
+            requests. An instance of :py:class:`~.CORSMiddleware` can instead be
+            passed to the middleware argument to customize its behaviour.
+            (default ``False``).
             (See also: :ref:`CORS <cors>`)
 
     Attributes:
diff --git a/falcon/asgi/app.py b/falcon/asgi/app.py
index 0e86d86..29d018c 100644
--- a/falcon/asgi/app.py
+++ b/falcon/asgi/app.py
@@ -212,7 +212,9 @@ class App(falcon.app.App):
 
         cors_enable (bool): Set this flag to ``True`` to enable a simple
             CORS policy for all responses, including support for preflighted
-            requests (default ``False``).
+            requests. An instance of :py:class:`..CORSMiddleware` can instead be
+            passed to the middleware argument to customize its behaviour.
+            (default ``False``).
             (See also: :ref:`CORS <cors>`)
 
     Attributes:
diff --git a/falcon/middlewares.py b/falcon/middlewares.py
index 37539d6..099a251 100644
--- a/falcon/middlewares.py
+++ b/falcon/middlewares.py
@@ -1,16 +1,101 @@
+from typing import Iterable, Optional, Union
+
+from .request import Request
+from .response import Response
+
 
 class CORSMiddleware(object):
-    def process_response(self, req, resp, resource, req_succeeded):
-        """Implement a simple blanket CORS policy for all routes.
+    """CORS Middleware
+
+    This middleware provides a simple out-of-the box CORS policy, including handling
+    of preflighted requests from the browser.
+
+    See also:
+
+    * https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
+    * https://www.w3.org/TR/cors/#resource-processing-model
+
+    Keyword Arguments:
+        allow_origins (Union[str, Iterable[str]]): List of origins to allow (case
+            sensitive). The string ``'*'`` acts as a wildcard, matching every origin.
+            (default ``'*'``).
+        expose_headers (Optional[Union[str, Iterable[str]]]): List of additional response
+            headers to expose via the ``Access-Control-Expose-Headers`` header.
+            These headers are in addition to the CORS-safelisted ones:
+            ``Cache-Control``, ``Content-Language``, ``Content-Length``, ``Content-Type``,
+            ``Expires``, ``Last-Modified``, ``Pragma``. (default ``None``).
+
+            See also:
+            https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
+        allow_credentials (Optional[Union[str, Iterable[str]]]): List of origins (case sensitive)
+            for which to allow credentials via the ``Access-Control-Allow-Credentials`` header.
+            The string ``'*'`` acts as a wildcard, matching every allowed origin, while
+            ``None`` disallows all origins. This parameter takes effect only
+            if the origin is allowed by the ``allow_origins`` argument. (Default ``None``).
+
+    """
+    def __init__(
+        self,
+        allow_origins: Union[str, Iterable[str]] = '*',
+        expose_headers: Optional[Union[str, Iterable[str]]] = None,
+        allow_credentials: Optional[Union[str, Iterable[str]]] = None,
+    ):
+        if allow_origins == '*':
+            self.allow_origins = allow_origins
+        else:
+            if isinstance(allow_origins, str):
+                allow_origins = [allow_origins]
+            self.allow_origins = frozenset(allow_origins)
+            if '*' in self.allow_origins:
+                raise ValueError(
+                    'The wildcard string "*" may only be passed to allow_origins as a '
+                    'string literal, not inside an iterable.'
+                )
+
+        if expose_headers is not None and not isinstance(expose_headers, str):
+            expose_headers = ', '.join(expose_headers)
+        self.expose_headers = expose_headers
+
+        if allow_credentials is None:
+            allow_credentials = frozenset()
+        elif allow_credentials != '*':
+            if isinstance(allow_credentials, str):
+                allow_credentials = [allow_credentials]
+            allow_credentials = frozenset(allow_credentials)
+            if '*' in allow_credentials:
+                raise ValueError(
+                    'The wildcard string "*" may only be passed to allow_credentials as a '
+                    'string literal, not inside an iterable.'
+                )
+        self.allow_credentials = allow_credentials
+
+    def process_response(self, req: Request, resp: Response, resource, req_succeeded):
+        """Implement the CORS policy for all routes.
 
         This middleware provides a simple out-of-the box CORS policy,
         including handling of preflighted requests from the browser.
 
         See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
+
+        See also: https://www.w3.org/TR/cors/#resource-processing-model
         """
 
+        origin = req.get_header('Origin')
+        if origin is None:
+            return
+
+        if self.allow_origins != '*' and origin not in self.allow_origins:
+            return
+
         if resp.get_header('Access-Control-Allow-Origin') is None:
-            resp.set_header('Access-Control-Allow-Origin', '*')
+            set_origin = '*' if self.allow_origins == '*' else origin
+            if self.allow_credentials == '*' or origin in self.allow_credentials:
+                set_origin = origin
+                resp.set_header('Access-Control-Allow-Credentials', 'true')
+            resp.set_header('Access-Control-Allow-Origin', set_origin)
+
+        if self.expose_headers:
+            resp.set_header('Access-Control-Expose-Headers', self.expose_headers)
 
         if (req_succeeded and
                 req.method == 'OPTIONS' and


Test patch:
diff --git a/tests/test_cors_middleware.py b/tests/test_cors_middleware.py
new file mode 100644
index 0000000..d8ef5dc
--- /dev/null
+++ b/tests/test_cors_middleware.py
@@ -0,0 +1,201 @@
+import pytest
+
+import falcon
+from falcon import testing
+
+from _util import create_app, disable_asgi_non_coroutine_wrapping  # NOQA
+
+
+@pytest.fixture
+def client(asgi):
+    app = create_app(asgi)
+    return testing.TestClient(app)
+
+
+@pytest.fixture(scope='function')
+def cors_client(asgi):
+    # NOTE(kgriffs): Disable wrapping to test that built-in middleware does
+    #   not require it (since this will be the case for non-test apps).
+    with disable_asgi_non_coroutine_wrapping():
+        app = create_app(asgi, cors_enable=True)
+    return testing.TestClient(app)
+
+
+class CORSHeaderResource:
+
+    def on_get(self, req, resp):
+        resp.body = "I'm a CORS test response"
+
+    def on_delete(self, req, resp):
+        resp.set_header('Access-Control-Allow-Origin', 'example.com')
+        resp.body = "I'm a CORS test response"
+
+
+class TestCorsMiddleware:
+
+    def test_disabled_cors_should_not_add_any_extra_headers(self, client):
+        client.app.add_route('/', CORSHeaderResource())
+        result = client.simulate_get(headers={'Origin': 'localhost'})
+        h = dict(result.headers.lower_items()).keys()
+        assert 'Access-Control-Allow-Origin'.lower() not in h
+        assert 'Access-Control-Allow-Credentials'.lower() not in h
+        assert 'Access-Control-Expose-Headers'.lower() not in h
+
+    def test_enabled_cors_no_origin(self, client):
+        client.app.add_route('/', CORSHeaderResource())
+        result = client.simulate_get()
+        h = dict(result.headers.lower_items()).keys()
+        assert 'Access-Control-Allow-Origin'.lower() not in h
+        assert 'Access-Control-Allow-Credentials'.lower() not in h
+        assert 'Access-Control-Expose-Headers'.lower() not in h
+
+    def test_enabled_cors_should_add_extra_headers_on_response(self, cors_client):
+        cors_client.app.add_route('/', CORSHeaderResource())
+        result = cors_client.simulate_get(headers={'Origin': 'localhost'})
+        assert 'Access-Control-Allow-Origin'.lower() in dict(
+            result.headers.lower_items()).keys()
+
+    def test_enabled_cors_should_accept_all_origins_requests(self, cors_client):
+        cors_client.app.add_route('/', CORSHeaderResource())
+
+        result = cors_client.simulate_get(headers={'Origin': 'localhost'})
+        assert result.headers['Access-Control-Allow-Origin'] == '*'
+
+        result = cors_client.simulate_delete(headers={'Origin': 'localhost'})
+        assert result.headers['Access-Control-Allow-Origin'] == 'example.com'
+
+    def test_enabled_cors_handles_preflighting(self, cors_client):
+        cors_client.app.add_route('/', CORSHeaderResource())
+        result = cors_client.simulate_options(headers=(
+            ('Origin', 'localhost'),
+            ('Access-Control-Request-Method', 'GET'),
+            ('Access-Control-Request-Headers', 'X-PINGOTHER, Content-Type'),
+        ))
+        assert result.headers['Access-Control-Allow-Methods'] == 'DELETE, GET'
+        assert result.headers['Access-Control-Allow-Headers'] == 'X-PINGOTHER, Content-Type'
+        assert result.headers['Access-Control-Max-Age'] == '86400'  # 24 hours in seconds
+
+    def test_enabled_cors_handles_preflighting_no_headers_in_req(self, cors_client):
+        cors_client.app.add_route('/', CORSHeaderResource())
+        result = cors_client.simulate_options(headers=(
+            ('Origin', 'localhost'),
+            ('Access-Control-Request-Method', 'POST'),
+        ))
+        assert result.headers['Access-Control-Allow-Methods'] == 'DELETE, GET'
+        assert result.headers['Access-Control-Allow-Headers'] == '*'
+        assert result.headers['Access-Control-Max-Age'] == '86400'  # 24 hours in seconds
+
+
+@pytest.fixture(scope='function')
+def make_cors_client(asgi):
+    def make(middleware):
+        app = create_app(asgi, middleware=middleware)
+        return testing.TestClient(app)
+    return make
+
+
+class TestCustomCorsMiddleware:
+
+    def test_raises(self):
+        with pytest.raises(ValueError, match='passed to allow_origins'):
+            falcon.CORSMiddleware(allow_origins=['*'])
+        with pytest.raises(ValueError, match='passed to allow_credentials'):
+            falcon.CORSMiddleware(allow_credentials=['*'])
+
+    @pytest.mark.parametrize('allow, fail_origins, success_origins', (
+        ('*', [None], ['foo', 'bar']),
+        ('test', ['other', 'Test', 'TEST'], ['test']),
+        (['foo', 'bar'], ['foo, bar', 'foobar', 'foo,bar', 'Foo', 'BAR'], ['foo', 'bar']),
+    ))
+    def test_allow_origin(self, make_cors_client, allow, fail_origins, success_origins):
+        client = make_cors_client(falcon.CORSMiddleware(allow_origins=allow))
+        client.app.add_route('/', CORSHeaderResource())
+
+        for origin in fail_origins:
+            h = {'Origin': origin} if origin is not None else {}
+            res = client.simulate_get(headers=h)
+            h = dict(res.headers.lower_items()).keys()
+            assert 'Access-Control-Allow-Origin'.lower() not in h
+            assert 'Access-Control-Allow-Credentials'.lower() not in h
+            assert 'Access-Control-Expose-Headers'.lower() not in h
+
+        for origin in success_origins:
+            res = client.simulate_get(headers={'Origin': origin})
+            assert res.headers['Access-Control-Allow-Origin'] == '*' if allow == '*' else origin
+            h = dict(res.headers.lower_items()).keys()
+            assert 'Access-Control-Allow-Credentials'.lower() not in h
+            assert 'Access-Control-Expose-Headers'.lower() not in h
+
+    def test_allow_credential_wildcard(self, make_cors_client):
+        client = make_cors_client(falcon.CORSMiddleware(allow_credentials='*'))
+        client.app.add_route('/', CORSHeaderResource())
+
+        res = client.simulate_get(headers={'Origin': 'localhost'})
+        assert res.headers['Access-Control-Allow-Origin'] == 'localhost'
+        assert res.headers['Access-Control-Allow-Credentials'] == 'true'
+
+    @pytest.mark.parametrize('allow, successOrigin', (
+        (['foo', 'bar'], ['foo', 'bar']),
+        ('foo', ['foo']),
+    ))
+    def test_allow_credential_list_or_str(self, make_cors_client, allow, successOrigin):
+        client = make_cors_client(falcon.CORSMiddleware(allow_credentials=allow))
+        client.app.add_route('/', CORSHeaderResource())
+
+        for origin in ('foo, bar', 'foobar', 'foo,bar', 'Foo', 'BAR'):
+            res = client.simulate_get(headers={'Origin': origin})
+            assert res.headers['Access-Control-Allow-Origin'] == '*'
+            h = dict(res.headers.lower_items()).keys()
+            assert 'Access-Control-Allow-Credentials'.lower() not in h
+            assert 'Access-Control-Expose-Headers'.lower() not in h
+
+        for origin in successOrigin:
+            res = client.simulate_get(headers={'Origin': origin})
+            assert res.headers['Access-Control-Allow-Origin'] == origin
+            assert res.headers['Access-Control-Allow-Credentials'] == 'true'
+            h = dict(res.headers.lower_items()).keys()
+            assert 'Access-Control-Expose-Headers'.lower() not in h
+
+    def test_allow_credential_existing_origin(self, make_cors_client):
+        client = make_cors_client(falcon.CORSMiddleware(allow_credentials='*'))
+        client.app.add_route('/', CORSHeaderResource())
+
+        res = client.simulate_delete(headers={'Origin': 'something'})
+        assert res.headers['Access-Control-Allow-Origin'] == 'example.com'
+        h = dict(res.headers.lower_items()).keys()
+        assert 'Access-Control-Allow-Credentials'.lower() not in h
+
+    def test_allow_origin_allow_credential(self, make_cors_client):
+        client = make_cors_client(
+            falcon.CORSMiddleware(allow_origins='test', allow_credentials='*'))
+        client.app.add_route('/', CORSHeaderResource())
+
+        for origin in ['foo', 'TEST']:
+            res = client.simulate_get(headers={'Origin': origin})
+            h = dict(res.headers.lower_items()).keys()
+            assert 'Access-Control-Allow-Origin'.lower() not in h
+            assert 'Access-Control-Allow-Credentials'.lower() not in h
+            assert 'Access-Control-Expose-Headers'.lower() not in h
+
+        res = client.simulate_get(headers={'Origin': 'test'})
+        assert res.headers['Access-Control-Allow-Origin'] == 'test'
+        assert res.headers['Access-Control-Allow-Credentials'] == 'true'
+        h = dict(res.headers.lower_items()).keys()
+        assert 'Access-Control-Expose-Headers'.lower() not in h
+
+    @pytest.mark.parametrize('attr, exp', (
+        ('foo', 'foo'),
+        ('foo, bar', 'foo, bar'),
+        (['foo', 'bar'], 'foo, bar'),
+    ))
+    def test_expose_headers(self, make_cors_client, attr, exp):
+        client = make_cors_client(
+            falcon.CORSMiddleware(expose_headers=attr, allow_credentials=None)
+        )
+        client.app.add_route('/', CORSHeaderResource())
+
+        res = client.simulate_get(headers={'Origin': 'something'})
+        assert res.headers['Access-Control-Allow-Origin'] == '*'
+        assert res.headers['Access-Control-Expose-Headers'] == exp
+        h = dict(res.headers.lower_items()).keys()
+        assert 'Access-Control-Allow-Credentials'.lower() not in h
diff --git a/tests/test_headers.py b/tests/test_headers.py
index d42efe3..41f23f8 100644
--- a/tests/test_headers.py
+++ b/tests/test_headers.py
@@ -6,7 +6,7 @@ import pytest
 import falcon
 from falcon import testing
 
-from _util import create_app, disable_asgi_non_coroutine_wrapping  # NOQA
+from _util import create_app  # NOQA
 
 
 SAMPLE_BODY = testing.rand_string(0, 128 * 1024)
@@ -18,15 +18,6 @@ def client(asgi):
     return testing.TestClient(app)
 
 
-@pytest.fixture(scope='function')
-def cors_client(asgi):
-    # NOTE(kgriffs): Disable wrapping to test that built-in middleware does
-    #   not require it (since this will be the case for non-test apps).
-    with disable_asgi_non_coroutine_wrapping():
-        app = create_app(asgi, cors_enable=True)
-    return testing.TestClient(app)
-
-
 class XmlResource:
     def __init__(self, content_type):
         self.content_type = content_type
@@ -245,16 +236,6 @@ class ExpiresHeaderResource:
         resp.expires = self._expires
 
 
-class CORSHeaderResource:
-
-    def on_get(self, req, resp):
-        resp.body = "I'm a CORS test response"
-
-    def on_delete(self, req, resp):
-        resp.set_header('Access-Control-Allow-Origin', 'example.com')
-        resp.body = "I'm a CORS test response"
-
-
 class CustomHeaders:
     def items(self):
         return [('test-header', 'test-value')]
@@ -769,46 +750,6 @@ class TestHeaders:
         content_length = str(len(falcon.HTTPNotFound().to_json()))
         assert result.headers['Content-Length'] == content_length
 
-    def test_disabled_cors_should_not_add_any_extra_headers(self, client):
-        client.app.add_route('/', CORSHeaderResource())
-        result = client.simulate_get()
-        assert 'Access-Control-Allow-Origin'.lower() not in dict(
-            result.headers.lower_items()).keys()
-
-    def test_enabled_cors_should_add_extra_headers_on_response(self, cors_client):
-        cors_client.app.add_route('/', CORSHeaderResource())
-        result = cors_client.simulate_get()
-        assert 'Access-Control-Allow-Origin'.lower() in dict(
-            result.headers.lower_items()).keys()
-
-    def test_enabled_cors_should_accept_all_origins_requests(self, cors_client):
-        cors_client.app.add_route('/', CORSHeaderResource())
-
-        result = cors_client.simulate_get()
-        assert result.headers['Access-Control-Allow-Origin'] == '*'
-
-        result = cors_client.simulate_delete()
-        assert result.headers['Access-Control-Allow-Origin'] == 'example.com'
-
-    def test_enabled_cors_handles_preflighting(self, cors_client):
-        cors_client.app.add_route('/', CORSHeaderResource())
-        result = cors_client.simulate_options(headers=(
-            ('Access-Control-Request-Method', 'GET'),
-            ('Access-Control-Request-Headers', 'X-PINGOTHER, Content-Type'),
-        ))
-        assert result.headers['Access-Control-Allow-Methods'] == 'DELETE, GET'
-        assert result.headers['Access-Control-Allow-Headers'] == 'X-PINGOTHER, Content-Type'
-        assert result.headers['Access-Control-Max-Age'] == '86400'  # 24 hours in seconds
-
-    def test_enabled_cors_handles_preflighting_no_headers_in_req(self, cors_client):
-        cors_client.app.add_route('/', CORSHeaderResource())
-        result = cors_client.simulate_options(headers=(
-            ('Access-Control-Request-Method', 'POST'),
-        ))
-        assert result.headers['Access-Control-Allow-Methods'] == 'DELETE, GET'
-        assert result.headers['Access-Control-Allow-Headers'] == '*'
-        assert result.headers['Access-Control-Max-Age'] == '86400'  # 24 hours in seconds
-
     def test_set_headers_with_custom_class(self, client):
         client.app.add_route('/', CustomHeadersResource())
 
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
index e2a20de..612af84 100644
--- a/tests/test_middleware.py
+++ b/tests/test_middleware.py
@@ -957,7 +957,7 @@ class TestCORSMiddlewareWithAnotherMiddleware(TestMiddleware):
         app = create_app(asgi, middleware=mw, cors_enable=True)
         app.add_route('/', TestCorsResource())
         client = testing.TestClient(app)
-        result = client.simulate_get()
+        result = client.simulate_get(headers={'Origin': 'localhost'})
         assert result.headers['Access-Control-Allow-Origin'] == '*'