diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py
index a37a15e27..9fc41c11e 100644
--- a/moto/awslambda/models.py
+++ b/moto/awslambda/models.py
@@ -500,6 +500,11 @@ class LambdaStorage(object):
except ValueError:
return self._functions[name]['latest']
+ def list_versions_by_function(self, name):
+ if name not in self._functions:
+ return None
+ return [self._functions[name]['latest']]
+
def get_arn(self, arn):
return self._arns.get(arn, None)
@@ -607,6 +612,9 @@ class LambdaBackend(BaseBackend):
def get_function(self, function_name, qualifier=None):
return self._lambdas.get_function(function_name, qualifier)
+ def list_versions_by_function(self, function_name):
+ return self._lambdas.list_versions_by_function(function_name)
+
def get_function_by_arn(self, function_arn):
return self._lambdas.get_arn(function_arn)
diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py
index 1a9a4df83..d4eb73bc3 100644
--- a/moto/awslambda/responses.py
+++ b/moto/awslambda/responses.py
@@ -52,7 +52,11 @@ class LambdaResponse(BaseResponse):
self.setup_class(request, full_url, headers)
if request.method == 'GET':
# This is ListVersionByFunction
- raise ValueError("Cannot handle request")
+
+ path = request.path if hasattr(request, 'path') else path_url(request.url)
+ function_name = path.split('/')[-2]
+ return self._list_versions_by_function(function_name)
+
elif request.method == 'POST':
return self._publish_function(request, full_url, headers)
else:
@@ -151,6 +155,19 @@ class LambdaResponse(BaseResponse):
return 200, {}, json.dumps(result)
+ def _list_versions_by_function(self, function_name):
+ result = {
+ 'Versions': []
+ }
+
+ functions = self.lambda_backend.list_versions_by_function(function_name)
+ if functions:
+ for fn in functions:
+ json_data = fn.get_configuration()
+ result['Versions'].append(json_data)
+
+ return 200, {}, json.dumps(result)
+
def _create_function(self, request, full_url, headers):
try:
fn = self.lambda_backend.create_function(self.json_body)
diff --git a/moto/iam/models.py b/moto/iam/models.py
index 86a5d7a32..92ac19da7 100644
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -134,6 +134,7 @@ class Role(BaseModel):
self.managed_policies = {}
self.create_date = datetime.now(pytz.utc)
self.tags = {}
+ self.description = ""
@classmethod
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
@@ -473,6 +474,16 @@ class IAMBackend(BaseBackend):
policy = arns[policy_arn]
policy.attach_to(self.get_role(role_name))
+ def update_role_description(self, role_name, role_description):
+ role = self.get_role(role_name)
+ role.description = role_description
+ return role
+
+ def update_role(self, role_name, role_description):
+ role = self.get_role(role_name)
+ role.description = role_description
+ return role
+
def detach_role_policy(self, policy_arn, role_name):
arns = dict((p.arn, p) for p in self.managed_policies.values())
try:
@@ -881,6 +892,16 @@ class IAMBackend(BaseBackend):
return users
+ def list_roles(self, path_prefix, marker, max_items):
+ roles = None
+ try:
+ roles = self.roles.values()
+ except KeyError:
+ raise IAMNotFoundException(
+ "Users {0}, {1}, {2} not found".format(path_prefix, marker, max_items))
+
+ return roles
+
def upload_signing_certificate(self, user_name, body):
user = self.get_user(user_name)
cert_id = random_resource_id(size=32)
diff --git a/moto/iam/responses.py b/moto/iam/responses.py
index e3cc4b90b..5b19c9cdc 100644
--- a/moto/iam/responses.py
+++ b/moto/iam/responses.py
@@ -107,6 +107,69 @@ class IamResponse(BaseResponse):
template = self.response_template(LIST_POLICIES_TEMPLATE)
return template.render(policies=policies, marker=marker)
+ def list_entities_for_policy(self):
+ policy_arn = self._get_param('PolicyArn')
+
+ # Options 'User'|'Role'|'Group'|'LocalManagedPolicy'|'AWSManagedPolicy
+ entity = self._get_param('EntityFilter')
+ path_prefix = self._get_param('PathPrefix')
+ # policy_usage_filter = self._get_param('PolicyUsageFilter')
+ marker = self._get_param('Marker')
+ max_items = self._get_param('MaxItems')
+
+ entity_roles = []
+ entity_groups = []
+ entity_users = []
+
+ if entity == 'User':
+ users = iam_backend.list_users(path_prefix, marker, max_items)
+ if users:
+ for user in users:
+ for p in user.managed_policies:
+ if p == policy_arn:
+ entity_users.append(user.name)
+
+ elif entity == 'Role':
+ roles = iam_backend.list_roles(path_prefix, marker, max_items)
+ if roles:
+ for role in roles:
+ for p in role.managed_policies:
+ if p == policy_arn:
+ entity_roles.append(role.name)
+
+ elif entity == 'Group':
+ groups = iam_backend.list_groups()
+ if groups:
+ for group in groups:
+ for p in group.managed_policies:
+ if p == policy_arn:
+ entity_groups.append(group.name)
+
+ elif entity == 'LocalManagedPolicy' or entity == 'AWSManagedPolicy':
+ users = iam_backend.list_users(path_prefix, marker, max_items)
+ if users:
+ for user in users:
+ for p in user.managed_policies:
+ if p == policy_arn:
+ entity_users.append(user.name)
+
+ roles = iam_backend.list_roles(path_prefix, marker, max_items)
+ if roles:
+ for role in roles:
+ for p in role.managed_policies:
+ if p == policy_arn:
+ entity_roles.append(role.name)
+
+ groups = iam_backend.list_groups()
+ if groups:
+ for group in groups:
+ for p in group.managed_policies:
+ if p == policy_arn:
+ entity_groups.append(group.name)
+
+ template = self.response_template(LIST_ENTITIES_FOR_POLICY_TEMPLATE)
+ return template.render(roles=entity_roles, users=entity_users, groups=entity_groups)
+
def create_role(self):
role_name = self._get_param('RoleName')
path = self._get_param('Path')
@@ -169,6 +232,20 @@ class IamResponse(BaseResponse):
template = self.response_template(GENERIC_EMPTY_TEMPLATE)
return template.render(name="UpdateAssumeRolePolicyResponse")
+ def update_role_description(self):
+ role_name = self._get_param('RoleName')
+ description = self._get_param('Description')
+ role = iam_backend.update_role_description(role_name, description)
+ template = self.response_template(UPDATE_ROLE_DESCRIPTION_TEMPLATE)
+ return template.render(role=role)
+
+ def update_role(self):
+ role_name = self._get_param('RoleName')
+ description = self._get_param('Description')
+ role = iam_backend.update_role(role_name, description)
+ template = self.response_template(UPDATE_ROLE_TEMPLATE)
+ return template.render(role=role)
+
def create_policy_version(self):
policy_arn = self._get_param('PolicyArn')
policy_document = self._get_param('PolicyDocument')
@@ -655,6 +732,37 @@ class IamResponse(BaseResponse):
return template.render()
+LIST_ENTITIES_FOR_POLICY_TEMPLATE = """
+
+
+ {% for role in roles %}
+
+ {{ role }}
+
+ {% endfor %}
+
+
+ {% for group in groups %}
+
+ {{ group }}
+
+ {% endfor %}
+
+ false
+
+ {% for user in users %}
+
+ {{ user }}
+
+ {% endfor %}
+
+
+
+ eb358e22-9d1f-11e4-93eb-190ecEXAMPLE
+
+"""
+
+
ATTACH_ROLE_POLICY_TEMPLATE = """
7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
@@ -898,6 +1006,40 @@ GET_ROLE_POLICY_TEMPLATE = """
+
+
+
+ df37e965-9967-11e1-a4c3-270EXAMPLE04
+
+"""
+
+UPDATE_ROLE_DESCRIPTION_TEMPLATE = """
+
+
+ {{ role.path }}
+ {{ role.arn }}
+ {{ role.name }}
+ {{ role.assume_role_policy_document }}
+ {{ role.create_date.isoformat() }}
+ {{ role.id }}
+ {% if role.tags %}
+
+ {% for tag in role.get_tags() %}
+
+ {{ tag['Key'] }}
+ {{ tag['Value'] }}
+
+ {% endfor %}
+
+ {% endif %}
+
+
+
+ df37e965-9967-11e1-a4c3-270EXAMPLE04
+
+"""
+
GET_ROLE_TEMPLATE = """
diff --git a/moto/kms/models.py b/moto/kms/models.py
index bb39d1b24..9d13589c1 100644
--- a/moto/kms/models.py
+++ b/moto/kms/models.py
@@ -21,6 +21,7 @@ class Key(BaseModel):
self.account_id = "0123456789012"
self.key_rotation_status = False
self.deletion_date = None
+ self.tags = {}
@property
def physical_resource_id(self):
@@ -35,7 +36,7 @@ class Key(BaseModel):
"KeyMetadata": {
"AWSAccountId": self.account_id,
"Arn": self.arn,
- "CreationDate": "2015-01-01 00:00:00",
+ "CreationDate": datetime.strftime(datetime.utcnow(), "%Y-%m-%dT%H:%M:%SZ"),
"Description": self.description,
"Enabled": self.enabled,
"KeyId": self.id,
@@ -63,7 +64,6 @@ class Key(BaseModel):
)
key.key_rotation_status = properties['EnableKeyRotation']
key.enabled = properties['Enabled']
-
return key
def get_cfn_attribute(self, attribute_name):
@@ -84,6 +84,18 @@ class KmsBackend(BaseBackend):
self.keys[key.id] = key
return key
+ def update_key_description(self, key_id, description):
+ key = self.keys[self.get_key_id(key_id)]
+ key.description = description
+
+ def tag_resource(self, key_id, tags):
+ key = self.keys[self.get_key_id(key_id)]
+ key.tags = tags
+
+ def list_resource_tags(self, key_id):
+ key = self.keys[self.get_key_id(key_id)]
+ return key.tags
+
def delete_key(self, key_id):
if key_id in self.keys:
if key_id in self.key_to_aliases:
diff --git a/moto/kms/responses.py b/moto/kms/responses.py
index 5883f51ec..2674f765c 100644
--- a/moto/kms/responses.py
+++ b/moto/kms/responses.py
@@ -38,6 +38,28 @@ class KmsResponse(BaseResponse):
policy, key_usage, description, self.region)
return json.dumps(key.to_dict())
+ def update_key_description(self):
+ key_id = self.parameters.get('KeyId')
+ description = self.parameters.get('Description')
+
+ self.kms_backend.update_key_description(key_id, description)
+ return json.dumps(None)
+
+ def tag_resource(self):
+ key_id = self.parameters.get('KeyId')
+ tags = self.parameters.get('Tags')
+ self.kms_backend.tag_resource(key_id, tags)
+ return json.dumps({})
+
+ def list_resource_tags(self):
+ key_id = self.parameters.get('KeyId')
+ tags = self.kms_backend.list_resource_tags(key_id)
+ return json.dumps({
+ "Tags": tags,
+ "NextMarker": None,
+ "Truncated": False,
+ })
+
def describe_key(self):
key_id = self.parameters.get('KeyId')
try:
diff --git a/moto/packages/httpretty/core.py b/moto/packages/httpretty/core.py
index 8ad9168a5..4eb92108f 100644
--- a/moto/packages/httpretty/core.py
+++ b/moto/packages/httpretty/core.py
@@ -1113,4 +1113,4 @@ def httprettified(test):
if isinstance(test, ClassTypes):
return decorate_class(test)
- return decorate_callable(test)
+ return decorate_callable(test)
\ No newline at end of file
diff --git a/moto/packages/httpretty/http.py b/moto/packages/httpretty/http.py
index 7e9a56885..ee1625905 100644
--- a/moto/packages/httpretty/http.py
+++ b/moto/packages/httpretty/http.py
@@ -29,7 +29,6 @@ import re
from .compat import BaseClass
from .utils import decode_utf8
-
STATUSES = {
100: "Continue",
101: "Switching Protocols",
diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py
index 8ea9cc6fd..7f3b44b79 100644
--- a/tests/test_awslambda/test_lambda.py
+++ b/tests/test_awslambda/test_lambda.py
@@ -12,6 +12,8 @@ import sure # noqa
from freezegun import freeze_time
from moto import mock_lambda, mock_s3, mock_ec2, mock_sns, mock_logs, settings
+from nose.tools import assert_raises
+from botocore.exceptions import ClientError
_lambda_region = 'us-west-2'
@@ -397,6 +399,11 @@ def test_get_function():
result = conn.get_function(FunctionName='testFunction', Qualifier='$LATEST')
result['Configuration']['Version'].should.equal('$LATEST')
+ # Test get function when can't find function name
+ with assert_raises(ClientError):
+ conn.get_function(FunctionName='junk', Qualifier='$LATEST')
+
+
@mock_lambda
@mock_s3
@@ -819,3 +826,87 @@ def get_function_policy():
assert isinstance(response['Policy'], str)
res = json.loads(response['Policy'])
assert res['Statement'][0]['Action'] == 'lambda:InvokeFunction'
+
+
+@mock_lambda
+@mock_s3
+def test_list_versions_by_function():
+ s3_conn = boto3.client('s3', 'us-west-2')
+ s3_conn.create_bucket(Bucket='test-bucket')
+
+ zip_content = get_test_zip_file2()
+ s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content)
+ conn = boto3.client('lambda', 'us-west-2')
+
+ conn.create_function(
+ FunctionName='testFunction',
+ Runtime='python2.7',
+ Role='test-iam-role',
+ Handler='lambda_function.lambda_handler',
+ Code={
+ 'S3Bucket': 'test-bucket',
+ 'S3Key': 'test.zip',
+ },
+ Description='test lambda function',
+ Timeout=3,
+ MemorySize=128,
+ Publish=True,
+ )
+
+ conn.publish_version(FunctionName='testFunction')
+
+ versions = conn.list_versions_by_function(FunctionName='testFunction')
+
+ assert versions['Versions'][0]['FunctionArn'] == 'arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST'
+
+
+@mock_lambda
+@mock_s3
+def test_create_function_with_already_exists():
+ s3_conn = boto3.client('s3', 'us-west-2')
+ s3_conn.create_bucket(Bucket='test-bucket')
+
+ zip_content = get_test_zip_file2()
+ s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content)
+ conn = boto3.client('lambda', 'us-west-2')
+
+ conn.create_function(
+ FunctionName='testFunction',
+ Runtime='python2.7',
+ Role='test-iam-role',
+ Handler='lambda_function.lambda_handler',
+ Code={
+ 'S3Bucket': 'test-bucket',
+ 'S3Key': 'test.zip',
+ },
+ Description='test lambda function',
+ Timeout=3,
+ MemorySize=128,
+ Publish=True,
+ )
+
+ response = conn.create_function(
+ FunctionName='testFunction',
+ Runtime='python2.7',
+ Role='test-iam-role',
+ Handler='lambda_function.lambda_handler',
+ Code={
+ 'S3Bucket': 'test-bucket',
+ 'S3Key': 'test.zip',
+ },
+ Description='test lambda function',
+ Timeout=3,
+ MemorySize=128,
+ Publish=True,
+ )
+
+ assert response['FunctionName'] == 'testFunction'
+
+
+@mock_lambda
+@mock_s3
+def test_list_versions_by_function_for_nonexistent_function():
+ conn = boto3.client('lambda', 'us-west-2')
+ versions = conn.list_versions_by_function(FunctionName='testFunction')
+
+ assert len(versions['Versions']) == 0
diff --git a/tests/test_batch/test_batch.py b/tests/test_batch/test_batch.py
index ec24cd911..310ac0b48 100644
--- a/tests/test_batch/test_batch.py
+++ b/tests/test_batch/test_batch.py
@@ -323,6 +323,54 @@ def test_create_job_queue():
resp.should.contain('jobQueues')
len(resp['jobQueues']).should.equal(0)
+ # Create job queue which already exists
+ try:
+ resp = batch_client.create_job_queue(
+ jobQueueName='test_job_queue',
+ state='ENABLED',
+ priority=123,
+ computeEnvironmentOrder=[
+ {
+ 'order': 123,
+ 'computeEnvironment': arn
+ },
+ ]
+ )
+
+ except ClientError as err:
+ err.response['Error']['Code'].should.equal('ClientException')
+
+
+ # Create job queue with incorrect state
+ try:
+ resp = batch_client.create_job_queue(
+ jobQueueName='test_job_queue2',
+ state='JUNK',
+ priority=123,
+ computeEnvironmentOrder=[
+ {
+ 'order': 123,
+ 'computeEnvironment': arn
+ },
+ ]
+ )
+
+ except ClientError as err:
+ err.response['Error']['Code'].should.equal('ClientException')
+
+ # Create job queue with no compute env
+ try:
+ resp = batch_client.create_job_queue(
+ jobQueueName='test_job_queue3',
+ state='JUNK',
+ priority=123,
+ computeEnvironmentOrder=[
+
+ ]
+ )
+
+ except ClientError as err:
+ err.response['Error']['Code'].should.equal('ClientException')
@mock_ec2
@mock_ecs
@@ -397,6 +445,17 @@ def test_update_job_queue():
len(resp['jobQueues']).should.equal(1)
resp['jobQueues'][0]['priority'].should.equal(5)
+ batch_client.update_job_queue(
+ jobQueue='test_job_queue',
+ priority=5
+ )
+
+ resp = batch_client.describe_job_queues()
+ resp.should.contain('jobQueues')
+ len(resp['jobQueues']).should.equal(1)
+ resp['jobQueues'][0]['priority'].should.equal(5)
+
+
@mock_ec2
@mock_ecs
diff --git a/tests/test_events/test_events.py b/tests/test_events/test_events.py
index 80630c5b8..a9d90ec32 100644
--- a/tests/test_events/test_events.py
+++ b/tests/test_events/test_events.py
@@ -1,5 +1,4 @@
import random
-
import boto3
import json
@@ -7,7 +6,6 @@ from moto.events import mock_events
from botocore.exceptions import ClientError
from nose.tools import assert_raises
-
RULES = [
{'Name': 'test1', 'ScheduleExpression': 'rate(5 minutes)'},
{'Name': 'test2', 'ScheduleExpression': 'rate(1 minute)'},
@@ -109,6 +107,13 @@ def test_enable_disable_rule():
rule = client.describe_rule(Name=rule_name)
assert(rule['State'] == 'ENABLED')
+ # Test invalid name
+ try:
+ client.enable_rule(Name='junk')
+
+ except ClientError as ce:
+ assert ce.response['Error']['Code'] == 'ResourceNotFoundException'
+
@mock_events
def test_list_rule_names_by_target():
diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py
index d5f1bb4f9..ceec5e06a 100644
--- a/tests/test_iam/test_iam.py
+++ b/tests/test_iam/test_iam.py
@@ -1151,3 +1151,118 @@ def test_untag_role():
# With a role that doesn't exist:
with assert_raises(ClientError):
conn.untag_role(RoleName='notarole', TagKeys=['somevalue'])
+
+
+@mock_iam()
+def test_update_role_description():
+ conn = boto3.client('iam', region_name='us-east-1')
+
+ with assert_raises(ClientError):
+ conn.delete_role(RoleName="my-role")
+
+ conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
+ response = conn.update_role_description(RoleName="my-role", Description="test")
+
+ assert response['Role']['RoleName'] == 'my-role'
+
+@mock_iam()
+def test_update_role():
+ conn = boto3.client('iam', region_name='us-east-1')
+
+ with assert_raises(ClientError):
+ conn.delete_role(RoleName="my-role")
+
+ conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
+ response = conn.update_role_description(RoleName="my-role", Description="test")
+ assert response['Role']['RoleName'] == 'my-role'
+
+@mock_iam()
+def test_update_role():
+ conn = boto3.client('iam', region_name='us-east-1')
+
+ with assert_raises(ClientError):
+ conn.delete_role(RoleName="my-role")
+
+ conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
+ response = conn.update_role(RoleName="my-role", Description="test")
+ assert len(response.keys()) == 1
+
+
+@mock_iam()
+def test_list_entities_for_policy():
+ import json
+ test_policy = json.dumps({
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Action": "s3:ListBucket",
+ "Resource": "*",
+ "Effect": "Allow",
+ }
+ ]
+ })
+
+ conn = boto3.client('iam', region_name='us-east-1')
+ conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
+ conn.create_user(Path='/', UserName='testUser')
+ conn.create_group(Path='/', GroupName='testGroup')
+ conn.create_policy(
+ PolicyName='testPolicy',
+ Path='/',
+ PolicyDocument=test_policy,
+ Description='Test Policy'
+ )
+
+ # Attach things to the user and group:
+ conn.put_user_policy(UserName='testUser', PolicyName='testPolicy', PolicyDocument=test_policy)
+ conn.put_group_policy(GroupName='testGroup', PolicyName='testPolicy', PolicyDocument=test_policy)
+
+ conn.attach_user_policy(UserName='testUser', PolicyArn='arn:aws:iam::123456789012:policy/testPolicy')
+ conn.attach_group_policy(GroupName='testGroup', PolicyArn='arn:aws:iam::123456789012:policy/testPolicy')
+
+ conn.add_user_to_group(UserName='testUser', GroupName='testGroup')
+
+ # Add things to the role:
+ conn.create_instance_profile(InstanceProfileName='ipn')
+ conn.add_role_to_instance_profile(InstanceProfileName='ipn', RoleName='my-role')
+ conn.tag_role(RoleName='my-role', Tags=[
+ {
+ 'Key': 'somekey',
+ 'Value': 'somevalue'
+ },
+ {
+ 'Key': 'someotherkey',
+ 'Value': 'someothervalue'
+ }
+ ])
+ conn.put_role_policy(RoleName='my-role', PolicyName='test-policy', PolicyDocument=test_policy)
+ conn.attach_role_policy(RoleName='my-role', PolicyArn='arn:aws:iam::123456789012:policy/testPolicy')
+
+ response = conn.list_entities_for_policy(
+ PolicyArn='arn:aws:iam::123456789012:policy/testPolicy',
+ EntityFilter='Role'
+ )
+ assert response['PolicyRoles'] == [{'RoleName': 'my-role'}]
+
+ response = conn.list_entities_for_policy(
+ PolicyArn='arn:aws:iam::123456789012:policy/testPolicy',
+ EntityFilter='User',
+ )
+ assert response['PolicyUsers'] == [{'UserName': 'testUser'}]
+
+ response = conn.list_entities_for_policy(
+ PolicyArn='arn:aws:iam::123456789012:policy/testPolicy',
+ EntityFilter='Group',
+ )
+ assert response['PolicyGroups'] == [{'GroupName': 'testGroup'}]
+
+ response = conn.list_entities_for_policy(
+ PolicyArn='arn:aws:iam::123456789012:policy/testPolicy',
+ EntityFilter='LocalManagedPolicy',
+ )
+ assert response['PolicyGroups'] == [{'GroupName': 'testGroup'}]
+ assert response['PolicyUsers'] == [{'UserName': 'testUser'}]
+ assert response['PolicyRoles'] == [{'RoleName': 'my-role'}]
+
+
+
diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py
index 8bccae27a..0f7bab4cd 100644
--- a/tests/test_kms/test_kms.py
+++ b/tests/test_kms/test_kms.py
@@ -1,6 +1,5 @@
from __future__ import unicode_literals
import os, re
-
import boto3
import boto.kms
from boto.exception import JSONResponseError
@@ -717,3 +716,60 @@ def test_cancel_key_deletion():
assert result["KeyMetadata"]["Enabled"] == False
assert result["KeyMetadata"]["KeyState"] == 'Disabled'
assert 'DeletionDate' not in result["KeyMetadata"]
+
+
+@mock_kms
+def test_update_key_description():
+ client = boto3.client('kms', region_name='us-east-1')
+ key = client.create_key(Description='old_description')
+ key_id = key['KeyMetadata']['KeyId']
+
+ result = client.update_key_description(KeyId=key_id, Description='new_description')
+ assert 'ResponseMetadata' in result
+
+
+@mock_kms
+def test_tag_resource():
+ client = boto3.client('kms', region_name='us-east-1')
+ key = client.create_key(Description='cancel-key-deletion')
+ response = client.schedule_key_deletion(
+ KeyId=key['KeyMetadata']['KeyId']
+ )
+
+ keyid = response['KeyId']
+ response = client.tag_resource(
+ KeyId=keyid,
+ Tags=[
+ {
+ 'TagKey': 'string',
+ 'TagValue': 'string'
+ },
+ ]
+ )
+
+ # Shouldn't have any data, just header
+ assert len(response.keys()) == 1
+
+
+@mock_kms
+def test_list_resource_tags():
+ client = boto3.client('kms', region_name='us-east-1')
+ key = client.create_key(Description='cancel-key-deletion')
+ response = client.schedule_key_deletion(
+ KeyId=key['KeyMetadata']['KeyId']
+ )
+
+ keyid = response['KeyId']
+ response = client.tag_resource(
+ KeyId=keyid,
+ Tags=[
+ {
+ 'TagKey': 'string',
+ 'TagValue': 'string'
+ },
+ ]
+ )
+
+ response = client.list_resource_tags(KeyId=keyid)
+ assert response['Tags'][0]['TagKey'] == 'string'
+ assert response['Tags'][0]['TagValue'] == 'string'
diff --git a/tests/test_packages/__init__.py b/tests/test_packages/__init__.py
new file mode 100644
index 000000000..bf582e0b3
--- /dev/null
+++ b/tests/test_packages/__init__.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+
+import logging
+# Disable extra logging for tests
+logging.getLogger('boto').setLevel(logging.CRITICAL)
+logging.getLogger('boto3').setLevel(logging.CRITICAL)
+logging.getLogger('botocore').setLevel(logging.CRITICAL)
+logging.getLogger('nose').setLevel(logging.CRITICAL)
diff --git a/tests/test_packages/test_httpretty.py b/tests/test_packages/test_httpretty.py
new file mode 100644
index 000000000..48277a2de
--- /dev/null
+++ b/tests/test_packages/test_httpretty.py
@@ -0,0 +1,37 @@
+# #!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+import mock
+
+from moto.packages.httpretty.core import HTTPrettyRequest, fake_gethostname, fake_gethostbyname
+
+
+def test_parse_querystring():
+
+ core = HTTPrettyRequest(headers='test test HTTP/1.1')
+
+ qs = 'test test'
+ response = core.parse_querystring(qs)
+
+ assert response == {}
+
+def test_parse_request_body():
+ core = HTTPrettyRequest(headers='test test HTTP/1.1')
+
+ qs = 'test'
+ response = core.parse_request_body(qs)
+
+ assert response == 'test'
+
+def test_fake_gethostname():
+
+ response = fake_gethostname()
+
+ assert response == 'localhost'
+
+def test_fake_gethostbyname():
+
+ host = 'test'
+ response = fake_gethostbyname(host=host)
+
+ assert response == '127.0.0.1'
\ No newline at end of file