APIGatewayV2 mappings (#5711)
This commit is contained in:
parent
97b5e8b3ab
commit
22539585e7
@ -175,13 +175,13 @@
|
||||
|
||||
## apigatewayv2
|
||||
<details>
|
||||
<summary>58% implemented</summary>
|
||||
<summary>69% implemented</summary>
|
||||
|
||||
- [X] create_api
|
||||
- [ ] create_api_mapping
|
||||
- [X] create_api_mapping
|
||||
- [X] create_authorizer
|
||||
- [ ] create_deployment
|
||||
- [ ] create_domain_name
|
||||
- [X] create_domain_name
|
||||
- [X] create_integration
|
||||
- [X] create_integration_response
|
||||
- [X] create_model
|
||||
@ -191,11 +191,11 @@
|
||||
- [X] create_vpc_link
|
||||
- [ ] delete_access_log_settings
|
||||
- [X] delete_api
|
||||
- [ ] delete_api_mapping
|
||||
- [X] delete_api_mapping
|
||||
- [X] delete_authorizer
|
||||
- [X] delete_cors_configuration
|
||||
- [ ] delete_deployment
|
||||
- [ ] delete_domain_name
|
||||
- [X] delete_domain_name
|
||||
- [X] delete_integration
|
||||
- [X] delete_integration_response
|
||||
- [X] delete_model
|
||||
@ -207,15 +207,15 @@
|
||||
- [X] delete_vpc_link
|
||||
- [ ] export_api
|
||||
- [X] get_api
|
||||
- [ ] get_api_mapping
|
||||
- [ ] get_api_mappings
|
||||
- [X] get_api_mapping
|
||||
- [X] get_api_mappings
|
||||
- [X] get_apis
|
||||
- [X] get_authorizer
|
||||
- [ ] get_authorizers
|
||||
- [ ] get_deployment
|
||||
- [ ] get_deployments
|
||||
- [ ] get_domain_name
|
||||
- [ ] get_domain_names
|
||||
- [X] get_domain_name
|
||||
- [X] get_domain_names
|
||||
- [X] get_integration
|
||||
- [X] get_integration_response
|
||||
- [X] get_integration_responses
|
||||
|
@ -33,10 +33,10 @@ apigatewayv2
|
||||
CredentialsArn, RouteKey, Tags, Target
|
||||
|
||||
|
||||
- [ ] create_api_mapping
|
||||
- [X] create_api_mapping
|
||||
- [X] create_authorizer
|
||||
- [ ] create_deployment
|
||||
- [ ] create_domain_name
|
||||
- [X] create_domain_name
|
||||
- [X] create_integration
|
||||
- [X] create_integration_response
|
||||
- [X] create_model
|
||||
@ -50,11 +50,11 @@ apigatewayv2
|
||||
- [X] create_vpc_link
|
||||
- [ ] delete_access_log_settings
|
||||
- [X] delete_api
|
||||
- [ ] delete_api_mapping
|
||||
- [X] delete_api_mapping
|
||||
- [X] delete_authorizer
|
||||
- [X] delete_cors_configuration
|
||||
- [ ] delete_deployment
|
||||
- [ ] delete_domain_name
|
||||
- [X] delete_domain_name
|
||||
- [X] delete_integration
|
||||
- [X] delete_integration_response
|
||||
- [X] delete_model
|
||||
@ -66,8 +66,8 @@ apigatewayv2
|
||||
- [X] delete_vpc_link
|
||||
- [ ] export_api
|
||||
- [X] get_api
|
||||
- [ ] get_api_mapping
|
||||
- [ ] get_api_mappings
|
||||
- [X] get_api_mapping
|
||||
- [X] get_api_mappings
|
||||
- [X] get_apis
|
||||
|
||||
Pagination is not yet implemented
|
||||
@ -77,8 +77,12 @@ apigatewayv2
|
||||
- [ ] get_authorizers
|
||||
- [ ] get_deployment
|
||||
- [ ] get_deployments
|
||||
- [ ] get_domain_name
|
||||
- [ ] get_domain_names
|
||||
- [X] get_domain_name
|
||||
- [X] get_domain_names
|
||||
|
||||
Pagination is not yet implemented
|
||||
|
||||
|
||||
- [X] get_integration
|
||||
- [X] get_integration_response
|
||||
- [X] get_integration_responses
|
||||
|
@ -93,3 +93,33 @@ class UnknownProtocol(APIGatewayV2Error):
|
||||
"BadRequestException",
|
||||
"Invalid protocol specified. Must be one of [HTTP, WEBSOCKET]",
|
||||
)
|
||||
|
||||
|
||||
class DomainNameNotFound(APIGatewayV2Error):
|
||||
code = 404
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
"NotFoundException",
|
||||
"The domain name resource specified in the request was not found.",
|
||||
)
|
||||
|
||||
|
||||
class DomainNameAlreadyExists(APIGatewayV2Error):
|
||||
code = 409
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
"ConflictException",
|
||||
"The domain name resource already exists.",
|
||||
)
|
||||
|
||||
|
||||
class ApiMappingNotFound(APIGatewayV2Error):
|
||||
code = 404
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
"NotFoundException",
|
||||
"The api mapping resource specified in the request was not found.",
|
||||
)
|
||||
|
@ -1,7 +1,8 @@
|
||||
"""ApiGatewayV2Backend class with methods for supported APIs."""
|
||||
import hashlib
|
||||
import string
|
||||
import yaml
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from moto.core import BaseBackend, BackendDict, BaseModel
|
||||
from moto.core.utils import unix_time
|
||||
@ -9,6 +10,7 @@ from moto.moto_api._internal import mock_random as random
|
||||
from moto.utilities.tagging_service import TaggingService
|
||||
|
||||
from .exceptions import (
|
||||
ApiMappingNotFound,
|
||||
ApiNotFound,
|
||||
AuthorizerNotFound,
|
||||
BadRequestException,
|
||||
@ -18,6 +20,8 @@ from .exceptions import (
|
||||
IntegrationResponseNotFound,
|
||||
RouteNotFound,
|
||||
VpcLinkNotFound,
|
||||
DomainNameNotFound,
|
||||
DomainNameAlreadyExists,
|
||||
)
|
||||
|
||||
|
||||
@ -1011,6 +1015,55 @@ class VpcLink(BaseModel):
|
||||
}
|
||||
|
||||
|
||||
class DomainName(BaseModel):
|
||||
def __init__(
|
||||
self,
|
||||
domain_name: str,
|
||||
domain_name_configurations: List[Dict[str, str]],
|
||||
mutual_tls_authentication: Dict[str, str],
|
||||
tags: Dict[str, str],
|
||||
):
|
||||
self.api_mapping_selection_expression = "$request.basepath"
|
||||
self.domain_name = domain_name
|
||||
self.domain_name_configurations = domain_name_configurations
|
||||
self.mutual_tls_authentication = mutual_tls_authentication
|
||||
self.tags = tags
|
||||
|
||||
def to_json(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"apiMappingSelectionExpression": self.api_mapping_selection_expression,
|
||||
"domainName": self.domain_name,
|
||||
"domainNameConfigurations": self.domain_name_configurations,
|
||||
"mutualTlsAuthentication": self.mutual_tls_authentication,
|
||||
"tags": self.tags,
|
||||
}
|
||||
|
||||
|
||||
class ApiMapping(BaseModel):
|
||||
def __init__(
|
||||
self,
|
||||
api_id: str,
|
||||
api_mapping_key: str,
|
||||
api_mapping_id: str,
|
||||
domain_name: str,
|
||||
stage: str,
|
||||
) -> None:
|
||||
self.api_id = api_id
|
||||
self.api_mapping_key = api_mapping_key
|
||||
self.api_mapping_id = api_mapping_id
|
||||
self.domain_name = domain_name
|
||||
self.stage = stage
|
||||
|
||||
def to_json(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"apiId": self.api_id,
|
||||
"apiMappingId": self.api_mapping_id,
|
||||
"apiMappingKey": self.api_mapping_key,
|
||||
"domainName": self.domain_name,
|
||||
"stage": self.stage,
|
||||
}
|
||||
|
||||
|
||||
class ApiGatewayV2Backend(BaseBackend):
|
||||
"""Implementation of ApiGatewayV2 APIs."""
|
||||
|
||||
@ -1018,6 +1071,8 @@ class ApiGatewayV2Backend(BaseBackend):
|
||||
super().__init__(region_name, account_id)
|
||||
self.apis: Dict[str, Api] = dict()
|
||||
self.vpc_links: Dict[str, VpcLink] = dict()
|
||||
self.domain_names: Dict[str, DomainName] = dict()
|
||||
self.api_mappings: Dict[str, ApiMapping] = dict()
|
||||
self.tagger = TaggingService()
|
||||
|
||||
def create_api(
|
||||
@ -1537,5 +1592,114 @@ class ApiGatewayV2Backend(BaseBackend):
|
||||
vpc_link.update(name)
|
||||
return vpc_link
|
||||
|
||||
def create_domain_name(
|
||||
self,
|
||||
domain_name: str,
|
||||
domain_name_configurations: List[Dict[str, str]],
|
||||
mutual_tls_authentication: Dict[str, str],
|
||||
tags: Dict[str, str],
|
||||
) -> DomainName:
|
||||
|
||||
if domain_name in self.domain_names.keys():
|
||||
raise DomainNameAlreadyExists
|
||||
|
||||
domain = DomainName(
|
||||
domain_name=domain_name,
|
||||
domain_name_configurations=domain_name_configurations,
|
||||
mutual_tls_authentication=mutual_tls_authentication,
|
||||
tags=tags,
|
||||
)
|
||||
self.domain_names[domain.domain_name] = domain
|
||||
return domain
|
||||
|
||||
def get_domain_name(self, domain_name: Union[str, None]) -> DomainName:
|
||||
if domain_name is None or domain_name not in self.domain_names:
|
||||
raise DomainNameNotFound
|
||||
return self.domain_names[domain_name]
|
||||
|
||||
def get_domain_names(self) -> List[DomainName]:
|
||||
"""
|
||||
Pagination is not yet implemented
|
||||
"""
|
||||
return list(self.domain_names.values())
|
||||
|
||||
def delete_domain_name(self, domain_name: str) -> None:
|
||||
if domain_name not in self.domain_names.keys():
|
||||
raise DomainNameNotFound
|
||||
|
||||
for mapping_id, mapping in self.api_mappings.items():
|
||||
if mapping.domain_name == domain_name:
|
||||
del self.api_mappings[mapping_id]
|
||||
|
||||
del self.domain_names[domain_name]
|
||||
|
||||
def _generate_api_maping_id(
|
||||
self, api_mapping_key: str, stage: str, domain_name: str
|
||||
) -> str:
|
||||
return str(
|
||||
hashlib.sha256(
|
||||
f"{stage} {domain_name}/{api_mapping_key}".encode("utf-8")
|
||||
).hexdigest()
|
||||
)[:5]
|
||||
|
||||
def create_api_mapping(
|
||||
self, api_id: str, api_mapping_key: str, domain_name: str, stage: str
|
||||
) -> ApiMapping:
|
||||
if domain_name not in self.domain_names.keys():
|
||||
raise DomainNameNotFound
|
||||
|
||||
if api_id not in self.apis.keys():
|
||||
raise ApiNotFound("The resource specified in the request was not found.")
|
||||
|
||||
if api_mapping_key.startswith("/") or "//" in api_mapping_key:
|
||||
raise BadRequestException(
|
||||
"API mapping key should not start with a '/' or have consecutive '/'s."
|
||||
)
|
||||
|
||||
if api_mapping_key.endswith("/"):
|
||||
raise BadRequestException("API mapping key should not end with a '/'.")
|
||||
|
||||
api_mapping_id = self._generate_api_maping_id(
|
||||
api_mapping_key=api_mapping_key, stage=stage, domain_name=domain_name
|
||||
)
|
||||
|
||||
mapping = ApiMapping(
|
||||
domain_name=domain_name,
|
||||
api_id=api_id,
|
||||
api_mapping_key=api_mapping_key,
|
||||
api_mapping_id=api_mapping_id,
|
||||
stage=stage,
|
||||
)
|
||||
|
||||
self.api_mappings[api_mapping_id] = mapping
|
||||
return mapping
|
||||
|
||||
def get_api_mapping(self, api_mapping_id: str, domain_name: str) -> ApiMapping:
|
||||
if domain_name not in self.domain_names.keys():
|
||||
raise DomainNameNotFound
|
||||
|
||||
if api_mapping_id not in self.api_mappings.keys():
|
||||
raise ApiMappingNotFound
|
||||
|
||||
return self.api_mappings[api_mapping_id]
|
||||
|
||||
def get_api_mappings(self, domain_name: str) -> List[ApiMapping]:
|
||||
domain_mappings = []
|
||||
for mapping in self.api_mappings.values():
|
||||
if mapping.domain_name == domain_name:
|
||||
domain_mappings.append(mapping)
|
||||
return domain_mappings
|
||||
|
||||
def delete_api_mapping(self, api_mapping_id: str, domain_name: str) -> None:
|
||||
if api_mapping_id not in self.api_mappings.keys():
|
||||
raise ApiMappingNotFound
|
||||
|
||||
if self.api_mappings[api_mapping_id].domain_name != domain_name:
|
||||
raise BadRequestException(
|
||||
f"given domain name {domain_name} does not match with mapping definition of mapping {api_mapping_id}"
|
||||
)
|
||||
|
||||
del self.api_mappings[api_mapping_id]
|
||||
|
||||
|
||||
apigatewayv2_backends = BackendDict(ApiGatewayV2Backend, "apigatewayv2")
|
||||
|
@ -180,6 +180,38 @@ class ApiGatewayV2Response(BaseResponse):
|
||||
if request.method == "POST":
|
||||
return self.create_vpc_link()
|
||||
|
||||
def domain_names(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: # type: ignore[return]
|
||||
self.setup_class(request, full_url, headers)
|
||||
|
||||
if request.method == "GET":
|
||||
return self.get_domain_names()
|
||||
if request.method == "POST":
|
||||
return self.create_domain_name()
|
||||
|
||||
def domain_name(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: # type: ignore[return]
|
||||
self.setup_class(request, full_url, headers)
|
||||
|
||||
if request.method == "GET":
|
||||
return self.get_domain_name()
|
||||
if request.method == "DELETE":
|
||||
return self.delete_domain_name()
|
||||
|
||||
def api_mappings(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: # type: ignore[return]
|
||||
self.setup_class(request, full_url, headers)
|
||||
|
||||
if request.method == "GET":
|
||||
return self.get_api_mappings()
|
||||
if request.method == "POST":
|
||||
return self.create_api_mapping()
|
||||
|
||||
def api_mapping(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: # type: ignore[return]
|
||||
self.setup_class(request, full_url, headers)
|
||||
|
||||
if request.method == "GET":
|
||||
return self.get_api_mapping()
|
||||
if request.method == "DELETE":
|
||||
return self.delete_api_mapping()
|
||||
|
||||
def create_api(self) -> TYPE_RESPONSE:
|
||||
params = json.loads(self.body)
|
||||
|
||||
@ -751,3 +783,74 @@ class ApiGatewayV2Response(BaseResponse):
|
||||
|
||||
vpc_link = self.apigatewayv2_backend.update_vpc_link(vpc_link_id, name=name)
|
||||
return 200, {}, json.dumps(vpc_link.to_json())
|
||||
|
||||
def create_domain_name(self) -> TYPE_RESPONSE:
|
||||
params = json.loads(self.body)
|
||||
domain_name = params.get("domainName")
|
||||
domain_name_configurations = params.get("domainNameConfigurations", [{}])
|
||||
mutual_tls_authentication = params.get("mutualTlsAuthentication", {})
|
||||
tags = params.get("tags", {})
|
||||
domain_name = self.apigatewayv2_backend.create_domain_name(
|
||||
domain_name=domain_name,
|
||||
domain_name_configurations=domain_name_configurations,
|
||||
mutual_tls_authentication=mutual_tls_authentication,
|
||||
tags=tags,
|
||||
)
|
||||
return 201, {}, json.dumps(domain_name.to_json())
|
||||
|
||||
def get_domain_name(self) -> TYPE_RESPONSE:
|
||||
domain_name_param = self.path.split("/")[-1]
|
||||
domain_name = self.apigatewayv2_backend.get_domain_name(
|
||||
domain_name=domain_name_param
|
||||
)
|
||||
return 200, {}, json.dumps(domain_name.to_json())
|
||||
|
||||
def get_domain_names(self) -> TYPE_RESPONSE:
|
||||
domain_names = self.apigatewayv2_backend.get_domain_names()
|
||||
list_of_dict = [domain_name.to_json() for domain_name in domain_names]
|
||||
return 200, {}, json.dumps(dict(items=list_of_dict))
|
||||
|
||||
def create_api_mapping(self) -> TYPE_RESPONSE:
|
||||
domain_name = self.path.split("/")[-2]
|
||||
params = json.loads(self.body)
|
||||
api_id = params.get("apiId")
|
||||
api_mapping_key = params.get("apiMappingKey", "")
|
||||
stage = params.get("stage")
|
||||
mapping = self.apigatewayv2_backend.create_api_mapping(
|
||||
api_id=api_id,
|
||||
api_mapping_key=api_mapping_key,
|
||||
domain_name=domain_name,
|
||||
stage=stage,
|
||||
)
|
||||
return 201, {}, json.dumps(mapping.to_json())
|
||||
|
||||
def get_api_mapping(self) -> TYPE_RESPONSE:
|
||||
api_mapping_id = self.path.split("/")[-1]
|
||||
domain_name = self.path.split("/")[-3]
|
||||
mapping = self.apigatewayv2_backend.get_api_mapping(
|
||||
api_mapping_id=api_mapping_id,
|
||||
domain_name=domain_name,
|
||||
)
|
||||
return 200, {}, json.dumps(mapping.to_json())
|
||||
|
||||
def get_api_mappings(self) -> TYPE_RESPONSE:
|
||||
domain_name = self.path.split("/")[-2]
|
||||
mappings = self.apigatewayv2_backend.get_api_mappings(domain_name=domain_name)
|
||||
list_of_dict = [mapping.to_json() for mapping in mappings]
|
||||
return 200, {}, json.dumps(dict(items=list_of_dict))
|
||||
|
||||
def delete_domain_name(self) -> TYPE_RESPONSE:
|
||||
domain_name = self.path.split("/")[-1]
|
||||
self.apigatewayv2_backend.delete_domain_name(
|
||||
domain_name=domain_name,
|
||||
)
|
||||
return 204, {}, ""
|
||||
|
||||
def delete_api_mapping(self) -> TYPE_RESPONSE:
|
||||
api_mapping_id = self.path.split("/")[-1]
|
||||
domain_name = self.path.split("/")[-3]
|
||||
self.apigatewayv2_backend.delete_api_mapping(
|
||||
api_mapping_id=api_mapping_id,
|
||||
domain_name=domain_name,
|
||||
)
|
||||
return 204, {}, ""
|
||||
|
@ -31,4 +31,8 @@ url_paths = {
|
||||
"{0}/v2/tags/(?P<resource_arn_pt1>[^/]+)/vpclinks/(?P<resource_arn_pt2>[^/]+)$": response_v2.tags,
|
||||
"{0}/v2/vpclinks$": response_v2.vpc_links,
|
||||
"{0}/v2/vpclinks/(?P<vpc_link_id>[^/]+)$": response_v2.vpc_link,
|
||||
"{0}/v2/domainnames$": response_v2.domain_names,
|
||||
"{0}/v2/domainnames/(?P<domain_name>[^/]+)$": response_v2.domain_name,
|
||||
"{0}/v2/domainnames/(?P<domain_name>[^/]+)/apimappings$": response_v2.api_mappings,
|
||||
"{0}/v2/domainnames/(?P<domain_name>[^/]+)/apimappings/(?P<api_mapping_id>[^/]+)$": response_v2.api_mapping,
|
||||
}
|
||||
|
96
tests/test_apigatewayv2/test_apigatewayv2_domains.py
Normal file
96
tests/test_apigatewayv2/test_apigatewayv2_domains.py
Normal file
@ -0,0 +1,96 @@
|
||||
import boto3
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
import pytest
|
||||
import botocore.exceptions
|
||||
from moto import mock_apigatewayv2
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_create_domain_name():
|
||||
client = boto3.client("apigatewayv2", region_name="us-east-1")
|
||||
domain_name = "dev"
|
||||
tags = {"tag": "it"}
|
||||
expected_keys = [
|
||||
"DomainName",
|
||||
"ApiMappingSelectionExpression",
|
||||
"DomainNameConfigurations",
|
||||
"MutualTlsAuthentication",
|
||||
"Tags",
|
||||
]
|
||||
|
||||
post_resp = client.create_domain_name(DomainName=domain_name, Tags=tags)
|
||||
get_resp = client.get_domain_name(DomainName=domain_name)
|
||||
|
||||
# check post response has all keys
|
||||
for key in expected_keys:
|
||||
post_resp.should.have.key(key)
|
||||
|
||||
# check get response has all keys
|
||||
for key in expected_keys:
|
||||
get_resp.should.have.key(key)
|
||||
|
||||
# check that values are equal for post and get of same resource
|
||||
for key in expected_keys:
|
||||
post_resp.get(key).should.equal(get_resp.get(key))
|
||||
|
||||
# ensure known values are set correct in post response
|
||||
post_resp.get("DomainName").should.equal(domain_name)
|
||||
post_resp.get("Tags").should.equal(tags)
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_create_domain_name_already_exists():
|
||||
client = boto3.client("apigatewayv2", region_name="us-east-1")
|
||||
client.create_domain_name(DomainName="exists.io")
|
||||
|
||||
with pytest.raises(botocore.exceptions.ClientError) as exc:
|
||||
client.create_domain_name(DomainName="exists.io")
|
||||
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("ConflictException")
|
||||
err["Message"].should.equal("The domain name resource already exists.")
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_get_domain_names():
|
||||
client = boto3.client("apigatewayv2", region_name="us-east-1")
|
||||
dev_domain = client.create_domain_name(DomainName="dev.service.io")
|
||||
prod_domain = client.create_domain_name(DomainName="prod.service.io")
|
||||
|
||||
# sanity check responses
|
||||
dev_domain.should.have.key("DomainName").equals("dev.service.io")
|
||||
prod_domain.should.have.key("DomainName").equals("prod.service.io")
|
||||
|
||||
# make comparable
|
||||
del dev_domain["ResponseMetadata"]
|
||||
del prod_domain["ResponseMetadata"]
|
||||
|
||||
get_resp = client.get_domain_names()
|
||||
get_resp.should.have.key("Items")
|
||||
get_resp.get("Items").should.contain(dev_domain)
|
||||
get_resp.get("Items").should.contain(prod_domain)
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_delete_domain_name():
|
||||
client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
|
||||
post_resp = client.create_domain_name(DomainName="dev.service.io")
|
||||
client.delete_domain_name(DomainName="dev.service.io")
|
||||
get_resp = client.get_domain_names()
|
||||
|
||||
del post_resp["ResponseMetadata"]
|
||||
get_resp.should.have.key("Items")
|
||||
get_resp.get("Items").should_not.contain(post_resp)
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_delete_domain_name_dne():
|
||||
client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
|
||||
with pytest.raises(botocore.exceptions.ClientError) as exc:
|
||||
client.delete_domain_name(DomainName="dne.io")
|
||||
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("NotFoundException")
|
||||
err["Message"].should.equal(
|
||||
"The domain name resource specified in the request was not found."
|
||||
)
|
195
tests/test_apigatewayv2/test_apigatewayv2_mappings.py
Normal file
195
tests/test_apigatewayv2/test_apigatewayv2_mappings.py
Normal file
@ -0,0 +1,195 @@
|
||||
import boto3
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
import pytest
|
||||
import botocore.exceptions
|
||||
from moto import mock_apigatewayv2
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_create_api_mapping():
|
||||
client = boto3.client("apigatewayv2", region_name="us-east-1")
|
||||
api = client.create_api(Name="test-api", ProtocolType="HTTP")
|
||||
dev_domain = client.create_domain_name(DomainName="dev.service.io")
|
||||
expected_keys = ["ApiId", "ApiMappingId", "ApiMappingKey", "Stage"]
|
||||
|
||||
post_resp = client.create_api_mapping(
|
||||
DomainName=dev_domain["DomainName"],
|
||||
ApiMappingKey="v1/api",
|
||||
Stage="$default",
|
||||
ApiId=api["ApiId"],
|
||||
)
|
||||
|
||||
get_resp = client.get_api_mapping(
|
||||
DomainName="dev.service.io", ApiMappingId=post_resp["ApiMappingId"]
|
||||
)
|
||||
|
||||
# check post response has all expected keys
|
||||
for key in expected_keys:
|
||||
post_resp.should.have.key(key)
|
||||
|
||||
# check get response has all expected keys
|
||||
for key in expected_keys:
|
||||
get_resp.should.have.key(key)
|
||||
|
||||
# check that values are equal for post and get of same resource
|
||||
for key in expected_keys:
|
||||
post_resp.get(key).should.equal(get_resp.get(key))
|
||||
|
||||
# ensure known values are set correct in post response
|
||||
post_resp.get("ApiId").should_not.equal(None)
|
||||
post_resp.get("ApiMappingId").should_not.equal(None)
|
||||
post_resp.get("ApiMappingKey").should.equal("v1/api")
|
||||
post_resp.get("Stage").should.equal("$default")
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_create_api_mapping_missing_api():
|
||||
client = boto3.client("apigatewayv2", region_name="us-east-1")
|
||||
dev_domain = client.create_domain_name(DomainName="dev.service.io")
|
||||
with pytest.raises(botocore.exceptions.ClientError) as exc:
|
||||
client.create_api_mapping(
|
||||
DomainName=dev_domain["DomainName"],
|
||||
Stage="$default",
|
||||
ApiId="123dne",
|
||||
)
|
||||
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("NotFoundException")
|
||||
err["Message"].should.equal(
|
||||
"Invalid API identifier specified The resource specified in the request was not found."
|
||||
)
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_create_api_mapping_missing_domain():
|
||||
client = boto3.client("apigatewayv2", region_name="us-east-1")
|
||||
api = client.create_api(Name="test-api", ProtocolType="HTTP")
|
||||
|
||||
with pytest.raises(botocore.exceptions.ClientError) as exc:
|
||||
client.create_api_mapping(
|
||||
DomainName="domain.dne",
|
||||
Stage="$default",
|
||||
ApiId=api["ApiId"],
|
||||
)
|
||||
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("NotFoundException")
|
||||
err["Message"].should.equal(
|
||||
"The domain name resource specified in the request was not found."
|
||||
)
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_create_api_mapping_bad_mapping_keys():
|
||||
client = boto3.client("apigatewayv2", region_name="us-east-1")
|
||||
api = client.create_api(Name="test-api", ProtocolType="HTTP")
|
||||
dev_domain = client.create_domain_name(DomainName="dev.service.io")
|
||||
bad_keys = {
|
||||
"/api": "API mapping key should not start with a '/' or have consecutive '/'s.",
|
||||
"v1//api": "API mapping key should not start with a '/' or have consecutive '/'s.",
|
||||
"api/": "API mapping key should not end with a '/'.",
|
||||
}
|
||||
|
||||
for bad_key, message in bad_keys.items():
|
||||
with pytest.raises(botocore.exceptions.ClientError) as exc:
|
||||
client.create_api_mapping(
|
||||
DomainName=dev_domain["DomainName"],
|
||||
ApiMappingKey=bad_key,
|
||||
Stage="$default",
|
||||
ApiId=api["ApiId"],
|
||||
)
|
||||
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("BadRequestException")
|
||||
err["Message"].should.equal(message)
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_get_api_mappings():
|
||||
client = boto3.client("apigatewayv2", region_name="eu-west-1")
|
||||
api = client.create_api(Name="test-api", ProtocolType="HTTP")
|
||||
included_domain = client.create_domain_name(DomainName="dev.service.io")
|
||||
excluded_domain = client.create_domain_name(DomainName="hr.service.io")
|
||||
|
||||
v1_mapping = client.create_api_mapping(
|
||||
DomainName=included_domain["DomainName"],
|
||||
ApiMappingKey="v1/api",
|
||||
Stage="$default",
|
||||
ApiId=api["ApiId"],
|
||||
)
|
||||
|
||||
v2_mapping = client.create_api_mapping(
|
||||
DomainName=included_domain["DomainName"],
|
||||
ApiMappingKey="v2/api",
|
||||
Stage="$default",
|
||||
ApiId=api["ApiId"],
|
||||
)
|
||||
|
||||
hr_mapping = client.create_api_mapping(
|
||||
DomainName=excluded_domain["DomainName"],
|
||||
ApiMappingKey="hr/api",
|
||||
Stage="$default",
|
||||
ApiId=api["ApiId"],
|
||||
)
|
||||
|
||||
# sanity check responses
|
||||
v1_mapping.should.have.key("ApiMappingKey").equals("v1/api")
|
||||
v2_mapping.should.have.key("ApiMappingKey").equals("v2/api")
|
||||
hr_mapping.should.have.key("ApiMappingKey").equals("hr/api")
|
||||
|
||||
# make comparable
|
||||
del v1_mapping["ResponseMetadata"]
|
||||
del v2_mapping["ResponseMetadata"]
|
||||
del hr_mapping["ResponseMetadata"]
|
||||
|
||||
get_resp = client.get_api_mappings(DomainName=included_domain["DomainName"])
|
||||
get_resp.should.have.key("Items")
|
||||
get_resp.get("Items").should.contain(v1_mapping)
|
||||
get_resp.get("Items").should.contain(v2_mapping)
|
||||
get_resp.get("Items").should_not.contain(hr_mapping)
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_delete_api_mapping():
|
||||
client = boto3.client("apigatewayv2", region_name="eu-west-1")
|
||||
api = client.create_api(Name="test-api", ProtocolType="HTTP")
|
||||
api_domain = client.create_domain_name(DomainName="dev.service.io")
|
||||
|
||||
v1_mapping = client.create_api_mapping(
|
||||
DomainName=api_domain["DomainName"],
|
||||
ApiMappingKey="v1/api",
|
||||
Stage="$default",
|
||||
ApiId=api["ApiId"],
|
||||
)
|
||||
|
||||
del v1_mapping["ResponseMetadata"]
|
||||
|
||||
get_resp = client.get_api_mappings(DomainName=api_domain["DomainName"])
|
||||
get_resp.should.have.key("Items")
|
||||
get_resp.get("Items").should.contain(v1_mapping)
|
||||
|
||||
client.delete_api_mapping(
|
||||
DomainName=api_domain["DomainName"], ApiMappingId=v1_mapping["ApiMappingId"]
|
||||
)
|
||||
|
||||
get_resp = client.get_api_mappings(DomainName=api_domain["DomainName"])
|
||||
get_resp.should.have.key("Items")
|
||||
get_resp.get("Items").should_not.contain(v1_mapping)
|
||||
|
||||
|
||||
@mock_apigatewayv2
|
||||
def test_delete_api_mapping_dne():
|
||||
client = boto3.client("apigatewayv2", region_name="eu-west-1")
|
||||
client.create_api(Name="test-api", ProtocolType="HTTP")
|
||||
api_domain = client.create_domain_name(DomainName="dev.service.io")
|
||||
|
||||
with pytest.raises(botocore.exceptions.ClientError) as exc:
|
||||
client.delete_api_mapping(
|
||||
DomainName=api_domain["DomainName"], ApiMappingId="123dne"
|
||||
)
|
||||
|
||||
err = exc.value.response["Error"]
|
||||
err["Code"].should.equal("NotFoundException")
|
||||
err["Message"].should.equal(
|
||||
"The api mapping resource specified in the request was not found."
|
||||
)
|
Loading…
Reference in New Issue
Block a user