Repository: Colin-b/requests_auth

Issue:
Allow to use AND (&) symbol between auth classes
As for now only + can be used

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e7a8d22
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+language: python
+python:
+  - "3.6"
+  - "3.7"
+  - "3.8"
+install:
+  - pip install .[testing]
+script:
+  - pytest
+deploy:
+  provider: pypi
+  username: __token__
+  edge: true
+  distributions: "sdist bdist_wheel"
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 25a73e5..e8f57bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+## [5.0.1] - 2019-11-28
+### Added
+- Allow to use & between authentication classes.
+
+### Fixed
+- Avoid DeprecationWarning in case multi auth is used with +
+- Avoid packaging tests (introduced in 5.0.0)
+
 ## [5.0.0] - 2019-11-21
 ### Changed
 - OAuth2ClientCredentials username parameter is now client_id
@@ -106,7 +114,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 - Public release
 
-[Unreleased]: https://github.com/Colin-b/requests_auth/compare/v5.0.0...HEAD
+[Unreleased]: https://github.com/Colin-b/requests_auth/compare/v5.0.1...HEAD
+[5.0.1]: https://github.com/Colin-b/requests_auth/compare/v5.0.0...v5.0.1
 [5.0.0]: https://github.com/Colin-b/requests_auth/compare/v4.1.0...v5.0.0
 [4.1.0]: https://github.com/Colin-b/requests_auth/compare/v4.0.1...v4.1.0
 [4.0.1]: https://github.com/Colin-b/requests_auth/compare/v4.0.0...v4.0.1
diff --git a/_config.yml b/_config.yml
deleted file mode 100644
index c419263..0000000
--- a/_config.yml
+++ /dev/null
@@ -1,1 +0,0 @@
-theme: jekyll-theme-cayman
\ No newline at end of file
diff --git a/requests_auth/authentication.py b/requests_auth/authentication.py
index 27aac84..4a00ebd 100644
--- a/requests_auth/authentication.py
+++ b/requests_auth/authentication.py
@@ -82,9 +82,14 @@ class SupportMultiAuth:
     """Inherit from this class to be able to use your class with requests_auth provided authentication classes."""
 
     def __add__(self, other):
-        if isinstance(other, Auths):
-            return Auths(self, *other.authentication_modes)
-        return Auths(self, other)
+        if isinstance(other, _MultiAuth):
+            return _MultiAuth(self, *other.authentication_modes)
+        return _MultiAuth(self, other)
+
+    def __and__(self, other):
+        if isinstance(other, _MultiAuth):
+            return _MultiAuth(self, *other.authentication_modes)
+        return _MultiAuth(self, other)
 
 
 class BrowserAuth:
@@ -1121,14 +1126,10 @@ class NTLM(requests.auth.AuthBase, SupportMultiAuth):
         return r
 
 
-class Auths(requests.auth.AuthBase):
+class _MultiAuth(requests.auth.AuthBase):
     """Authentication using multiple authentication methods."""
 
     def __init__(self, *authentication_modes):
-        warnings.warn(
-            "Auths class will be removed in the future. Use + instead.",
-            DeprecationWarning,
-        )
         self.authentication_modes = authentication_modes
 
     def __call__(self, r):
@@ -1137,6 +1138,20 @@ class Auths(requests.auth.AuthBase):
         return r
 
     def __add__(self, other):
-        if isinstance(other, Auths):
-            return Auths(*self.authentication_modes, *other.authentication_modes)
-        return Auths(*self.authentication_modes, other)
+        if isinstance(other, _MultiAuth):
+            return _MultiAuth(*self.authentication_modes, *other.authentication_modes)
+        return _MultiAuth(*self.authentication_modes, other)
+
+    def __and__(self, other):
+        if isinstance(other, _MultiAuth):
+            return _MultiAuth(*self.authentication_modes, *other.authentication_modes)
+        return _MultiAuth(*self.authentication_modes, other)
+
+
+class Auths(_MultiAuth):
+    def __init__(self, *authentication_modes):
+        warnings.warn(
+            "Auths class will be removed in the future. Use + instead.",
+            DeprecationWarning,
+        )
+        super().__init__(*authentication_modes)
diff --git a/requests_auth/version.py b/requests_auth/version.py
index ffe1d01..f309563 100644
--- a/requests_auth/version.py
+++ b/requests_auth/version.py
@@ -3,4 +3,4 @@
 # Major should be incremented in case there is a breaking change. (eg: 2.5.8 -> 3.0.0)
 # Minor should be incremented in case there is an enhancement. (eg: 2.5.8 -> 2.6.0)
 # Patch should be incremented in case there is a bug fix. (eg: 2.5.8 -> 2.5.9)
-__version__ = "5.0.0"
+__version__ = "5.0.1"
diff --git a/setup.py b/setup.py
index f1d298b..ae20ee1 100644
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,7 @@ setup(
     author_email="colin.bounouar.dev@gmail.com",
     maintainer="Colin Bounouar",
     maintainer_email="colin.bounouar.dev@gmail.com",
-    url="https://github.com/Colin-b/requests_auth",
+    url="https://colin-b.github.io/requests_auth/",
     description="Authentication for Requests",
     long_description=long_description,
     long_description_content_type="text/markdown",
@@ -41,7 +41,7 @@ setup(
         "apikey",
         "multiple",
     ],
-    packages=find_packages(exclude=["tests"]),
+    packages=find_packages(exclude=["tests*"]),
     install_requires=[
         # Used for Base Authentication and to communicate with OAuth2 servers
         "requests==2.*"
@@ -56,6 +56,7 @@ setup(
     },
     python_requires=">=3.6",
     project_urls={
+        "GitHub": "https://github.com/Colin-b/requests_auth",
         "Changelog": "https://github.com/Colin-b/requests_auth/blob/master/CHANGELOG.md",
         "Issues": "https://github.com/Colin-b/requests_auth/issues",
     },


Test patch:
diff --git a/tests/test_multiple_authentication.py b/tests/test_add_operator.py
similarity index 97%
rename from tests/test_multiple_authentication.py
rename to tests/test_add_operator.py
index 6956378..f1856f1 100644
--- a/tests/test_multiple_authentication.py
+++ b/tests/test_add_operator.py
@@ -16,16 +16,6 @@ def test_basic_and_api_key_authentication_can_be_combined(responses: RequestsMoc
     assert header.get("X-Api-Key") == "my_provided_api_key"
 
 
-def test_basic_and_api_key_authentication_can_be_combined_deprecated(
-    responses: RequestsMock,
-):
-    basic_auth = requests_auth.Basic("test_user", "test_pwd")
-    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
-    header = get_header(responses, requests_auth.Auths(basic_auth, api_key_auth))
-    assert header.get("Authorization") == "Basic dGVzdF91c2VyOnRlc3RfcHdk"
-    assert header.get("X-Api-Key") == "my_provided_api_key"
-
-
 def test_header_api_key_and_multiple_authentication_can_be_combined(
     token_cache, responses: RequestsMock
 ):
diff --git a/tests/test_and_operator.py b/tests/test_and_operator.py
new file mode 100644
index 0000000..68250fd
--- /dev/null
+++ b/tests/test_and_operator.py
@@ -0,0 +1,391 @@
+import datetime
+
+from responses import RequestsMock
+import requests
+
+import requests_auth
+from tests.oauth2_helper import token_cache, browser_mock, BrowserMock, create_token
+from tests.auth_helper import get_header
+
+
+def test_basic_and_api_key_authentication_can_be_combined(responses: RequestsMock):
+    basic_auth = requests_auth.Basic("test_user", "test_pwd")
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    header = get_header(responses, basic_auth & api_key_auth)
+    assert header.get("Authorization") == "Basic dGVzdF91c2VyOnRlc3RfcHdk"
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+
+
+def test_header_api_key_and_multiple_authentication_can_be_combined(
+    token_cache, responses: RequestsMock
+):
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    api_key_auth2 = requests_auth.HeaderApiKey(
+        "my_provided_api_key2", header_name="X-Api-Key2"
+    )
+    api_key_auth3 = requests_auth.HeaderApiKey(
+        "my_provided_api_key3", header_name="X-Api-Key3"
+    )
+    header = get_header(responses, api_key_auth & (api_key_auth2 & api_key_auth3))
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+    assert header.get("X-Api-Key2") == "my_provided_api_key2"
+    assert header.get("X-Api-Key3") == "my_provided_api_key3"
+
+
+def test_multiple_auth_and_header_api_key_can_be_combined(
+    token_cache, responses: RequestsMock
+):
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    api_key_auth2 = requests_auth.HeaderApiKey(
+        "my_provided_api_key2", header_name="X-Api-Key2"
+    )
+    api_key_auth3 = requests_auth.HeaderApiKey(
+        "my_provided_api_key3", header_name="X-Api-Key3"
+    )
+    header = get_header(responses, (api_key_auth & api_key_auth2) & api_key_auth3)
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+    assert header.get("X-Api-Key2") == "my_provided_api_key2"
+    assert header.get("X-Api-Key3") == "my_provided_api_key3"
+
+
+def test_multiple_auth_and_multiple_auth_can_be_combined(
+    token_cache, responses: RequestsMock
+):
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    api_key_auth2 = requests_auth.HeaderApiKey(
+        "my_provided_api_key2", header_name="X-Api-Key2"
+    )
+    api_key_auth3 = requests_auth.HeaderApiKey(
+        "my_provided_api_key3", header_name="X-Api-Key3"
+    )
+    api_key_auth4 = requests_auth.HeaderApiKey(
+        "my_provided_api_key4", header_name="X-Api-Key4"
+    )
+    header = get_header(
+        responses, (api_key_auth & api_key_auth2) & (api_key_auth3 & api_key_auth4)
+    )
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+    assert header.get("X-Api-Key2") == "my_provided_api_key2"
+    assert header.get("X-Api-Key3") == "my_provided_api_key3"
+    assert header.get("X-Api-Key4") == "my_provided_api_key4"
+
+
+def test_basic_and_multiple_authentication_can_be_combined(
+    token_cache, responses: RequestsMock
+):
+    basic_auth = requests_auth.Basic("test_user", "test_pwd")
+    api_key_auth2 = requests_auth.HeaderApiKey(
+        "my_provided_api_key2", header_name="X-Api-Key2"
+    )
+    api_key_auth3 = requests_auth.HeaderApiKey(
+        "my_provided_api_key3", header_name="X-Api-Key3"
+    )
+    header = get_header(responses, basic_auth & (api_key_auth2 & api_key_auth3))
+    assert header.get("Authorization") == "Basic dGVzdF91c2VyOnRlc3RfcHdk"
+    assert header.get("X-Api-Key2") == "my_provided_api_key2"
+    assert header.get("X-Api-Key3") == "my_provided_api_key3"
+
+
+def test_query_api_key_and_multiple_authentication_can_be_combined(
+    token_cache, responses: RequestsMock
+):
+    api_key_auth = requests_auth.QueryApiKey("my_provided_api_key")
+    api_key_auth2 = requests_auth.QueryApiKey(
+        "my_provided_api_key2", query_parameter_name="api_key2"
+    )
+    api_key_auth3 = requests_auth.HeaderApiKey(
+        "my_provided_api_key3", header_name="X-Api-Key3"
+    )
+
+    # Mock a dummy response
+    responses.add(responses.GET, "http://authorized_only")
+    # Send a request to this dummy URL with authentication
+    response = requests.get(
+        "http://authorized_only", auth=api_key_auth & (api_key_auth2 & api_key_auth3)
+    )
+    # Return headers received on this dummy URL
+    assert (
+        response.request.path_url
+        == "/?api_key=my_provided_api_key&api_key2=my_provided_api_key2"
+    )
+    assert response.request.headers.get("X-Api-Key3") == "my_provided_api_key3"
+
+
+def test_oauth2_resource_owner_password_and_api_key_authentication_can_be_combined(
+    token_cache, responses: RequestsMock
+):
+    resource_owner_password_auth = requests_auth.OAuth2ResourceOwnerPasswordCredentials(
+        "http://provide_access_token", username="test_user", password="test_pwd"
+    )
+    responses.add(
+        responses.POST,
+        "http://provide_access_token",
+        json={
+            "access_token": "2YotnFZFEjr1zCsicMWpAA",
+            "token_type": "example",
+            "expires_in": 3600,
+            "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
+            "example_parameter": "example_value",
+        },
+    )
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    header = get_header(responses, resource_owner_password_auth & api_key_auth)
+    assert header.get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA"
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+
+
+def test_oauth2_resource_owner_password_and_multiple_authentication_can_be_combined(
+    token_cache, responses: RequestsMock
+):
+    resource_owner_password_auth = requests_auth.OAuth2ResourceOwnerPasswordCredentials(
+        "http://provide_access_token", username="test_user", password="test_pwd"
+    )
+    responses.add(
+        responses.POST,
+        "http://provide_access_token",
+        json={
+            "access_token": "2YotnFZFEjr1zCsicMWpAA",
+            "token_type": "example",
+            "expires_in": 3600,
+            "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
+            "example_parameter": "example_value",
+        },
+    )
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    api_key_auth2 = requests_auth.HeaderApiKey(
+        "my_provided_api_key2", header_name="X-Api-Key2"
+    )
+    header = get_header(
+        responses, resource_owner_password_auth & (api_key_auth & api_key_auth2)
+    )
+    assert header.get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA"
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+    assert header.get("X-Api-Key2") == "my_provided_api_key2"
+
+
+def test_oauth2_client_credential_and_api_key_authentication_can_be_combined(
+    token_cache, responses: RequestsMock
+):
+    resource_owner_password_auth = requests_auth.OAuth2ClientCredentials(
+        "http://provide_access_token", client_id="test_user", client_secret="test_pwd"
+    )
+    responses.add(
+        responses.POST,
+        "http://provide_access_token",
+        json={
+            "access_token": "2YotnFZFEjr1zCsicMWpAA",
+            "token_type": "example",
+            "expires_in": 3600,
+            "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
+            "example_parameter": "example_value",
+        },
+    )
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    header = get_header(responses, resource_owner_password_auth & api_key_auth)
+    assert header.get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA"
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+
+
+def test_oauth2_client_credential_and_multiple_authentication_can_be_combined(
+    token_cache, responses: RequestsMock
+):
+    resource_owner_password_auth = requests_auth.OAuth2ClientCredentials(
+        "http://provide_access_token", client_id="test_user", client_secret="test_pwd"
+    )
+    responses.add(
+        responses.POST,
+        "http://provide_access_token",
+        json={
+            "access_token": "2YotnFZFEjr1zCsicMWpAA",
+            "token_type": "example",
+            "expires_in": 3600,
+            "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
+            "example_parameter": "example_value",
+        },
+    )
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    api_key_auth2 = requests_auth.HeaderApiKey(
+        "my_provided_api_key2", header_name="X-Api-Key2"
+    )
+    header = get_header(
+        responses, resource_owner_password_auth & (api_key_auth & api_key_auth2)
+    )
+    assert header.get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA"
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+    assert header.get("X-Api-Key2") == "my_provided_api_key2"
+
+
+def test_oauth2_authorization_code_and_api_key_authentication_can_be_combined(
+    token_cache, responses: RequestsMock, browser_mock: BrowserMock
+):
+    authorization_code_auth = requests_auth.OAuth2AuthorizationCode(
+        "http://provide_code", "http://provide_access_token"
+    )
+    tab = browser_mock.add_response(
+        opened_url="http://provide_code?response_type=code&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F",
+        reply_url="http://localhost:5000#code=SplxlOBeZQQYbYS6WxSbIA&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de",
+    )
+    responses.add(
+        responses.POST,
+        "http://provide_access_token",
+        json={
+            "access_token": "2YotnFZFEjr1zCsicMWpAA",
+            "token_type": "example",
+            "expires_in": 3600,
+            "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
+            "example_parameter": "example_value",
+        },
+    )
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    header = get_header(responses, authorization_code_auth & api_key_auth)
+    assert header.get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA"
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+    tab.assert_success(
+        "You are now authenticated on 163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de. You may close this tab."
+    )
+
+
+def test_oauth2_authorization_code_and_multiple_authentication_can_be_combined(
+    token_cache, responses: RequestsMock, browser_mock: BrowserMock
+):
+    authorization_code_auth = requests_auth.OAuth2AuthorizationCode(
+        "http://provide_code", "http://provide_access_token"
+    )
+    tab = browser_mock.add_response(
+        opened_url="http://provide_code?response_type=code&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F",
+        reply_url="http://localhost:5000#code=SplxlOBeZQQYbYS6WxSbIA&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de",
+    )
+    responses.add(
+        responses.POST,
+        "http://provide_access_token",
+        json={
+            "access_token": "2YotnFZFEjr1zCsicMWpAA",
+            "token_type": "example",
+            "expires_in": 3600,
+            "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
+            "example_parameter": "example_value",
+        },
+    )
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    api_key_auth2 = requests_auth.HeaderApiKey(
+        "my_provided_api_key2", header_name="X-Api-Key2"
+    )
+    header = get_header(
+        responses, authorization_code_auth & (api_key_auth & api_key_auth2)
+    )
+    assert header.get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA"
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+    assert header.get("X-Api-Key2") == "my_provided_api_key2"
+    tab.assert_success(
+        "You are now authenticated on 163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de. You may close this tab."
+    )
+
+
+def test_oauth2_pkce_and_api_key_authentication_can_be_combined(
+    token_cache, responses: RequestsMock, browser_mock: BrowserMock, monkeypatch
+):
+    monkeypatch.setattr(requests_auth.authentication.os, "urandom", lambda x: b"1" * 63)
+    pkce_auth = requests_auth.OAuth2AuthorizationCodePKCE(
+        "http://provide_code", "http://provide_access_token"
+    )
+    tab = browser_mock.add_response(
+        opened_url="http://provide_code?response_type=code&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256",
+        reply_url="http://localhost:5000#code=SplxlOBeZQQYbYS6WxSbIA&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de",
+    )
+    responses.add(
+        responses.POST,
+        "http://provide_access_token",
+        json={
+            "access_token": "2YotnFZFEjr1zCsicMWpAA",
+            "token_type": "example",
+            "expires_in": 3600,
+            "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
+            "example_parameter": "example_value",
+        },
+    )
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    header = get_header(responses, pkce_auth & api_key_auth)
+    assert header.get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA"
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+    tab.assert_success(
+        "You are now authenticated on 163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de. You may close this tab."
+    )
+
+
+def test_oauth2_pkce_and_multiple_authentication_can_be_combined(
+    token_cache, responses: RequestsMock, browser_mock: BrowserMock, monkeypatch
+):
+    monkeypatch.setattr(requests_auth.authentication.os, "urandom", lambda x: b"1" * 63)
+    pkce_auth = requests_auth.OAuth2AuthorizationCodePKCE(
+        "http://provide_code", "http://provide_access_token"
+    )
+    tab = browser_mock.add_response(
+        opened_url="http://provide_code?response_type=code&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256",
+        reply_url="http://localhost:5000#code=SplxlOBeZQQYbYS6WxSbIA&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de",
+    )
+    responses.add(
+        responses.POST,
+        "http://provide_access_token",
+        json={
+            "access_token": "2YotnFZFEjr1zCsicMWpAA",
+            "token_type": "example",
+            "expires_in": 3600,
+            "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
+            "example_parameter": "example_value",
+        },
+    )
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    api_key_auth2 = requests_auth.HeaderApiKey(
+        "my_provided_api_key2", header_name="X-Api-Key2"
+    )
+    header = get_header(responses, pkce_auth & (api_key_auth & api_key_auth2))
+    assert header.get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA"
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+    assert header.get("X-Api-Key2") == "my_provided_api_key2"
+    tab.assert_success(
+        "You are now authenticated on 163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de. You may close this tab."
+    )
+
+
+def test_oauth2_implicit_and_api_key_authentication_can_be_combined(
+    token_cache, responses: RequestsMock, browser_mock: BrowserMock
+):
+    implicit_auth = requests_auth.OAuth2Implicit("http://provide_token")
+    expiry_in_1_hour = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
+    token = create_token(expiry_in_1_hour)
+    tab = browser_mock.add_response(
+        opened_url="http://provide_token?response_type=token&state=42a85b271b7a652ca3cc4c398cfd3f01b9ad36bf9c945ba823b023e8f8b95c4638576a0e3dcc96838b838bec33ec6c0ee2609d62ed82480b3b8114ca494c0521&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F",
+        reply_url="http://localhost:5000",
+        data=f"access_token={token}&state=42a85b271b7a652ca3cc4c398cfd3f01b9ad36bf9c945ba823b023e8f8b95c4638576a0e3dcc96838b838bec33ec6c0ee2609d62ed82480b3b8114ca494c0521",
+    )
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    header = get_header(responses, implicit_auth & api_key_auth)
+    assert header.get("Authorization") == f"Bearer {token}"
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+    tab.assert_success(
+        "You are now authenticated on 42a85b271b7a652ca3cc4c398cfd3f01b9ad36bf9c945ba823b023e8f8b95c4638576a0e3dcc96838b838bec33ec6c0ee2609d62ed82480b3b8114ca494c0521. You may close this tab."
+    )
+
+
+def test_oauth2_implicit_and_multiple_authentication_can_be_combined(
+    token_cache, responses: RequestsMock, browser_mock: BrowserMock
+):
+    implicit_auth = requests_auth.OAuth2Implicit("http://provide_token")
+    expiry_in_1_hour = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
+    token = create_token(expiry_in_1_hour)
+    tab = browser_mock.add_response(
+        opened_url="http://provide_token?response_type=token&state=42a85b271b7a652ca3cc4c398cfd3f01b9ad36bf9c945ba823b023e8f8b95c4638576a0e3dcc96838b838bec33ec6c0ee2609d62ed82480b3b8114ca494c0521&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F",
+        reply_url="http://localhost:5000",
+        data=f"access_token={token}&state=42a85b271b7a652ca3cc4c398cfd3f01b9ad36bf9c945ba823b023e8f8b95c4638576a0e3dcc96838b838bec33ec6c0ee2609d62ed82480b3b8114ca494c0521",
+    )
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    api_key_auth2 = requests_auth.HeaderApiKey(
+        "my_provided_api_key2", header_name="X-Api-Key2"
+    )
+    header = get_header(responses, implicit_auth & (api_key_auth & api_key_auth2))
+    assert header.get("Authorization") == f"Bearer {token}"
+    assert header.get("X-Api-Key") == "my_provided_api_key"
+    assert header.get("X-Api-Key2") == "my_provided_api_key2"
+    tab.assert_success(
+        "You are now authenticated on 42a85b271b7a652ca3cc4c398cfd3f01b9ad36bf9c945ba823b023e8f8b95c4638576a0e3dcc96838b838bec33ec6c0ee2609d62ed82480b3b8114ca494c0521. You may close this tab."
+    )
diff --git a/tests/test_auths.py b/tests/test_auths.py
new file mode 100644
index 0000000..5435f79
--- /dev/null
+++ b/tests/test_auths.py
@@ -0,0 +1,16 @@
+import pytest
+from responses import RequestsMock
+
+import requests_auth
+from tests.auth_helper import get_header
+
+
+def test_basic_and_api_key_authentication_can_be_combined_deprecated(
+    responses: RequestsMock,
+):
+    basic_auth = requests_auth.Basic("test_user", "test_pwd")
+    api_key_auth = requests_auth.HeaderApiKey("my_provided_api_key")
+    with pytest.warns(DeprecationWarning):
+        header = get_header(responses, requests_auth.Auths(basic_auth, api_key_auth))
+    assert header.get("Authorization") == "Basic dGVzdF91c2VyOnRlc3RfcHdk"
+    assert header.get("X-Api-Key") == "my_provided_api_key"