diff --git a/pygitweb/auth_test.py b/pygitweb/auth_test.py
index 070e5a8..1b996c3 100644
--- a/pygitweb/auth_test.py
+++ b/pygitweb/auth_test.py
@@ -5,10 +5,13 @@ import sys
 from collections.abc import Generator
 from pathlib import Path
 from unittest.mock import AsyncMock, patch
+from urllib.parse import parse_qs, urlparse
 
+import httpx
 import pytest
-from fastapi import HTTPException
+from fastapi import FastAPI, HTTPException, Request
 from fastapi.testclient import TestClient
+from httpx import ASGITransport
 
 from pygitweb.auth import ACCESS_TOKEN_COOKIE_NAME
 from pygitweb.auth_config import AuthConfig, LocalUser, init_auth_config, write_auth_config
@@ -29,6 +32,8 @@ from pygitweb.password_hash import hash_password, verify_password
 from pygitweb.permissions import PERMISSION_ADD_PROJECTS, PermissionsMap
 from pygitweb.sessions import get_session
 
+_REAL_ASYNC_CLIENT = httpx.AsyncClient
+
 
 class _DummyResponse:
 	def __init__(self, payload: object, is_success: bool = True) -> None:
@@ -48,6 +53,47 @@ _LOCAL_ADMIN = AuthConfig(
 )
 
 
+def _build_test_idp_app(
+	email: str = "user@example.com",
+	preferred_username: str = "oauthuser",
+) -> FastAPI:
+	idp = FastAPI()
+
+	@idp.post("/token")
+	async def google_token(request: Request) -> dict[str, str]:
+		form = await request.form()
+		grant_type = str(form.get("grant_type") or "")
+		code = str(form.get("code") or "")
+		if grant_type != "authorization_code" or code != "auth-code":
+			return {"error": "invalid_grant"}
+		return {"access_token": "idp-access-token"}
+
+	@idp.post("/login/oauth/access_token")
+	async def github_token(request: Request) -> dict[str, str]:
+		form = await request.form()
+		code = str(form.get("code") or "")
+		if code != "auth-code":
+			return {"error": "bad_code"}
+		return {"access_token": "idp-access-token"}
+
+	@idp.get("/v1/userinfo")
+	async def google_userinfo() -> dict[str, str]:
+		return {"sub": "oauth-sub-1", "email": email, "preferred_username": preferred_username}
+
+	@idp.get("/user")
+	async def github_user() -> dict[str, object]:
+		return {"id": "123", "login": preferred_username, "email": None}
+
+	@idp.get("/user/emails")
+	async def github_emails() -> list[dict[str, object]]:
+		return [
+			{"email": email, "primary": True, "verified": True},
+			{"email": "other@example.com", "primary": False, "verified": True},
+		]
+
+	return idp
+
+
 @pytest.fixture
 def client() -> Generator[TestClient, None, None]:
 	with TestClient(app) as c:
@@ -382,6 +428,41 @@ def test_oauth_callback_creates_session(client: TestClient, oauth_auth_config: A
 		clear_client_cookies(client)
 
 
+def test_oauth_flow_with_asgi_idp(client: TestClient, oauth_auth_config: AuthConfig) -> None:
+	idp_app = _build_test_idp_app()
+
+	async def runner() -> None:
+		transport = ASGITransport(app=idp_app)
+
+		def _idp_client_factory(*_: object, **__: object) -> httpx.AsyncClient:
+			return _REAL_ASYNC_CLIENT(transport=transport, base_url="https://idp.test")
+
+		with (
+			patch.object(settings, "AUTH", True),
+			patch("pygitweb.auth_oauth.httpx.AsyncClient", side_effect=_idp_client_factory),
+		):
+			start = client.get("/auth/oauth/start", params={"next": "/boards"}, follow_redirects=False)
+			assert start.status_code == 303
+			location = start.headers["location"]
+			state_values = parse_qs(urlparse(location).query).get("state", [])
+			assert state_values
+			state = state_values[0]
+			callback = client.get(
+				"/auth/oauth/callback",
+				params={"code": "auth-code", "state": state},
+				follow_redirects=False,
+			)
+			assert callback.status_code == 303
+			rec = get_session(callback.cookies[ACCESS_TOKEN_COOKIE_NAME])
+			assert rec is not None
+			assert rec.username == "oauthuser"
+			assert rec.subject == "oauthuser"
+			assert rec.email == "user@example.com"
+			assert rec.auth_method == "oauth"
+
+	asyncio.run(runner())
+
+
 def test_auth_status_no_gravatar_for_local_login(client: TestClient, local_auth_config: AuthConfig) -> None:
 	with patch.object(settings, "AUTH", True):
 		client.post(
