Skip to content

Commit 0330959

Browse files
whummerclaude
andcommitted
add proxy tests for the API Gateway service
Tests cover: - REST API requests (create, get, update) - Resources and methods (get_method, get_method_response, get_integration) - Deployments and stages - Read-only mode - Selective resource matching and error handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 75c4d82 commit 0330959

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
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_apigateway_rest_api_requests(start_aws_proxy, cleanups):
16+
api_name_aws = f"test-api-aws-{short_uid()}"
17+
18+
# start proxy - forwarding all API Gateway requests
19+
config = ProxyConfig(services={"apigateway": {"resources": ".*"}})
20+
start_aws_proxy(config)
21+
22+
# create clients
23+
region_name = "us-east-1"
24+
apigw_client = connect_to(region_name=region_name).apigateway
25+
apigw_client_aws = boto3.client("apigateway", region_name=region_name)
26+
27+
# create REST API in AWS
28+
create_response_aws = apigw_client_aws.create_rest_api(
29+
name=api_name_aws, description="Test API for proxy testing"
30+
)
31+
api_id_aws = create_response_aws["id"]
32+
cleanups.append(lambda: apigw_client_aws.delete_rest_api(restApiId=api_id_aws))
33+
34+
# assert that local call for this API is proxied
35+
get_api_local = apigw_client.get_rest_api(restApiId=api_id_aws)
36+
get_api_aws = apigw_client_aws.get_rest_api(restApiId=api_id_aws)
37+
assert get_api_local["name"] == get_api_aws["name"] == api_name_aws
38+
assert get_api_local["id"] == get_api_aws["id"] == api_id_aws
39+
40+
# negative test: verify that requesting a non-existent API fails
41+
with pytest.raises(ClientError) as ctx:
42+
apigw_client_aws.get_rest_api(restApiId="nonexistent123")
43+
assert ctx.value.response["Error"]["Code"] == "NotFoundException"
44+
45+
# list APIs from AWS should include the created API
46+
apis_aws = apigw_client_aws.get_rest_apis()
47+
aws_api_ids = [api["id"] for api in apis_aws.get("items", [])]
48+
assert api_id_aws in aws_api_ids
49+
50+
# update API description via LocalStack client (should proxy to AWS)
51+
updated_description = "Updated description via proxy"
52+
apigw_client.update_rest_api(
53+
restApiId=api_id_aws,
54+
patchOperations=[
55+
{"op": "replace", "path": "/description", "value": updated_description}
56+
],
57+
)
58+
59+
# verify update is reflected in AWS
60+
get_api_aws = apigw_client_aws.get_rest_api(restApiId=api_id_aws)
61+
assert get_api_aws["description"] == updated_description
62+
63+
64+
def test_apigateway_resources_and_methods(start_aws_proxy, cleanups):
65+
api_name_aws = f"test-api-resources-{short_uid()}"
66+
67+
# start proxy - forwarding all API Gateway requests
68+
config = ProxyConfig(services={"apigateway": {"resources": ".*"}})
69+
start_aws_proxy(config)
70+
71+
# create clients
72+
region_name = "us-east-1"
73+
apigw_client = connect_to(region_name=region_name).apigateway
74+
apigw_client_aws = boto3.client("apigateway", region_name=region_name)
75+
76+
# create REST API in AWS
77+
create_response_aws = apigw_client_aws.create_rest_api(name=api_name_aws)
78+
api_id_aws = create_response_aws["id"]
79+
cleanups.append(lambda: apigw_client_aws.delete_rest_api(restApiId=api_id_aws))
80+
81+
# get root resource from AWS
82+
resources_response = apigw_client_aws.get_resources(restApiId=api_id_aws)
83+
root_resource_id = resources_response["items"][0]["id"]
84+
85+
# create a new resource via AWS client
86+
resource_response = apigw_client_aws.create_resource(
87+
restApiId=api_id_aws, parentId=root_resource_id, pathPart="users"
88+
)
89+
resource_id = resource_response["id"]
90+
91+
# create a method via AWS client
92+
apigw_client_aws.put_method(
93+
restApiId=api_id_aws,
94+
resourceId=resource_id,
95+
httpMethod="GET",
96+
authorizationType="NONE",
97+
)
98+
99+
# verify method exists via LocalStack client (should proxy to AWS)
100+
method_local = apigw_client.get_method(
101+
restApiId=api_id_aws, resourceId=resource_id, httpMethod="GET"
102+
)
103+
assert method_local["httpMethod"] == "GET"
104+
assert method_local["authorizationType"] == "NONE"
105+
106+
# create method response via AWS client
107+
apigw_client_aws.put_method_response(
108+
restApiId=api_id_aws,
109+
resourceId=resource_id,
110+
httpMethod="GET",
111+
statusCode="200",
112+
)
113+
114+
# verify method response via LocalStack (proxied)
115+
method_response_local = apigw_client.get_method_response(
116+
restApiId=api_id_aws, resourceId=resource_id, httpMethod="GET", statusCode="200"
117+
)
118+
assert method_response_local["statusCode"] == "200"
119+
120+
# create integration via AWS client
121+
apigw_client_aws.put_integration(
122+
restApiId=api_id_aws,
123+
resourceId=resource_id,
124+
httpMethod="GET",
125+
type="MOCK",
126+
requestTemplates={"application/json": '{"statusCode": 200}'},
127+
)
128+
129+
# verify integration via LocalStack (proxied)
130+
integration_local = apigw_client.get_integration(
131+
restApiId=api_id_aws, resourceId=resource_id, httpMethod="GET"
132+
)
133+
assert integration_local["type"] == "MOCK"
134+
135+
# delete method via AWS client
136+
apigw_client_aws.delete_method(
137+
restApiId=api_id_aws, resourceId=resource_id, httpMethod="GET"
138+
)
139+
140+
# verify method is deleted via LocalStack (proxied)
141+
with pytest.raises(ClientError) as ctx:
142+
apigw_client.get_method(
143+
restApiId=api_id_aws, resourceId=resource_id, httpMethod="GET"
144+
)
145+
assert ctx.value.response["Error"]["Code"] in ["NotFoundException", "404"]
146+
147+
148+
def test_apigateway_deployments(start_aws_proxy, cleanups):
149+
api_name_aws = f"test-api-deploy-{short_uid()}"
150+
151+
# start proxy - forwarding all API Gateway requests
152+
config = ProxyConfig(services={"apigateway": {"resources": ".*"}})
153+
start_aws_proxy(config)
154+
155+
# create clients
156+
region_name = "us-east-1"
157+
apigw_client = connect_to(region_name=region_name).apigateway
158+
apigw_client_aws = boto3.client("apigateway", region_name=region_name)
159+
160+
# create REST API in AWS
161+
create_response_aws = apigw_client_aws.create_rest_api(name=api_name_aws)
162+
api_id_aws = create_response_aws["id"]
163+
cleanups.append(lambda: apigw_client_aws.delete_rest_api(restApiId=api_id_aws))
164+
165+
# get root resource and create a simple method
166+
resources_response = apigw_client_aws.get_resources(restApiId=api_id_aws)
167+
root_resource_id = resources_response["items"][0]["id"]
168+
169+
apigw_client_aws.put_method(
170+
restApiId=api_id_aws,
171+
resourceId=root_resource_id,
172+
httpMethod="GET",
173+
authorizationType="NONE",
174+
)
175+
apigw_client_aws.put_integration(
176+
restApiId=api_id_aws,
177+
resourceId=root_resource_id,
178+
httpMethod="GET",
179+
type="MOCK",
180+
)
181+
182+
# create deployment via LocalStack client (should proxy to AWS)
183+
stage_name = "test"
184+
deployment_response = apigw_client.create_deployment(
185+
restApiId=api_id_aws, stageName=stage_name, description="Test deployment"
186+
)
187+
deployment_id = deployment_response["id"]
188+
189+
# verify deployment exists in AWS
190+
deployment_aws = apigw_client_aws.get_deployment(
191+
restApiId=api_id_aws, deploymentId=deployment_id
192+
)
193+
assert deployment_aws["id"] == deployment_id
194+
assert deployment_aws["description"] == "Test deployment"
195+
196+
# get stage via LocalStack client
197+
stage_local = apigw_client.get_stage(restApiId=api_id_aws, stageName=stage_name)
198+
stage_aws = apigw_client_aws.get_stage(restApiId=api_id_aws, stageName=stage_name)
199+
assert stage_local["stageName"] == stage_aws["stageName"] == stage_name
200+
assert stage_local["deploymentId"] == stage_aws["deploymentId"] == deployment_id
201+
202+
# list deployments via AWS client (verify deployment exists)
203+
deployments_aws = apigw_client_aws.get_deployments(restApiId=api_id_aws)
204+
aws_deployment_ids = [d["id"] for d in deployments_aws.get("items", [])]
205+
assert deployment_id in aws_deployment_ids
206+
207+
208+
def test_apigateway_read_only_mode(start_aws_proxy, cleanups):
209+
api_name_aws = f"test-api-readonly-{short_uid()}"
210+
211+
# create REST API in AWS first (before starting proxy)
212+
region_name = "us-east-1"
213+
apigw_client_aws = boto3.client("apigateway", region_name=region_name)
214+
create_response_aws = apigw_client_aws.create_rest_api(name=api_name_aws)
215+
api_id_aws = create_response_aws["id"]
216+
cleanups.append(lambda: apigw_client_aws.delete_rest_api(restApiId=api_id_aws))
217+
218+
# start proxy in read-only mode
219+
config = ProxyConfig(
220+
services={"apigateway": {"resources": ".*", "read_only": True}}
221+
)
222+
start_aws_proxy(config)
223+
224+
# create LocalStack client
225+
apigw_client = connect_to(region_name=region_name).apigateway
226+
227+
# read operations should work (proxied to AWS)
228+
get_api_local = apigw_client.get_rest_api(restApiId=api_id_aws)
229+
assert get_api_local["name"] == api_name_aws
230+
assert get_api_local["id"] == api_id_aws
231+
232+
# verify the API can also be read directly from AWS
233+
get_api_aws = apigw_client_aws.get_rest_api(restApiId=api_id_aws)
234+
assert get_api_local["name"] == get_api_aws["name"]
235+
236+
# write operations should fail (not allowed in read-only mode)
237+
# In read-only mode, the API exists in AWS but LocalStack should not
238+
# allow write operations to be proxied
239+
original_description = get_api_aws.get("description", "")
240+
241+
# Attempt write operation - should either be blocked or not proxied
242+
try:
243+
apigw_client.update_rest_api(
244+
restApiId=api_id_aws,
245+
patchOperations=[
246+
{
247+
"op": "replace",
248+
"path": "/description",
249+
"value": "Should not reach AWS",
250+
}
251+
],
252+
)
253+
except Exception as e:
254+
logger.info(f"Read-only mode blocked write operation: {e}")
255+
256+
# Verify the API description was not changed in AWS
257+
get_api_aws_after = apigw_client_aws.get_rest_api(restApiId=api_id_aws)
258+
assert get_api_aws_after.get("description", "") == original_description
259+
260+
261+
def test_apigateway_selective_resource_matching(start_aws_proxy, cleanups):
262+
api_name_aws = f"test-api-selective-{short_uid()}"
263+
264+
# start proxy - forwarding all API Gateway requests
265+
config = ProxyConfig(services={"apigateway": {"resources": ".*"}})
266+
start_aws_proxy(config)
267+
268+
# create clients
269+
region_name = "us-east-1"
270+
apigw_client = connect_to(region_name=region_name).apigateway
271+
apigw_client_aws = boto3.client("apigateway", region_name=region_name)
272+
273+
# create API in AWS
274+
create_response_aws = apigw_client_aws.create_rest_api(name=api_name_aws)
275+
api_id_aws = create_response_aws["id"]
276+
cleanups.append(lambda: apigw_client_aws.delete_rest_api(restApiId=api_id_aws))
277+
278+
# accessing the API via LocalStack should be proxied
279+
get_api_local = apigw_client.get_rest_api(restApiId=api_id_aws)
280+
assert get_api_local["name"] == api_name_aws
281+
282+
# verify the API details match between LocalStack and AWS
283+
get_api_aws = apigw_client_aws.get_rest_api(restApiId=api_id_aws)
284+
assert get_api_local["id"] == get_api_aws["id"]
285+
assert get_api_local["name"] == get_api_aws["name"]
286+
287+
# negative test: verify that requesting a non-existent API ID fails
288+
with pytest.raises(ClientError) as ctx:
289+
apigw_client_aws.get_rest_api(restApiId="nonexistent123456")
290+
assert ctx.value.response["Error"]["Code"] == "NotFoundException"
291+
292+
# verify LocalStack also returns error for non-existent API
293+
with pytest.raises(ClientError) as ctx:
294+
apigw_client.get_rest_api(restApiId="nonexistent123456")
295+
assert ctx.value.response["Error"]["Code"] in ["NotFoundException", "404"]

0 commit comments

Comments
 (0)