@@ -1030,6 +1030,98 @@ async def test_reusing_operation_id_does_not_count_against_limit(
10301030 await ws .close ()
10311031
10321032
1033+ async def test_completed_subscriptions_do_not_count_against_limit (
1034+ http_client_class : type [HttpClient ],
1035+ ):
1036+ """Subscriptions that complete on their own must release their slot,
1037+ otherwise a client could hit the limit with operations that are no longer
1038+ active."""
1039+ test_client = http_client_class (schema , max_subscriptions_per_connection = 2 )
1040+
1041+ async with test_client .ws_connect (
1042+ "/graphql" , protocols = [GRAPHQL_WS_PROTOCOL ]
1043+ ) as ws :
1044+ await ws .send_legacy_message ({"type" : "connection_init" })
1045+ response : ConnectionAckMessage = await ws .receive_json ()
1046+ assert response ["type" ] == "connection_ack"
1047+
1048+ # Each of these completes on its own after a single result, so the
1049+ # third one must not be rejected even though the limit is 2.
1050+ for operation_id in ("sub1" , "sub2" , "sub3" ):
1051+ await ws .send_legacy_message (
1052+ {
1053+ "type" : "start" ,
1054+ "id" : operation_id ,
1055+ "payload" : {
1056+ "query" : 'subscription { echo(message: "Hi") }' ,
1057+ },
1058+ }
1059+ )
1060+
1061+ data_message : DataMessage = await ws .receive_json ()
1062+ assert data_message ["type" ] == "data"
1063+ assert data_message ["id" ] == operation_id
1064+ assert data_message ["payload" ]["data" ] == {"echo" : "Hi" }
1065+
1066+ complete_message : CompleteMessage = await ws .receive_json ()
1067+ assert complete_message ["type" ] == "complete"
1068+ assert complete_message ["id" ] == operation_id
1069+
1070+ # Stopping an operation that already completed is a no-op and must
1071+ # not affect the connection.
1072+ await ws .send_legacy_message ({"type" : "stop" , "id" : "sub1" })
1073+
1074+ await ws .send_legacy_message (
1075+ {
1076+ "type" : "start" ,
1077+ "id" : "sub4" ,
1078+ "payload" : {
1079+ "query" : 'subscription { echo(message: "Hi") }' ,
1080+ },
1081+ }
1082+ )
1083+ data_message = await ws .receive_json ()
1084+ assert data_message ["type" ] == "data"
1085+ assert data_message ["id" ] == "sub4"
1086+
1087+ complete_message = await ws .receive_json ()
1088+ assert complete_message ["type" ] == "complete"
1089+ assert complete_message ["id" ] == "sub4"
1090+
1091+ await ws .close ()
1092+
1093+
1094+ async def test_failed_subscriptions_do_not_count_against_limit (
1095+ http_client_class : type [HttpClient ],
1096+ ):
1097+ """Operations that fail before execution (e.g. validation errors) must
1098+ release their slot as well."""
1099+ test_client = http_client_class (schema , max_subscriptions_per_connection = 2 )
1100+
1101+ async with test_client .ws_connect (
1102+ "/graphql" , protocols = [GRAPHQL_WS_PROTOCOL ]
1103+ ) as ws :
1104+ await ws .send_legacy_message ({"type" : "connection_init" })
1105+ response : ConnectionAckMessage = await ws .receive_json ()
1106+ assert response ["type" ] == "connection_ack"
1107+
1108+ for operation_id in ("sub1" , "sub2" , "sub3" ):
1109+ await ws .send_legacy_message (
1110+ {
1111+ "type" : "start" ,
1112+ "id" : operation_id ,
1113+ "payload" : {"query" : "subscription { doesNotExist }" },
1114+ }
1115+ )
1116+
1117+ error_message : ErrorMessage = await ws .receive_json ()
1118+ assert error_message ["type" ] == "error"
1119+ assert error_message ["id" ] == operation_id
1120+ assert error_message ["payload" ] != {"message" : "Subscription limit reached" }
1121+
1122+ await ws .close ()
1123+
1124+
10331125async def test_max_subscriptions_per_connection_disabled (
10341126 http_client_class : type [HttpClient ],
10351127):
0 commit comments