Skip to content

Commit 784f1c0

Browse files
committed
🐛 Fix typo error
🧪 Fix test_auth_utils.py failed only in GitHub workflow
1 parent df968ea commit 784f1c0

File tree

11 files changed

+116
-39
lines changed

11 files changed

+116
-39
lines changed

backend/apps/northbound_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from fastapi import APIRouter, Body, Header, Request
66

7-
from consts.model import UnauthorizedError
7+
from consts.exceptions import UnauthorizedError
88
from services.northbound_service import (
99
NorthboundContext,
1010
get_conversation_history,

backend/apps/northbound_base_app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
from fastapi.middleware.cors import CORSMiddleware
55

66
from .northbound_app import router as northbound_router
7-
from consts.model import LimitExceededError, UnauthorizedError, SignatureValidationError
7+
from consts.exceptions import LimitExceededError, UnauthorizedError, SignatureValidationError
88

99
logger = logging.getLogger("northbound_base_app")
1010

1111

1212
northbound_app = FastAPI(
1313
title="Nexent Northbound API",
14-
description="北向接口 - 面向合作伙伴的外部API",
14+
description="Northbound APIs for partners",
1515
version="1.0.0",
1616
root_path="/api"
1717
)

backend/apps/user_management_app.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from consts.const import SUPABASE_URL, SUPABASE_KEY
1010
from consts.model import STATUS_CODES, ServiceResponse, UserSignUpRequest, UserSignInRequest
1111
from database.model_management_db import create_model_record
12-
from utils.auth_utils import get_jwt_expiry_seconds, calculate_expires_at
12+
from utils.auth_utils import get_jwt_expiry_seconds, calculate_expires_at, get_current_user_id
1313
from database.user_tenant_db import insert_user_tenant
1414
from utils.config_utils import config_manager
1515

@@ -550,7 +550,6 @@ async def get_user_id(request: Request):
550550

551551
# If the token is invalid, try to parse the user ID from the token
552552
try:
553-
from utils.auth_utils import get_current_user_id
554553
user_id, _ = get_current_user_id(authorization)
555554
if user_id:
556555
logging.info(f"Successfully parsed user ID from token: {user_id}")

backend/consts/exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Northbound customize error class
2+
class LimitExceededError(Exception):
3+
pass
4+
5+
class UnauthorizedError(Exception):
6+
pass
7+
8+
class SignatureValidationError(Exception):
9+
"""Raised when X-Signature header is missing or does not match the expected HMAC value."""
10+
pass

backend/consts/model.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -379,15 +379,3 @@ class MemoryAgentShareMode(str, Enum):
379379
@classmethod
380380
def default(cls) -> "MemoryAgentShareMode":
381381
return cls.NEVER
382-
383-
# Northbound customize error class
384-
class LimitExceededError(Exception):
385-
pass
386-
387-
class UnauthorizedError(Exception):
388-
pass
389-
390-
# Custom error raised when HMAC signature validation fails in northbound requests
391-
class SignatureValidationError(Exception):
392-
"""Raised when X-Signature header is missing or does not match the expected HMAC value."""
393-
pass

backend/services/northbound_service.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
from fastapi.responses import StreamingResponse
1010

1111
from database.conversation_db import get_conversation_messages
12-
from consts.model import (
13-
AgentRequest,
12+
from consts.model import AgentRequest
13+
from consts.exceptions import (
1414
LimitExceededError,
1515
UnauthorizedError,
1616
)
@@ -122,7 +122,7 @@ async def to_external_conversation_id(internal_id: int) -> str:
122122
raise Exception("invalid internal conversation id")
123123
external_id = get_external_id_by_internal(internal_id=internal_id, mapping_type="CONVERSATION")
124124
if not external_id:
125-
raise Exception(f"cannot find external id for conversation_id: {internal_id}")
125+
logger.warning(f"cannot find external id for conversation_id: {internal_id}")
126126
return external_id
127127

128128

@@ -197,7 +197,7 @@ async def start_streaming_chat(
197197
)
198198
finally:
199199
if composed_key:
200-
asyncio.create_task(_release_idempotency_after_delay(composed_key, 3))
200+
asyncio.create_task(_release_idempotency_after_delay(composed_key))
201201

202202
# Attach request id header
203203
response.headers["X-Request-Id"] = ctx.request_id
@@ -219,10 +219,7 @@ async def list_conversations(ctx: NorthboundContext) -> Dict[str, Any]:
219219
conversations = get_conversation_list_service(ctx.user_id)
220220
# get_conversation_list_service is sync
221221
for item in conversations:
222-
try:
223-
item["conversation_id"] = await to_external_conversation_id(int(item["conversation_id"]))
224-
except Exception:
225-
pass
222+
item["conversation_id"] = await to_external_conversation_id(int(item["conversation_id"]))
226223
return {"message": "success", "data": conversations, "requestId": ctx.request_id}
227224

228225

@@ -280,4 +277,4 @@ async def update_conversation_title(ctx: NorthboundContext, external_conversatio
280277
raise Exception(f"Failed to update conversation title for external conversation id {external_conversation_id}: {str(e)}")
281278
finally:
282279
if composed_key:
283-
asyncio.create_task(_release_idempotency_after_delay(composed_key, 3))
280+
asyncio.create_task(_release_idempotency_after_delay(composed_key))

backend/utils/auth_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from fastapi import Request, HTTPException
1010
from consts.const import DEFAULT_USER_ID, DEFAULT_TENANT_ID, IS_SPEED_MODE
11-
from consts.model import SignatureValidationError, UnauthorizedError
11+
from consts.exceptions import SignatureValidationError, UnauthorizedError
1212
import jwt
1313
from supabase import create_client
1414
from database.user_tenant_db import get_user_tenant_by_user_id

test/backend/app/test_northbound_app.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,42 @@
1616
# Pre-mock heavy dependencies before importing router
1717
sys.modules['consts'] = MagicMock()
1818
sys.modules['consts.model'] = MagicMock()
19+
# Provide stub for consts.exceptions with expected exception classes
20+
# so that imports in application code succeed during tests.
21+
# We intentionally use real classes (not MagicMock) so that isinstance checks work if present.
22+
23+
import types
24+
25+
consts_exceptions_mod = types.ModuleType("consts.exceptions")
26+
27+
28+
class LimitExceededError(Exception):
29+
pass
30+
31+
32+
class UnauthorizedError(Exception):
33+
pass
34+
35+
36+
class SignatureValidationError(Exception):
37+
pass
38+
39+
40+
consts_exceptions_mod.LimitExceededError = LimitExceededError
41+
consts_exceptions_mod.UnauthorizedError = UnauthorizedError
42+
consts_exceptions_mod.SignatureValidationError = SignatureValidationError
43+
44+
# Ensure the parent 'consts' is a module (could be MagicMock) and register submodule.
45+
import sys as _sys
46+
if 'consts' not in _sys.modules or not isinstance(_sys.modules['consts'], types.ModuleType):
47+
consts_root = types.ModuleType("consts")
48+
consts_root.__path__ = []
49+
_sys.modules['consts'] = consts_root
50+
else:
51+
consts_root = _sys.modules['consts']
52+
53+
consts_root.exceptions = consts_exceptions_mod
54+
_sys.modules['consts.exceptions'] = consts_exceptions_mod
1955
sys.modules['services'] = MagicMock()
2056
sys.modules['services.northbound_service'] = MagicMock()
2157
sys.modules['utils'] = MagicMock()

test/backend/app/test_northbound_base_app.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ class SignatureValidationError(Exception):
6767
consts_module.model = consts_model_module
6868
sys.modules['consts'] = consts_module
6969
sys.modules['consts.model'] = consts_model_module
70+
# ---------------------------------------------------------------------------
71+
# Provide 'consts.exceptions' stub so that northbound_base_app import succeeds
72+
# ---------------------------------------------------------------------------
73+
consts_exceptions_module = types.ModuleType("consts.exceptions")
74+
consts_exceptions_module.LimitExceededError = LimitExceededError
75+
consts_exceptions_module.UnauthorizedError = UnauthorizedError
76+
consts_exceptions_module.SignatureValidationError = SignatureValidationError
77+
78+
# Register the stub so that `from consts.exceptions import ...` works seamlessly
79+
sys.modules['consts.exceptions'] = consts_exceptions_module
7080

7181
# ---------------------------------------------------------------------------
7282
# SAFE TO IMPORT THE TARGET MODULE UNDER TEST NOW

test/backend/services/test_northbound_service.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,28 @@
1212

1313
# consts.model stubs used by northbound_service
1414
consts_mod = types.ModuleType("consts")
15+
consts_mod.__path__ = [] # Mark as namespace package so that submodule imports work
1516
consts_model_mod = types.ModuleType("consts.model")
17+
consts_exceptions_mod = types.ModuleType("consts.exceptions")
1618

1719

20+
# Define the custom exception classes expected by northbound_service
1821
class LimitExceededError(Exception):
19-
pass
22+
"""Raised when the rate limit or similar guard is violated."""
2023

2124

2225
class UnauthorizedError(Exception):
23-
pass
26+
"""Raised when authentication or authorization fails."""
27+
28+
29+
class SignatureValidationError(Exception):
30+
"""Raised when request signature header is missing or invalid."""
31+
32+
33+
# Attach them to the stub module so that `from consts.exceptions import ...` works
34+
consts_exceptions_mod.LimitExceededError = LimitExceededError
35+
consts_exceptions_mod.UnauthorizedError = UnauthorizedError
36+
consts_exceptions_mod.SignatureValidationError = SignatureValidationError
2437

2538

2639
class AgentRequest:
@@ -34,10 +47,10 @@ def __init__(self, conversation_id: int, agent_id: int, query: str, history: Any
3447

3548

3649
consts_model_mod.AgentRequest = AgentRequest
37-
consts_model_mod.LimitExceededError = LimitExceededError
38-
consts_model_mod.UnauthorizedError = UnauthorizedError
3950
sys.modules['consts'] = consts_mod
51+
# Register stubs
4052
sys.modules['consts.model'] = consts_model_mod
53+
sys.modules['consts.exceptions'] = consts_exceptions_mod
4154

4255
# database.* stubs
4356
database_mod = types.ModuleType('database')
@@ -145,8 +158,8 @@ async def test_to_external_and_internal_conversation_id_success():
145158
@pytest.mark.asyncio
146159
async def test_to_external_conversation_id_not_found():
147160
partner_db_mod.get_external_id_by_internal.return_value = None
148-
with pytest.raises(Exception):
149-
await ns.to_external_conversation_id(123)
161+
result = await ns.to_external_conversation_id(123)
162+
assert result is None
150163

151164

152165
@pytest.mark.asyncio
@@ -230,14 +243,14 @@ async def _agen():
230243
async def test_rate_limit_exceeded(monkeypatch):
231244
monkeypatch.setattr(ns, "_RATE_LIMIT_PER_MINUTE", 1)
232245
await ns.check_and_consume_rate_limit("tenant-x")
233-
with pytest.raises(LimitExceededError):
246+
with pytest.raises(consts_exceptions_mod.LimitExceededError):
234247
await ns.check_and_consume_rate_limit("tenant-x")
235248

236249

237250
@pytest.mark.asyncio
238251
async def test_idempotency_prevents_duplicates():
239252
await ns.idempotency_start("dup-key")
240-
with pytest.raises(LimitExceededError):
253+
with pytest.raises(consts_exceptions_mod.LimitExceededError):
241254
await ns.idempotency_start("dup-key")
242255
await ns.idempotency_end("dup-key")
243256

@@ -298,7 +311,7 @@ async def test_update_conversation_title_success_and_idempotency(ctx):
298311
assert res["message"] == "success"
299312
assert res["data"] == "ext-10"
300313
# duplicate should raise until released
301-
with pytest.raises(LimitExceededError):
314+
with pytest.raises(consts_exceptions_mod.LimitExceededError):
302315
await ns.update_conversation_title(ctx, "ext-10", "Title", idempotency_key="title-key")
303316
# cleanup manually to avoid bleed between tests
304317
await ns.idempotency_end("title-key")

0 commit comments

Comments
 (0)