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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user