Repository: getmoto/moto (python)

Issue:
ServiceURL not matching when using apigatewaymanagementapi 
Python Version: 3.12.2
Moto Version: 5.0.13
OS: Mac OSX 14.5

Hi All!

I'm hoping I am just missing something instead of this being a bug. I am trying to use the `apigatewaymanagementapi` client with moto and I keep getting a error of `botocore.exceptions.ClientError: An error occurred (404) when calling the PostToConnection operation: Not yet implemented` 

I have done some digging into this and I think it is because I am using an `endpoint_url` in the client as per the documentation. _To use it, you must explicitly set the SDK’s endpoint to point to the endpoint of your deployed API. The endpoint will be of the form [https:/](https://boto3.amazonaws.com/)/{api-id}.execute-api.{region}.amazonaws.com/{stage}, or will be the endpoint corresponding to your API’s custom domain and base path, if applicable._ (https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/apigatewaymanagementapi.html)

The error is coming from BotocoreStubber because the the URL is not matching any of the service URLs. This is what it tried to run for `apigatewaymanagementapi` 

```
service: apigatewaymanagementapi 
regex: re.compile('https?://execute-api\\.(.+)\\.amazonaws\\.com') 
url: https://gldhhqzr.execute-api.us-east-1.amazonaws.com/@connections/123
```

I have looked at tests on the PR where apigatewaymanagementapi was introduced and can see they are not using the endpoint_url but when I don't it has no way to understanding the stage name for the api and does not work in real execution. 

I've looked through the docs and cannot see anything specially about how to run this any differently than any other moto calls.

The code being tested looks like this 

```python
client = boto3.client(
        "apigatewaymanagementapi",
        endpoint_url=f"https://{os.environ['API_ID']}.execute-api.{os.environ['REGION']}.amazonaws.com/{os.environ['STAGE']}",
    )
...
client.post_to_connection(
                    ConnectionId=connection["connectionId"]["S"],
                    Data=f"{json.dumps(deserialize_dynamodb_json(message))}",
                ) 
```


Pull request description:
APIGatewayManagement() - can now be called with an APIgw endpoint URL
Fixes #8008 

Patch:
diff --git a/moto/apigatewaymanagementapi/responses.py b/moto/apigatewaymanagementapi/responses.py
index 093fb81d9..49d45c359 100644
--- a/moto/apigatewaymanagementapi/responses.py
+++ b/moto/apigatewaymanagementapi/responses.py
@@ -3,7 +3,7 @@
 import json
 from typing import Any
 
-from moto.core.responses import BaseResponse
+from moto.core.responses import TYPE_RESPONSE, BaseResponse
 
 from .models import ApiGatewayManagementApiBackend, apigatewaymanagementapi_backends
 
@@ -46,3 +46,16 @@ class ApiGatewayManagementApiResponse(BaseResponse):
             connection_id=connection_id,
         )
         return "{}"
+
+    @staticmethod
+    def connect_to_apigateway(  # type: ignore[misc]
+        request: Any, full_url: str, headers: Any
+    ) -> TYPE_RESPONSE:
+        self = ApiGatewayManagementApiResponse()
+        self.setup_class(request, full_url, headers, use_raw_body=True)
+        if request.method == "GET":
+            return 200, {}, self.get_connection()
+        elif request.method == "DELETE":
+            return 200, {}, self.delete_connection()
+        else:
+            return 200, {}, self.post_to_connection()
diff --git a/moto/apigatewaymanagementapi/urls.py b/moto/apigatewaymanagementapi/urls.py
index f72047201..bbf32b52f 100644
--- a/moto/apigatewaymanagementapi/urls.py
+++ b/moto/apigatewaymanagementapi/urls.py
@@ -2,7 +2,9 @@
 
 from .responses import ApiGatewayManagementApiResponse
 
-url_bases = [r"https?://execute-api\.(.+)\.amazonaws\.com"]
+# execute-api.us-east-1.amazonaws.com
+# api_id.execute-api.us-east-1.amazonaws.com
+url_bases = [r"https?://(.+\.)*execute-api\.(.+)\.amazonaws\.com"]
 
 
 response = ApiGatewayManagementApiResponse()
@@ -10,4 +12,5 @@ response = ApiGatewayManagementApiResponse()
 
 url_paths = {
     "{0}/@connections/(?P<connection_id>[^/]+)$": response.dispatch,
+    "{0}/(?P<stage_name>.+/)+@connections/(?P<connection_id>[^/]+)$": response.connect_to_apigateway,
 }
diff --git a/moto/backend_index.py b/moto/backend_index.py
index 91f12fa51..b7d561b88 100644
--- a/moto/backend_index.py
+++ b/moto/backend_index.py
@@ -8,7 +8,7 @@ backend_url_patterns = [
     ("apigateway", re.compile("https?://apigateway\\.(.+)\\.amazonaws.com")),
     (
         "apigatewaymanagementapi",
-        re.compile("https?://execute-api\\.(.+)\\.amazonaws\\.com"),
+        re.compile("https?://(.+\\.)*execute-api\\.(.+)\\.amazonaws\\.com"),
     ),
     ("appconfig", re.compile("https?://appconfig\\.(.+)\\.amazonaws\\.com")),
     (
diff --git a/moto/ec2/models/elastic_network_interfaces.py b/moto/ec2/models/elastic_network_interfaces.py
index 9c12986d1..8b6b05437 100644
--- a/moto/ec2/models/elastic_network_interfaces.py
+++ b/moto/ec2/models/elastic_network_interfaces.py
@@ -133,9 +133,9 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
                         group_id,
                         group_id,
                         group_id,
-                        vpc_id=subnet.vpc_id,
+                        vpc_id=self.subnet.vpc_id,
                     )
-                    self.ec2_backend.groups[subnet.vpc_id][group_id] = group
+                    self.ec2_backend.groups[self.subnet.vpc_id][group_id] = group
                 if group:
                     self._group_set.append(group)
         if not group_ids:


Test patch:
diff --git a/tests/test_apigateway/test_apigateway_integration.py b/tests/test_apigateway/test_apigateway_integration.py
index 8c82ee3c1..8899827cd 100644
--- a/tests/test_apigateway/test_apigateway_integration.py
+++ b/tests/test_apigateway/test_apigateway_integration.py
@@ -112,7 +112,7 @@ def test_aws_integration_dynamodb_multiple_stages():
         f"https://{api_id}.execute-api.us-west-2.amazonaws.com/prod",
         json={"TableName": table_name, "Item": {"name": {"S": "the-key"}}},
     )
-    assert res.status_code == 400
+    assert res.status_code in [400, 404]
 
 
 @mock_aws
diff --git a/tests/test_apigatewaymanagementapi/test_apigatewaymanagementapi.py b/tests/test_apigatewaymanagementapi/test_apigatewaymanagementapi.py
index c461752f7..e1f3e0ec7 100644
--- a/tests/test_apigatewaymanagementapi/test_apigatewaymanagementapi.py
+++ b/tests/test_apigatewaymanagementapi/test_apigatewaymanagementapi.py
@@ -14,8 +14,9 @@ from tests import DEFAULT_ACCOUNT_ID
 @mock_aws
 def test_delete_connection():
     if settings.TEST_SERVER_MODE and not is_werkzeug_2_3_x():
-        # URL matching changed between 2.2.x and 2.3.x
-        # 2.3.x has no problem matching the root path '/@connections', but 2.2.x refuses
+        # URL matching changed in 2.2.x - the root path '/@connections' cannot be found
+        # 2.1.x works, 2.2.x is broken, and 2.3.x (and 3.x) works again
+        # We could only skip 2.2.x - but only supporting >= 2.3.x is easier
         raise SkipTest("Can't test this in older werkzeug versions")
     client = boto3.client("apigatewaymanagementapi", region_name="eu-west-1")
     # NO-OP
@@ -54,3 +55,17 @@ def test_post_to_connection():
     if not settings.TEST_SERVER_MODE:
         backend = apigatewaymanagementapi_backends[DEFAULT_ACCOUNT_ID]["ap-southeast-1"]
         assert backend.connections["anything"].data == b"my first bytesmore bytes"
+
+
+@mock_aws
+def test_post_to_connection_using_endpoint_url():
+    if not settings.TEST_DECORATOR_MODE:
+        raise SkipTest("Can only test this with decorators")
+    client = boto3.client(
+        "apigatewaymanagementapi",
+        "us-west-2",
+        endpoint_url="https://someapiid.execute-api.us-west-2.amazonaws.com/stage",
+    )
+
+    client.get_connection(ConnectionId="anything")
+    client.post_to_connection(ConnectionId="n/a", Data=b"some data")
diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py
index eb3ea0e13..0aaec121d 100644
--- a/tests/test_ecs/test_ecs_boto3.py
+++ b/tests/test_ecs/test_ecs_boto3.py
@@ -2093,6 +2093,43 @@ def test_run_task_awsvpc_network():
     assert {"name": "macAddress", "value": eni["MacAddress"]} in details
 
 
+@mock_aws
+def test_run_task_awsvpc_network__unknown_sec_group():
+    # Setup
+    client = boto3.client("ecs", region_name=ECS_REGION)
+    ec2_client = boto3.client("ec2", region_name=ECS_REGION)
+    ec2 = boto3.resource("ec2", region_name=ECS_REGION)
+
+    # ECS setup
+    setup_resources = setup_ecs(client, ec2)
+
+    # Execute
+    task = client.run_task(
+        cluster="test_ecs_cluster",
+        overrides={},
+        taskDefinition="test_ecs_task",
+        startedBy="moto",
+        launchType="FARGATE",
+        networkConfiguration={
+            "awsvpcConfiguration": {
+                "subnets": [setup_resources[0].id],
+                "securityGroups": ["unknown_sg"],
+            }
+        },
+    )["tasks"][0]
+    eni_id = next(
+        t["value"]
+        for t in task["attachments"][0]["details"]
+        if t["name"] == "networkInterfaceId"
+    )
+
+    eni = ec2_client.describe_network_interfaces(NetworkInterfaceIds=[eni_id])[
+        "NetworkInterfaces"
+    ][0]
+    # Unknown SecurityGroup is created for us
+    assert eni["Groups"] == [{"GroupId": "unknown_sg", "GroupName": "unknown_sg"}]
+
+
 @mock_aws
 def test_run_task_awsvpc_network_error():
     # Setup