1212
1313# consts.model stubs used by northbound_service
1414consts_mod = types .ModuleType ("consts" )
15+ consts_mod .__path__ = [] # Mark as namespace package so that submodule imports work
1516consts_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
1821class LimitExceededError (Exception ):
19- pass
22+ """Raised when the rate limit or similar guard is violated."""
2023
2124
2225class 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
2639class AgentRequest :
@@ -34,10 +47,10 @@ def __init__(self, conversation_id: int, agent_id: int, query: str, history: Any
3447
3548
3649consts_model_mod .AgentRequest = AgentRequest
37- consts_model_mod .LimitExceededError = LimitExceededError
38- consts_model_mod .UnauthorizedError = UnauthorizedError
3950sys .modules ['consts' ] = consts_mod
51+ # Register stubs
4052sys .modules ['consts.model' ] = consts_model_mod
53+ sys .modules ['consts.exceptions' ] = consts_exceptions_mod
4154
4255# database.* stubs
4356database_mod = types .ModuleType ('database' )
@@ -145,8 +158,8 @@ async def test_to_external_and_internal_conversation_id_success():
145158@pytest .mark .asyncio
146159async 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():
230243async 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
238251async 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