"""Unit tests for route53resolver rule association-related APIs.""" import boto3 from botocore.exceptions import ClientError import pytest from moto import mock_route53resolver from moto.ec2 import mock_ec2 from moto.moto_api._internal import mock_random from .test_route53resolver_endpoint import TEST_REGION, create_vpc from .test_route53resolver_rule import create_test_rule def create_test_rule_association( client, ec2_client, resolver_rule_id=None, name=None, vpc_id=None ): """Create a Resolver Rule Association for testing purposes.""" if not resolver_rule_id: resolver_rule_id = create_test_rule(client)["Id"] name = name if name else "R" + mock_random.get_random_hex(10) if not vpc_id: vpc_id = create_vpc(ec2_client) return client.associate_resolver_rule( ResolverRuleId=resolver_rule_id, Name=name, VPCId=vpc_id )["ResolverRuleAssociation"] @mock_route53resolver def test_route53resolver_invalid_associate_resolver_rule_args(): """Test invalid arguments to the associate_resolver_rule API.""" client = boto3.client("route53resolver", region_name=TEST_REGION) random_num = mock_random.get_random_hex(10) # Verify ValidationException error messages are accumulated properly: # - resolver rule ID that exceeds the allowed length of 64. # - name that exceeds the allowed length of 64. # - vpc_id that exceeds the allowed length of 64. long_id = random_num * 7 long_name = random_num * 6 + "abcde" long_vpc_id = random_num * 6 + "fghij" with pytest.raises(ClientError) as exc: client.associate_resolver_rule( ResolverRuleId=long_id, Name=long_name, VPCId=long_vpc_id ) err = exc.value.response["Error"] assert err["Code"] == "ValidationException" assert "3 validation errors detected" in err["Message"] assert ( f"Value '{long_id}' at 'resolverRuleId' failed to satisfy " f"constraint: Member must have length less than or equal to 64" ) in err["Message"] assert ( f"Value '{long_name}' at 'name' failed to satisfy constraint: " f"Member must have length less than or equal to 64" ) in err["Message"] assert ( f"Value '{long_vpc_id}' at 'vPCId' failed to satisfy " f"constraint: Member must have length less than or equal to 64" ) in err["Message"] @mock_ec2 @mock_route53resolver def test_route53resolver_associate_resolver_rule(): """Test good associate_resolver_rule API calls.""" client = boto3.client("route53resolver", region_name=TEST_REGION) ec2_client = boto3.client("ec2", region_name=TEST_REGION) resolver_rule_id = create_test_rule(client)["Id"] name = "X" + mock_random.get_random_hex(10) vpc_id = create_vpc(ec2_client) rule_association = client.associate_resolver_rule( ResolverRuleId=resolver_rule_id, Name=name, VPCId=vpc_id )["ResolverRuleAssociation"] assert rule_association["Id"].startswith("rslvr-rrassoc-") assert rule_association["ResolverRuleId"] == resolver_rule_id assert rule_association["Name"] == name assert rule_association["VPCId"] == vpc_id assert rule_association["Status"] == "COMPLETE" assert "StatusMessage" in rule_association @mock_ec2 @mock_route53resolver def test_route53resolver_other_associate_resolver_rule_errors(): """Test good associate_resolver_rule API calls.""" client = boto3.client("route53resolver", region_name=TEST_REGION) ec2_client = boto3.client("ec2", region_name=TEST_REGION) # Resolver referenced by resolver_rule_id doesn't exist. with pytest.raises(ClientError) as exc: create_test_rule_association(client, ec2_client, resolver_rule_id="foo") err = exc.value.response["Error"] assert err["Code"] == "ResourceNotFoundException" assert "Resolver rule with ID 'foo' does not exist" in err["Message"] # Invalid vpc_id with pytest.raises(ClientError) as exc: create_test_rule_association(client, ec2_client, vpc_id="foo") err = exc.value.response["Error"] assert err["Code"] == "InvalidParameterException" assert "The vpc ID 'foo' does not exist" in err["Message"] # Same resolver_rule_id and vpc_id pair for an association. resolver_rule_id = create_test_rule(client)["Id"] vpc_id = create_vpc(ec2_client) create_test_rule_association( client, ec2_client, resolver_rule_id=resolver_rule_id, vpc_id=vpc_id ) with pytest.raises(ClientError) as exc: create_test_rule_association( client, ec2_client, resolver_rule_id=resolver_rule_id, vpc_id=vpc_id ) err = exc.value.response["Error"] assert err["Code"] == "InvalidRequestException" assert ( f"Cannot associate rules with same domain name with same VPC. " f"Conflict with resolver rule '{resolver_rule_id}'" ) in err["Message"] # Not testing "Too many rule associations" as it takes too long to create # 2000 VPCs and rule associations. @mock_ec2 @mock_route53resolver def test_route53resolver_disassociate_resolver_rule(): """Test good disassociate_resolver_rule API calls.""" client = boto3.client("route53resolver", region_name=TEST_REGION) ec2_client = boto3.client("ec2", region_name=TEST_REGION) created_association = create_test_rule_association(client, ec2_client) # Disassociate the resolver rule and verify the response. response = client.disassociate_resolver_rule( ResolverRuleId=created_association["ResolverRuleId"], VPCId=created_association["VPCId"], ) association = response["ResolverRuleAssociation"] assert association["Id"] == created_association["Id"] assert association["ResolverRuleId"] == created_association["ResolverRuleId"] assert association["Name"] == created_association["Name"] assert association["VPCId"] == created_association["VPCId"] assert association["Status"] == "DELETING" assert "Deleting" in association["StatusMessage"] @mock_ec2 @mock_route53resolver def test_route53resolver_bad_disassociate_resolver_rule(): """Test disassociate_resolver_rule API calls with a bad ID.""" client = boto3.client("route53resolver", region_name=TEST_REGION) ec2_client = boto3.client("ec2", region_name=TEST_REGION) random_num = mock_random.get_random_hex(10) # Use a resolver rule id and vpc id that is too long. long_id = "0123456789" * 6 + "xxxxx" long_vpc_id = random_num * 6 + "12345" with pytest.raises(ClientError) as exc: client.disassociate_resolver_rule(ResolverRuleId=long_id, VPCId=long_vpc_id) err = exc.value.response["Error"] assert err["Code"] == "ValidationException" assert "2 validation errors detected" in err["Message"] assert ( f"Value '{long_id}' at 'resolverRuleId' failed to satisfy " f"constraint: Member must have length less than or equal to 64" ) in err["Message"] assert ( f"Value '{long_vpc_id}' at 'vPCId' failed to satisfy " f"constraint: Member must have length less than or equal to 64" ) in err["Message"] # Create a test association. test_association = create_test_rule_association(client, ec2_client) test_rule_id = test_association["ResolverRuleId"] test_vpc_id = test_association["VPCId"] # Disassociate from a non-existent resolver rule id. with pytest.raises(ClientError) as exc: client.disassociate_resolver_rule(ResolverRuleId=random_num, VPCId=test_vpc_id) err = exc.value.response["Error"] assert err["Code"] == "ResourceNotFoundException" assert f"Resolver rule with ID '{random_num}' does not exist" in err["Message"] # Disassociate using a non-existent vpc id. with pytest.raises(ClientError) as exc: client.disassociate_resolver_rule(ResolverRuleId=test_rule_id, VPCId=random_num) err = exc.value.response["Error"] assert err["Code"] == "ResourceNotFoundException" assert ( f"Resolver Rule Association between Resolver Rule " f"'{test_rule_id}' and VPC '{random_num}' does not exist" ) in err["Message"] # Disassociate successfully, then test that it's not possible to # disassociate again. client.disassociate_resolver_rule(ResolverRuleId=test_rule_id, VPCId=test_vpc_id) with pytest.raises(ClientError) as exc: client.disassociate_resolver_rule( ResolverRuleId=test_rule_id, VPCId=test_vpc_id ) err = exc.value.response["Error"] assert err["Code"] == "ResourceNotFoundException" assert ( f"Resolver Rule Association between Resolver Rule " f"'{test_rule_id}' and VPC '{test_vpc_id}' does not exist" ) in err["Message"] @mock_ec2 @mock_route53resolver def test_route53resolver_get_resolver_rule_association(): """Test good get_resolver_rule_association API calls.""" client = boto3.client("route53resolver", region_name=TEST_REGION) ec2_client = boto3.client("ec2", region_name=TEST_REGION) # Create a good association for testing purposes. created_association = create_test_rule_association(client, ec2_client) # Now get the resolver rule association and verify the response. response = client.get_resolver_rule_association( ResolverRuleAssociationId=created_association["Id"] ) association = response["ResolverRuleAssociation"] assert association["Id"] == created_association["Id"] assert association["ResolverRuleId"] == created_association["ResolverRuleId"] assert association["Name"] == created_association["Name"] assert association["VPCId"] == created_association["VPCId"] assert association["Status"] == created_association["Status"] assert association["StatusMessage"] == created_association["StatusMessage"] @mock_route53resolver def test_route53resolver_bad_get_resolver_rule_association(): """Test get_resolver_rule_association API calls with a bad ID.""" client = boto3.client("route53resolver", region_name=TEST_REGION) random_num = mock_random.get_random_hex(10) # Use a resolver rule association id that is too long. long_id = "0123456789" * 6 + "xxxxx" with pytest.raises(ClientError) as exc: client.get_resolver_rule_association(ResolverRuleAssociationId=long_id) err = exc.value.response["Error"] assert err["Code"] == "ValidationException" assert "1 validation error detected" in err["Message"] assert ( f"Value '{long_id}' at 'resolverRuleAssociationId' failed to satisfy " f"constraint: Member must have length less than or equal to 64" ) in err["Message"] # Get a non-existent resolver rule association. with pytest.raises(ClientError) as exc: client.get_resolver_rule_association(ResolverRuleAssociationId=random_num) err = exc.value.response["Error"] assert err["Code"] == "ResourceNotFoundException" assert f"ResolverRuleAssociation '{random_num}' does not Exist" in err["Message"] @mock_ec2 @mock_route53resolver def test_route53resolver_list_resolver_rule_associations(): """Test good list_resolver_rule_associations API calls.""" client = boto3.client("route53resolver", region_name=TEST_REGION) ec2_client = boto3.client("ec2", region_name=TEST_REGION) random_num = mock_random.get_random_hex(10) # List rule associations when there are none. response = client.list_resolver_rule_associations() assert len(response["ResolverRuleAssociations"]) == 0 assert response["MaxResults"] == 10 assert "NextToken" not in response # Create 4 associations, verify all are listed when no filters, max_results. for idx in range(4): create_test_rule_association(client, ec2_client, name=f"A{idx}-{random_num}") response = client.list_resolver_rule_associations() associations = response["ResolverRuleAssociations"] assert len(associations) == 4 assert response["MaxResults"] == 10 for idx in range(4): assert associations[idx]["Name"].startswith(f"A{idx}") # Set max_results to return 1 association, use next_token to get # remaining 3. response = client.list_resolver_rule_associations(MaxResults=1) associations = response["ResolverRuleAssociations"] assert len(associations) == 1 assert response["MaxResults"] == 1 assert "NextToken" in response assert associations[0]["Name"].startswith("A0") response = client.list_resolver_rule_associations(NextToken=response["NextToken"]) associations = response["ResolverRuleAssociations"] assert len(associations) == 3 assert response["MaxResults"] == 10 assert "NextToken" not in response for idx, association in enumerate(associations): assert association["Name"].startswith(f"A{idx + 1}") @mock_ec2 @mock_route53resolver def test_route53resolver_list_resolver_rule_associations_filters(): """Test good list_resolver_rule_associations API calls that use filters.""" client = boto3.client("route53resolver", region_name=TEST_REGION) ec2_client = boto3.client("ec2", region_name=TEST_REGION) random_num = mock_random.get_random_hex(10) # Create some rule associations for testing purposes vpc_id1 = create_vpc(ec2_client) vpc_id2 = create_vpc(ec2_client) associations = [] rule_ids = ["zero_offset"] for idx in range(1, 5): association = create_test_rule_association( client, ec2_client, name=f"F{idx}-{random_num}", vpc_id=vpc_id1 if idx % 2 else vpc_id2, ) associations.append(association) rule_ids.append(association["ResolverRuleId"]) # Try all the valid filter names, including some of the old style names. response = client.list_resolver_rule_associations( Filters=[{"Name": "ResolverRuleId", "Values": [rule_ids[3]]}] ) assert len(response["ResolverRuleAssociations"]) == 1 assert response["ResolverRuleAssociations"][0]["ResolverRuleId"] == rule_ids[3] response = client.list_resolver_rule_associations( Filters=[{"Name": "RESOLVER_RULE_ID", "Values": [rule_ids[2], rule_ids[4]]}] ) assert len(response["ResolverRuleAssociations"]) == 2 assert response["ResolverRuleAssociations"][0]["ResolverRuleId"] == rule_ids[2] assert response["ResolverRuleAssociations"][1]["ResolverRuleId"] == rule_ids[4] response = client.list_resolver_rule_associations( Filters=[{"Name": "VPCId", "Values": [vpc_id2]}] ) assert len(response["ResolverRuleAssociations"]) == 2 response = client.list_resolver_rule_associations( Filters=[{"Name": "Name", "Values": [f"F1-{random_num}"]}] ) assert len(response["ResolverRuleAssociations"]) == 1 assert response["ResolverRuleAssociations"][0]["Name"] == f"F1-{random_num}" response = client.list_resolver_rule_associations( Filters=[ {"Name": "VPC_ID", "Values": [vpc_id1]}, {"Name": "NAME", "Values": [f"F3-{random_num}"]}, ] ) assert len(response["ResolverRuleAssociations"]) == 1 assert response["ResolverRuleAssociations"][0]["Name"] == f"F3-{random_num}" response = client.list_resolver_rule_associations( Filters=[{"Name": "Status", "Values": ["COMPLETE"]}] ) assert len(response["ResolverRuleAssociations"]) == 4 response = client.list_resolver_rule_associations( Filters=[{"Name": "Status", "Values": ["CREATING"]}] ) assert len(response["ResolverRuleAssociations"]) == 0 @mock_route53resolver def test_route53resolver_bad_list_resolver_rule_associations_filters(): """Test bad list_resolver_rule_associations API calls that use filters.""" client = boto3.client("route53resolver", region_name=TEST_REGION) # botocore barfs on an empty "Values": # TypeError: list_resolver_rule_associations() only accepts keyword arguments. # client.list_resolver_rule_associations([{"Name": "Direction", "Values": []}]) # client.list_resolver_rule_associations([{"Values": []}]) with pytest.raises(ClientError) as exc: client.list_resolver_rule_associations( Filters=[{"Name": "foo", "Values": ["bar"]}] ) err = exc.value.response["Error"] assert err["Code"] == "InvalidParameterException" assert "The filter 'foo' is invalid" in err["Message"] with pytest.raises(ClientError) as exc: client.list_resolver_rule_associations( Filters=[{"Name": "VpcId", "Values": ["bar"]}] ) err = exc.value.response["Error"] assert err["Code"] == "InvalidParameterException" assert "The filter 'VpcId' is invalid" in err["Message"] @mock_ec2 @mock_route53resolver def test_route53resolver_bad_list_resolver_rule_associations(): """Test bad list_resolver_rule_associations API calls.""" client = boto3.client("route53resolver", region_name=TEST_REGION) ec2_client = boto3.client("ec2", region_name=TEST_REGION) # Bad max_results. create_test_rule_association(client, ec2_client) with pytest.raises(ClientError) as exc: client.list_resolver_rule_associations(MaxResults=250) err = exc.value.response["Error"] assert err["Code"] == "ValidationException" assert "1 validation error detected" in err["Message"] assert ( "Value '250' at 'maxResults' failed to satisfy constraint: Member " "must have length less than or equal to 100" ) in err["Message"]