You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何被补丁的admin_only函数未返回True?排查及修复方案

FastAPI依赖补丁未生效的原因及修复方案

我在src/routes/get_orders.py中导入了src.lib.authorization路径下的admin_only函数,并将其作为FastAPI路由的依赖项。在test_get_orders.py中尝试用unittest.mock.patch对该函数打补丁,让它返回True,但运行pytest时补丁未生效,程序仍然执行原函数的逻辑。

get_orders.py

# get_orders.py

import boto3
import os
from fastapi import APIRouter, Depends
from aws_lambda_powertools import Logger
from fastapi.exceptions import HTTPException
from src.models import Order
from src.lib.dynamodb import DynamoConnection
from src.lib.response import fastapi_gateway_response
from src.lib.authorization import admin_only

logger = Logger()
router = APIRouter()

orders_table = DynamoConnection(
    os.environ.get("DYNAMODB_REGION", "us-east-1"),
    os.environ.get("DYNAMODB_ENDPOINT_URL", None),
    os.environ.get("DYNAMODB_ORDERS_TABLE_NAME", "orders"),
).table


@logger.inject_lambda_context
@router.get(
    "/orders",
    status_code=200,
    dependencies=[Depends(admin_only)],
)
def get_orders():
    logger.info(f"Getting orders")

    dynamo_response = orders_table.scan()

    if "Items" not in dynamo_response:
        raise HTTPException(status_code=404, detail="No orders found")

    orders = [Order(**o).clean() for o in dynamo_response.get("Items")]

    logger.info(f"Returning {len(orders)} orders")
    return fastapi_gateway_response(200, {}, orders)

test_get_orders.py

#test_get_orders.py

import pytest
import logging
from unittest.mock import patch
from freezegun import freeze_time
from botocore.stub import Stubber
from fastapi.testclient import TestClient
from src.api import app
from src.routes.get_orders import orders_table

logger = logging.getLogger()
test_client = TestClient(app, headers={"Authorization": "Bearer TOKEN"})


@pytest.fixture(autouse=True, scope="function")
def orders_dynamodb_stub():
    with Stubber(orders_table.meta.client) as ddb_stubber:
        yield ddb_stubber
        ddb_stubber.assert_no_pending_responses()


@freeze_time("2024-03-22 12:00:00")
@patch("src.routes.get_orders.admin_only", return_value=True)
def test_handler_valid_event_get_orders(orders_dynamodb_stub):
    orders_dynamodb_stub.add_response(
        "scan",
        {
            "Items": [
                {
                    "order_id": {"S": "ORDER-1"},
                    "dessert_id": {"S": "DESSERT-1"},
                    "dessert_name": {"S": "Chocolate Cake"},
                    "quantity": {"N": "1"},
                    "customer_first_name": {"S": "jane"},
                    "customer_last_name": {"S": "doe"},
                    "customer_email": {"S": "jane.doe@gmail.com"},
                    "customer_phone_number": {"S": "555-555-5555"},
                    "customer_zip_code": {"S": "90210"},
                    "delivery_address": {"S": "123 Main St, Los Angeles, CA 90210"},
                    "scheduled_delivery_time": {"N": "1711108800"},
                    "order_total": {"N": "10.00"},
                    "order_status": {"S": "NEW"},
                    "order_date": {"N": "1711108800"},
                },
            ],
        },
        expected_params={"TableName": "orders"},
    )

    response = test_client.get("/v1/orders")

    pytest.helpers.assert_responses_equal(
        response,
        200,
        [
            {
                "order_id": "ORDER-1",
                "dessert_id": "DESSERT-1",
                "dessert_name": "Chocolate Cake",
                "quantity": 1,
                "customer_first_name": "jane",
                "customer_last_name": "doe",
                "customer_email": "jane.doe@gmail.com",
                "customer_phone_number": "555-555-5555",
                "customer_zip_code": "90210",
                "delivery_address": "123 Main St, Los Angeles, CA 90210",
                "scheduled_delivery_time": 1711108800,
                "order_total": 10.00,
                "order_status": "NEW",
                "order_date": 1711108800,
            }
        ],
    )

原因分析

  • 核心问题:路由加载时机早于补丁时机
    FastAPI在导入app实例时会立即加载所有注册的路由,此时/orders路由的依赖项Depends(admin_only)已经绑定了原始的admin_only函数对象。而测试代码中from src.api import app是在模块顶部执行的,早于测试函数的补丁操作。后续的patch只能修改get_orders模块中admin_only变量的引用,无法改变已经绑定到路由依赖中的原始函数对象。
  • 测试函数未接收patch生成的mock对象,虽不是补丁失效的主因,但不符合mock使用规范。

修复方案

方案一:延迟app导入,确保补丁生效后加载路由

修改测试文件,将app的导入移到测试函数内部,让补丁先生效再加载路由:

# 修改后的test_get_orders.py关键部分
@freeze_time("2024-03-22 12:00:00")
@patch("src.routes.get_orders.admin_only", return_value=True)
def test_handler_valid_event_get_orders(mock_admin_only, orders_dynamodb_stub):
    # 补丁生效后再导入app并创建TestClient
    from src.api import app
    test_client = TestClient(app, headers={"Authorization": "Bearer TOKEN"})
    
    # 后续测试逻辑不变
    orders_dynamodb_stub.add_response(...)
    response = test_client.get("/v1/orders")
    ...

同时删除模块顶部的from src.api import app和test_client定义。

方案二:修改导入方式,让依赖引用原模块函数

修改get_orders.py的导入方式,避免直接复制函数对象,转而引用原模块的函数:

# get_orders.py修改导入部分
import src.lib.authorization as authorization

# 路由依赖更新为
@router.get(
    "/orders",
    status_code=200,
    dependencies=[Depends(authorization.admin_only)],
)

然后测试时直接补丁原模块的函数:

# 测试函数的装饰器修改为
@patch("src.lib.authorization.admin_only", return_value=True)

这样补丁会直接修改原模块的admin_only函数,路由依赖引用的是原模块的函数,补丁即可生效。

内容的提问来源于stack exchange,提问作者Tony96

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 05:44:59