Skip to content

Commit 3e85ce7

Browse files
whummerclaude
andcommitted
add proxy tests for the API Gateway v2 service
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0330959 commit 3e85ce7

File tree

1 file changed

+282
-0
lines changed

1 file changed

+282
-0
lines changed
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
# Note/disclosure: This file has been (partially or fully) generated by an AI agent.
2+
import logging
3+
4+
import boto3
5+
import pytest
6+
from botocore.exceptions import ClientError
7+
from localstack.aws.connect import connect_to
8+
from localstack.utils.strings import short_uid
9+
10+
from aws_proxy.shared.models import ProxyConfig
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
def test_apigatewayv2_http_api_requests(start_aws_proxy, cleanups):
16+
"""Test basic API Gateway v2 HTTP API operations with proxy."""
17+
api_name_aws = f"test-http-api-{short_uid()}"
18+
19+
# start proxy - forwarding all API Gateway v2 requests
20+
config = ProxyConfig(services={"apigatewayv2": {"resources": ".*"}})
21+
start_aws_proxy(config)
22+
23+
# create clients
24+
region_name = "us-east-1"
25+
apigwv2_client = connect_to(region_name=region_name).apigatewayv2
26+
apigwv2_client_aws = boto3.client("apigatewayv2", region_name=region_name)
27+
28+
# create HTTP API in AWS
29+
create_response_aws = apigwv2_client_aws.create_api(
30+
Name=api_name_aws, ProtocolType="HTTP", Description="Test HTTP API for proxy"
31+
)
32+
api_id_aws = create_response_aws["ApiId"]
33+
cleanups.append(lambda: apigwv2_client_aws.delete_api(ApiId=api_id_aws))
34+
35+
# assert that local call for this API is proxied
36+
get_api_local = apigwv2_client.get_api(ApiId=api_id_aws)
37+
get_api_aws = apigwv2_client_aws.get_api(ApiId=api_id_aws)
38+
assert get_api_local["Name"] == get_api_aws["Name"] == api_name_aws
39+
assert get_api_local["ApiId"] == get_api_aws["ApiId"] == api_id_aws
40+
41+
# negative test: verify that requesting a non-existent API fails
42+
with pytest.raises(ClientError) as ctx:
43+
apigwv2_client_aws.get_api(ApiId="nonexistent123")
44+
assert ctx.value.response["Error"]["Code"] == "NotFoundException"
45+
46+
# list APIs from AWS should include the created API
47+
apis_aws = apigwv2_client_aws.get_apis()
48+
aws_api_ids = [api["ApiId"] for api in apis_aws.get("Items", [])]
49+
assert api_id_aws in aws_api_ids
50+
51+
# update API description via LocalStack client (should proxy to AWS)
52+
updated_description = "Updated description via proxy"
53+
apigwv2_client.update_api(ApiId=api_id_aws, Description=updated_description)
54+
55+
# verify update is reflected in AWS
56+
get_api_aws = apigwv2_client_aws.get_api(ApiId=api_id_aws)
57+
assert get_api_aws["Description"] == updated_description
58+
59+
60+
def test_apigatewayv2_routes_and_integrations(start_aws_proxy, cleanups):
61+
"""Test API Gateway v2 routes and integrations with proxy."""
62+
api_name_aws = f"test-http-api-routes-{short_uid()}"
63+
64+
# start proxy - forwarding all API Gateway v2 requests
65+
config = ProxyConfig(services={"apigatewayv2": {"resources": ".*"}})
66+
start_aws_proxy(config)
67+
68+
# create clients
69+
region_name = "us-east-1"
70+
apigwv2_client = connect_to(region_name=region_name).apigatewayv2
71+
apigwv2_client_aws = boto3.client("apigatewayv2", region_name=region_name)
72+
73+
# create HTTP API in AWS
74+
create_response_aws = apigwv2_client_aws.create_api(
75+
Name=api_name_aws, ProtocolType="HTTP"
76+
)
77+
api_id_aws = create_response_aws["ApiId"]
78+
cleanups.append(lambda: apigwv2_client_aws.delete_api(ApiId=api_id_aws))
79+
80+
# create integration via AWS client
81+
integration_response = apigwv2_client_aws.create_integration(
82+
ApiId=api_id_aws,
83+
IntegrationType="HTTP_PROXY",
84+
IntegrationMethod="GET",
85+
IntegrationUri="https://httpbin.org/get",
86+
PayloadFormatVersion="1.0",
87+
)
88+
integration_id = integration_response["IntegrationId"]
89+
90+
# verify integration via LocalStack (proxied)
91+
integration_local = apigwv2_client.get_integration(
92+
ApiId=api_id_aws, IntegrationId=integration_id
93+
)
94+
assert integration_local["IntegrationType"] == "HTTP_PROXY"
95+
assert integration_local["IntegrationId"] == integration_id
96+
97+
# create route via AWS client
98+
route_response = apigwv2_client_aws.create_route(
99+
ApiId=api_id_aws,
100+
RouteKey="GET /users",
101+
Target=f"integrations/{integration_id}",
102+
)
103+
route_id = route_response["RouteId"]
104+
105+
# verify route via LocalStack (proxied)
106+
route_local = apigwv2_client.get_route(ApiId=api_id_aws, RouteId=route_id)
107+
assert route_local["RouteKey"] == "GET /users"
108+
assert route_local["RouteId"] == route_id
109+
110+
# list routes via LocalStack (should proxy to AWS)
111+
routes_local = apigwv2_client.get_routes(ApiId=api_id_aws)
112+
route_ids_local = [r["RouteId"] for r in routes_local.get("Items", [])]
113+
assert route_id in route_ids_local
114+
115+
# delete route via AWS client
116+
apigwv2_client_aws.delete_route(ApiId=api_id_aws, RouteId=route_id)
117+
118+
# verify route is deleted via LocalStack (proxied)
119+
with pytest.raises(ClientError) as ctx:
120+
apigwv2_client.get_route(ApiId=api_id_aws, RouteId=route_id)
121+
assert ctx.value.response["Error"]["Code"] == "NotFoundException"
122+
123+
124+
def test_apigatewayv2_stages_and_deployments(start_aws_proxy, cleanups):
125+
"""Test API Gateway v2 stages and deployments with proxy."""
126+
api_name_aws = f"test-http-api-deploy-{short_uid()}"
127+
128+
# start proxy - forwarding all API Gateway v2 requests
129+
config = ProxyConfig(services={"apigatewayv2": {"resources": ".*"}})
130+
start_aws_proxy(config)
131+
132+
# create clients
133+
region_name = "us-east-1"
134+
apigwv2_client = connect_to(region_name=region_name).apigatewayv2
135+
apigwv2_client_aws = boto3.client("apigatewayv2", region_name=region_name)
136+
137+
# create HTTP API in AWS
138+
create_response_aws = apigwv2_client_aws.create_api(
139+
Name=api_name_aws, ProtocolType="HTTP"
140+
)
141+
api_id_aws = create_response_aws["ApiId"]
142+
cleanups.append(lambda: apigwv2_client_aws.delete_api(ApiId=api_id_aws))
143+
144+
# create a simple integration and route (required for deployment)
145+
integration_response = apigwv2_client_aws.create_integration(
146+
ApiId=api_id_aws,
147+
IntegrationType="HTTP_PROXY",
148+
IntegrationMethod="GET",
149+
IntegrationUri="https://httpbin.org/get",
150+
PayloadFormatVersion="1.0",
151+
)
152+
integration_id = integration_response["IntegrationId"]
153+
154+
apigwv2_client_aws.create_route(
155+
ApiId=api_id_aws,
156+
RouteKey="GET /test",
157+
Target=f"integrations/{integration_id}",
158+
)
159+
160+
# create stage via LocalStack client (should proxy to AWS)
161+
stage_name = "test"
162+
apigwv2_client.create_stage(
163+
ApiId=api_id_aws, StageName=stage_name, Description="Test stage"
164+
)
165+
166+
# verify stage exists in AWS
167+
stage_aws = apigwv2_client_aws.get_stage(ApiId=api_id_aws, StageName=stage_name)
168+
assert stage_aws["StageName"] == stage_name
169+
assert stage_aws["Description"] == "Test stage"
170+
171+
# get stage via LocalStack client
172+
stage_local = apigwv2_client.get_stage(ApiId=api_id_aws, StageName=stage_name)
173+
assert stage_local["StageName"] == stage_aws["StageName"]
174+
175+
# create deployment via AWS client
176+
deployment_response = apigwv2_client_aws.create_deployment(
177+
ApiId=api_id_aws, StageName=stage_name, Description="Test deployment"
178+
)
179+
deployment_id = deployment_response["DeploymentId"]
180+
181+
# verify deployment via LocalStack (proxied)
182+
deployment_local = apigwv2_client.get_deployment(
183+
ApiId=api_id_aws, DeploymentId=deployment_id
184+
)
185+
assert deployment_local["DeploymentId"] == deployment_id
186+
187+
# list stages via AWS client
188+
stages_aws = apigwv2_client_aws.get_stages(ApiId=api_id_aws)
189+
stage_names_aws = [s["StageName"] for s in stages_aws.get("Items", [])]
190+
assert stage_name in stage_names_aws
191+
192+
193+
def test_apigatewayv2_read_only_mode(start_aws_proxy, cleanups):
194+
"""Test API Gateway v2 operations in read-only proxy mode."""
195+
api_name_aws = f"test-http-api-readonly-{short_uid()}"
196+
197+
# create HTTP API in AWS first (before starting proxy)
198+
region_name = "us-east-1"
199+
apigwv2_client_aws = boto3.client("apigatewayv2", region_name=region_name)
200+
create_response_aws = apigwv2_client_aws.create_api(
201+
Name=api_name_aws, ProtocolType="HTTP"
202+
)
203+
api_id_aws = create_response_aws["ApiId"]
204+
cleanups.append(lambda: apigwv2_client_aws.delete_api(ApiId=api_id_aws))
205+
206+
# start proxy in read-only mode
207+
config = ProxyConfig(
208+
services={"apigatewayv2": {"resources": ".*", "read_only": True}}
209+
)
210+
start_aws_proxy(config)
211+
212+
# create LocalStack client
213+
apigwv2_client = connect_to(region_name=region_name).apigatewayv2
214+
215+
# read operations should work (proxied to AWS)
216+
get_api_local = apigwv2_client.get_api(ApiId=api_id_aws)
217+
assert get_api_local["Name"] == api_name_aws
218+
assert get_api_local["ApiId"] == api_id_aws
219+
220+
# verify the API can also be read directly from AWS
221+
get_api_aws = apigwv2_client_aws.get_api(ApiId=api_id_aws)
222+
assert get_api_local["Name"] == get_api_aws["Name"]
223+
224+
# write operations should not be proxied in read-only mode
225+
original_description = get_api_aws.get("Description", "")
226+
227+
# Attempt write operation - should either be blocked or not proxied
228+
try:
229+
apigwv2_client.update_api(
230+
ApiId=api_id_aws, Description="Should not reach AWS"
231+
)
232+
except Exception as e:
233+
logger.info(f"Read-only mode blocked write operation: {e}")
234+
235+
# Verify the API description was not changed in AWS
236+
get_api_aws_after = apigwv2_client_aws.get_api(ApiId=api_id_aws)
237+
assert get_api_aws_after.get("Description", "") == original_description
238+
239+
240+
def test_apigatewayv2_websocket_api(start_aws_proxy, cleanups):
241+
"""Test API Gateway v2 WebSocket API operations with proxy."""
242+
api_name_aws = f"test-websocket-api-{short_uid()}"
243+
244+
# start proxy - forwarding all API Gateway v2 requests
245+
config = ProxyConfig(services={"apigatewayv2": {"resources": ".*"}})
246+
start_aws_proxy(config)
247+
248+
# create clients
249+
region_name = "us-east-1"
250+
apigwv2_client = connect_to(region_name=region_name).apigatewayv2
251+
apigwv2_client_aws = boto3.client("apigatewayv2", region_name=region_name)
252+
253+
# create WebSocket API in AWS
254+
create_response_aws = apigwv2_client_aws.create_api(
255+
Name=api_name_aws,
256+
ProtocolType="WEBSOCKET",
257+
RouteSelectionExpression="$request.body.action",
258+
)
259+
api_id_aws = create_response_aws["ApiId"]
260+
cleanups.append(lambda: apigwv2_client_aws.delete_api(ApiId=api_id_aws))
261+
262+
# assert that local call for this API is proxied
263+
get_api_local = apigwv2_client.get_api(ApiId=api_id_aws)
264+
get_api_aws = apigwv2_client_aws.get_api(ApiId=api_id_aws)
265+
assert get_api_local["Name"] == get_api_aws["Name"] == api_name_aws
266+
assert get_api_local["ProtocolType"] == "WEBSOCKET"
267+
268+
# create a route for WebSocket API via AWS client
269+
route_response = apigwv2_client_aws.create_route(
270+
ApiId=api_id_aws, RouteKey="$connect"
271+
)
272+
route_id = route_response["RouteId"]
273+
274+
# verify route via LocalStack (proxied)
275+
route_local = apigwv2_client.get_route(ApiId=api_id_aws, RouteId=route_id)
276+
assert route_local["RouteKey"] == "$connect"
277+
assert route_local["RouteId"] == route_id
278+
279+
# list routes should include the $connect route
280+
routes_local = apigwv2_client.get_routes(ApiId=api_id_aws)
281+
route_keys = [r["RouteKey"] for r in routes_local.get("Items", [])]
282+
assert "$connect" in route_keys

0 commit comments

Comments
 (0)