@@ -682,3 +682,94 @@ async def app_impl(scope, receive, send):
682682 await _make_asgi_call (middleware , scope )
683683
684684 mock_token_service .log_token_usage .assert_not_awaited ()
685+
686+
687+ @pytest .mark .asyncio
688+ async def test_skips_rejected_request_without_bearer_header ():
689+ """Middleware skips logging when a 401/403 response has no Bearer authorization header."""
690+ app = AsyncMock ()
691+
692+ async def app_impl (scope , receive , send ):
693+ await send ({"type" : "http.response.start" , "status" : 401 , "headers" : []})
694+ await send ({"type" : "http.response.body" , "body" : b"unauthorized" })
695+
696+ app .side_effect = app_impl
697+ middleware = TokenUsageMiddleware (app = app )
698+
699+ scope = {
700+ "type" : "http" ,
701+ "path" : "/api/tools" ,
702+ "method" : "GET" ,
703+ "state" : {},
704+ "client" : ("10.0.0.1" , 9000 ),
705+ "headers" : [], # No authorization header
706+ }
707+
708+ with patch ("mcpgateway.middleware.token_usage_middleware.fresh_db_session" ) as mock_session :
709+ await _make_asgi_call (middleware , scope )
710+
711+ mock_session .assert_not_called ()
712+
713+
714+ @pytest .mark .asyncio
715+ async def test_skips_rejected_non_api_token_jwt ():
716+ """Middleware skips logging when a 401/403 response carries a JWT that is not an API token."""
717+ # Standard
718+ import jwt as _jwt_lib
719+
720+ token_payload = {
721+ "jti" : "jti-jwt-session" ,
722+ "sub" : "user@example.com" ,
723+ "user" : {"auth_provider" : "email" }, # Not an API token
724+ }
725+ raw_token = _jwt_lib .encode (token_payload , "test-secret-key-for-unit-tests-only" , algorithm = "HS256" )
726+
727+ app = AsyncMock ()
728+
729+ async def app_impl (scope , receive , send ):
730+ await send ({"type" : "http.response.start" , "status" : 403 , "headers" : []})
731+ await send ({"type" : "http.response.body" , "body" : b"forbidden" })
732+
733+ app .side_effect = app_impl
734+ middleware = TokenUsageMiddleware (app = app )
735+
736+ scope = {
737+ "type" : "http" ,
738+ "path" : "/api/tools" ,
739+ "method" : "GET" ,
740+ "state" : {},
741+ "client" : ("10.0.0.1" , 9000 ),
742+ "headers" : [(b"authorization" , f"Bearer { raw_token } " .encode ())],
743+ }
744+
745+ with patch ("mcpgateway.middleware.token_usage_middleware.fresh_db_session" ) as mock_session :
746+ await _make_asgi_call (middleware , scope )
747+
748+ mock_session .assert_not_called ()
749+
750+
751+ @pytest .mark .asyncio
752+ async def test_skips_rejected_request_with_malformed_token ():
753+ """Middleware skips logging when a 401/403 response carries a malformed token that cannot be decoded."""
754+ app = AsyncMock ()
755+
756+ async def app_impl (scope , receive , send ):
757+ await send ({"type" : "http.response.start" , "status" : 401 , "headers" : []})
758+ await send ({"type" : "http.response.body" , "body" : b"unauthorized" })
759+
760+ app .side_effect = app_impl
761+ middleware = TokenUsageMiddleware (app = app )
762+
763+ scope = {
764+ "type" : "http" ,
765+ "path" : "/api/tools" ,
766+ "method" : "GET" ,
767+ "state" : {},
768+ "client" : ("10.0.0.1" , 9000 ),
769+ "headers" : [(b"authorization" , b"Bearer not-a-valid-jwt-at-all" )],
770+ }
771+
772+ with patch ("mcpgateway.middleware.token_usage_middleware.fresh_db_session" ) as mock_session :
773+ await _make_asgi_call (middleware , scope )
774+
775+ mock_session .assert_not_called ()
0 commit comments