"""#1718:微信回调路由可观测性日志分支覆盖(UA/state/错误透传)。 直接驱动 wechat_callback 路由函数,mock OAuth service 与用户仓储: - 成功路径:日志记录 UA、state 校验通过(MicroMessenger 内置浏览器) - 失败路径:OAuth 返回错误时记 warning 并抛 400 """ from __future__ import annotations import asyncio import os import sys from pathlib import Path from types import SimpleNamespace from unittest.mock import MagicMock, patch import pytest os.environ.setdefault("JWT_SECRET_KEY", "unit-test-secret-key-for-testing") os.environ.setdefault("DATABASE_URL", "sqlite:///test.db") sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "api")) from app.api.routes import auth as auth_route # noqa: E402 from fastapi import HTTPException # noqa: E402 class _FakeRequest: def __init__(self, ua: str): self.headers = {"User-Agent": ua} def _wechat_user(): return SimpleNamespace( openid="openid-callback-1", unionid="union-callback-1", nickname="微信用户", avatar_url="http://x/a.png", ) def _fake_oauth_factory(success: bool): service = MagicMock() if success: service.handle_callback.return_value = (_wechat_user(), None) else: service.handle_callback.return_value = (None, "无效的 state 参数,请求可能已过期或被篡改") return service def test_wechat_callback_success_logs_ua_and_state(caplog): fake_repo = MagicMock() sync_response = SimpleNamespace( access_token="at", refresh_token="rt", user_id="u-1", nickname="微信用户", avatar_url="", is_new_user=False, expires_in=1800, ) fake_use_case = MagicMock() fake_use_case.execute.return_value = (sync_response, None) user = SimpleNamespace( id="u-1", phone_verified=True, email_verified=True, email="u@example.com", ) fake_repo.find_by_id.return_value = user request_obj = SimpleNamespace(code="code-1", state="state-1") fake_http = _FakeRequest("Mozilla/5.0 (Linux; Android 13) MicroMessenger/8.0.40 WeChat/8.0.40") import packages.application.auth.wechat_oauth_service as oauth_mod import packages.application.auth.wechat_sync_use_case as sync_mod orig_oauth = oauth_mod.get_wechat_oauth_service orig_sync = sync_mod.WechatSyncUseCase oauth_mod.get_wechat_oauth_service = MagicMock(return_value=_fake_oauth_factory(success=True)) sync_mod.WechatSyncUseCase = MagicMock(return_value=fake_use_case) try: with caplog.at_level("INFO", logger="app.api.routes.auth"): resp = asyncio.run(auth_route.wechat_callback(request_obj, fake_http, user_repository=fake_repo)) finally: oauth_mod.get_wechat_oauth_service = orig_oauth sync_mod.WechatSyncUseCase = orig_sync assert resp.user_id == "u-1" assert resp.binding_complete is True log_text = " ".join(rec.getMessage() for rec in caplog.records) assert "微信回调" in log_text assert "MicroMessenger" in log_text or "微信内置浏览器=True" in log_text def test_wechat_callback_failure_raises_400_with_detail(caplog): request_obj = SimpleNamespace(code="code-bad", state="state-bad") fake_http = _FakeRequest("Mozilla/5.0 Chrome/127") fake_service = _fake_oauth_factory(success=False) import packages.application.auth.wechat_oauth_service as oauth_mod orig = oauth_mod.get_wechat_oauth_service oauth_mod.get_wechat_oauth_service = MagicMock(return_value=fake_service) try: with caplog.at_level("WARNING", logger="app.api.routes.auth"): with pytest.raises(HTTPException) as exc_info: asyncio.run(auth_route.wechat_callback(request_obj, fake_http, user_repository=MagicMock())) finally: oauth_mod.get_wechat_oauth_service = orig assert exc_info.value.status_code == 400 assert "state" in exc_info.value.detail assert any("微信回调" in rec.getMessage() for rec in caplog.records)