httpx auth flow is skipped by replay?
Ogaday opened this issue · comments
Hello, firstly thanks for VCR - I use it tons of projects. I've come across an issue with VCR for custom authorization with HTTPX.
I'm following the custom auth instructions for HTTPX here and testing my implementation with vcrpy.
I've tried to simplify as much as possible:
import httpx
class MyAuth(httpx.Auth):
requires_response_body = True
def __init__(self, client_id: str, client_secret: str, auth_url: str):
self.client_id = client_id
self.client_secret = client_secret
self.auth_url = auth_url
self.access_token = "Bearer empty"
def auth_flow(self, request):
try:
request.headers["Authorization"] = self.access_token
response = yield request
response.raise_for_status()
except httpx.HTTPStatusError:
auth_response = yield self.build_refresh_request()
self.update_token(response=auth_response)
request.headers["Authorization"] = self.access_token
yield request
def build_refresh_request(self):
return httpx.Request(
"POST",
self.auth_url,
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
}
)
def update_token(self, response: httpx.Response):
contents = response.json()
self.access_token = f"Bearer {contents['access_token']}"
And test file:
import os
from typing import Dict
import httpx
import pytest
import vcr
from my_auth import MyAuth
@pytest.fixture
def secrets() -> Dict[str, str]:
return {
"client_id": os.environ["CLIENT_ID"],
"client_secret": os.environ["CLIENT_SECRET"],
"auth_url": os.environ["AUTH_URL"],
"api_url": os.environ["API_URL"],
"api_key": os.environ["API_KEY"],
}
def test_auth(secrets: Dict[str, str]):
api_url = secrets["api_url"]
api_key = secrets["api_key"]
auth = MyAuth(
client_id=secrets["client_id"],
client_secret=secrets["client_secret"],
auth_url=secrets["auth_url"],
)
with vcr.use_cassette("my_auth.yaml"):
with httpx.Client(base_url=api_url) as client:
r = client.get(
"/endpoint",
auth=auth,
headers={"api-key": api_key, "Content-Type": "application/json"}
)
assert r.status_code == 200
# On first run:
# test_my_auth.py::test_auth PASSED
# On subsequent runs:
# FAILED test_my_auth.py::test_auth - assert 401 == 200
This test runs successfully the first time, as the auth flow is successful, but fails on subsequent test runs. Primarily, it looks like the requests from the auth flow are not replayed from the cassette. I think the mocking of client.get
obscures the calling of the auth flow. This means the initial unauthorized response is returned, and there is no auth refresh request made.
This is with:
Python 3.10.8
httpx==0.23.3
pytest==7.2.2
If I could be pointed in the right direction I'd be happy to try to open a PR.