891 lines
29 KiB
Python
891 lines
29 KiB
Python
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the 'License'). You
|
|
# may not use this file except in compliance with the License. A copy of
|
|
# the License is located at
|
|
#
|
|
# http://aws.amazon.com/apache2.0/
|
|
#
|
|
# or in the 'license' file accompanying this file. This file is
|
|
# distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
# ANY KIND, either express or implied. See the License for the specific
|
|
# language governing permissions and limitations under the License.
|
|
from botocore.model import DenormalizedStructureBuilder, ServiceModel
|
|
from tests import BaseTestCase, mock
|
|
|
|
from boto3.exceptions import ResourceLoadException
|
|
from boto3.utils import ServiceContext
|
|
from boto3.resources.base import ServiceResource
|
|
from boto3.resources.collection import CollectionManager
|
|
from boto3.resources.factory import ResourceFactory
|
|
from boto3.resources.action import WaiterAction
|
|
|
|
|
|
class BaseTestResourceFactory(BaseTestCase):
|
|
def setUp(self):
|
|
super(BaseTestResourceFactory, self).setUp()
|
|
self.emitter = mock.Mock()
|
|
self.factory = ResourceFactory(self.emitter)
|
|
|
|
def load(self, resource_name, resource_json_definition=None,
|
|
resource_json_definitions=None, service_model=None):
|
|
if resource_json_definition is None:
|
|
resource_json_definition = {}
|
|
if resource_json_definitions is None:
|
|
resource_json_definitions = {}
|
|
service_context=ServiceContext(
|
|
service_name='test',
|
|
resource_json_definitions=resource_json_definitions,
|
|
service_model=service_model,
|
|
service_waiter_model=None
|
|
)
|
|
|
|
return self.factory.load_from_definition(
|
|
resource_name=resource_name,
|
|
single_resource_json_definition=resource_json_definition,
|
|
service_context=service_context
|
|
)
|
|
|
|
|
|
class TestResourceFactory(BaseTestResourceFactory):
|
|
def test_get_service_returns_resource_class(self):
|
|
TestResource = self.load('test')
|
|
|
|
self.assertIn(ServiceResource, TestResource.__bases__,
|
|
'Did not return a ServiceResource subclass for service')
|
|
|
|
def test_get_resource_returns_resource_class(self):
|
|
QueueResource = self.load('Queue')
|
|
|
|
self.assertIn(ServiceResource, QueueResource.__bases__,
|
|
'Did not return a ServiceResource subclass for resource')
|
|
|
|
def test_factory_sets_service_name(self):
|
|
QueueResource = self.load('Queue')
|
|
|
|
self.assertEqual(QueueResource.meta.service_name, 'test',
|
|
'Service name not set')
|
|
|
|
def test_factory_sets_identifiers(self):
|
|
model = {
|
|
'identifiers': [
|
|
{'name': 'QueueUrl'},
|
|
{'name': 'ReceiptHandle'},
|
|
],
|
|
}
|
|
|
|
MessageResource = self.load('Message', model)
|
|
|
|
self.assertIn('queue_url', MessageResource.meta.identifiers,
|
|
'Missing queue_url identifier from model')
|
|
self.assertIn('receipt_handle', MessageResource.meta.identifiers,
|
|
'Missing receipt_handle identifier from model')
|
|
|
|
def test_identifiers_in_repr(self):
|
|
model = {
|
|
'identifiers': [
|
|
{'name': 'QueueUrl'},
|
|
{'name': 'ReceiptHandle'},
|
|
],
|
|
}
|
|
defs = {
|
|
'Message': model
|
|
}
|
|
|
|
resource = self.load('Message', model, defs)('url', 'handle')
|
|
|
|
# Class name
|
|
self.assertIn('test.Message', repr(resource))
|
|
|
|
# Identifier names and values
|
|
self.assertIn('queue_url', repr(resource))
|
|
self.assertIn("'url'", repr(resource))
|
|
self.assertIn('receipt_handle', repr(resource))
|
|
self.assertIn("'handle'", repr(resource))
|
|
|
|
def test_factory_creates_dangling_resources(self):
|
|
model = {
|
|
'has': {
|
|
'Queue': {
|
|
'resource': {
|
|
'type': 'Queue',
|
|
'identifiers': [
|
|
{'target': 'Url', 'source': 'input'}
|
|
]
|
|
}
|
|
},
|
|
'Message': {
|
|
'resource': {
|
|
'type': 'Message',
|
|
'identifiers': [
|
|
{'target': 'QueueUrl', 'source': 'input'},
|
|
{'target': 'Handle', 'source': 'input'}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
defs = {
|
|
'Queue': {},
|
|
'Message': {}
|
|
}
|
|
|
|
TestResource = self.load('test', model, defs)
|
|
|
|
self.assertTrue(hasattr(TestResource, 'Queue'),
|
|
'Missing Queue class from model')
|
|
self.assertTrue(hasattr(TestResource, 'Message'),
|
|
'Missing Message class from model')
|
|
|
|
def test_factory_creates_properties(self):
|
|
model = {
|
|
'shape': 'TestShape',
|
|
'load': {
|
|
'request': {
|
|
'operation': 'DescribeTest',
|
|
}
|
|
}
|
|
}
|
|
shape = DenormalizedStructureBuilder().with_members({
|
|
'ETag': {
|
|
'type': 'string',
|
|
},
|
|
'LastModified': {
|
|
'type': 'string'
|
|
}
|
|
}).build_model()
|
|
service_model = mock.Mock()
|
|
service_model.shape_for.return_value = shape
|
|
|
|
TestResource = self.load('test', model, service_model=service_model)
|
|
|
|
self.assertTrue(hasattr(TestResource, 'e_tag'),
|
|
'ETag shape member not available on resource')
|
|
self.assertTrue(hasattr(TestResource, 'last_modified'),
|
|
'LastModified shape member not available on resource')
|
|
|
|
def test_factory_renames_on_clobber_identifier(self):
|
|
model = {
|
|
'identifiers': [
|
|
{'name': 'Meta'}
|
|
]
|
|
}
|
|
|
|
# Each resource has a ``meta`` defined, so this identifier
|
|
# must be renamed.
|
|
cls = self.load('test', model)
|
|
|
|
self.assertTrue(hasattr(cls, 'meta_identifier'))
|
|
|
|
def test_factory_fails_on_clobber_action(self):
|
|
model = {
|
|
'identifiers': [
|
|
{'name': 'Test'},
|
|
{'name': 'TestAction'}
|
|
],
|
|
'actions': {
|
|
'Test': {
|
|
'request': {
|
|
'operation': 'GetTest'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# This fails because the resource has an identifier
|
|
# that would be clobbered by the action name.
|
|
with self.assertRaises(ValueError) as cm:
|
|
self.load('test', model)
|
|
|
|
self.assertIn('test', str(cm.exception))
|
|
self.assertIn('action', str(cm.exception))
|
|
|
|
def test_can_instantiate_service_resource(self):
|
|
TestResource = self.load('test')
|
|
resource = TestResource()
|
|
|
|
self.assertIsInstance(resource, ServiceResource,
|
|
'Object is not an instance of ServiceResource')
|
|
|
|
def test_non_service_resource_missing_defs(self):
|
|
# Only services should get dangling defs
|
|
defs = {
|
|
'Queue': {
|
|
'identifiers': [
|
|
{'name': 'Url'}
|
|
]
|
|
},
|
|
'Message': {
|
|
'identifiers': [
|
|
{'name': 'QueueUrl'},
|
|
{'name': 'ReceiptHandle'}
|
|
]
|
|
}
|
|
}
|
|
|
|
model = defs['Queue']
|
|
|
|
queue = self.load('Queue', model, defs)('url')
|
|
|
|
self.assertTrue(not hasattr(queue, 'Queue'))
|
|
self.assertTrue(not hasattr(queue, 'Message'))
|
|
|
|
def test_subresource_requires_only_identifier(self):
|
|
defs = {
|
|
'Queue': {
|
|
'identifiers': [
|
|
{'name': 'Url'}
|
|
],
|
|
'has': {
|
|
'Message': {
|
|
'resource': {
|
|
'type': 'Message',
|
|
'identifiers': [
|
|
{'target': 'QueueUrl', 'source': 'identifier',
|
|
'name': 'Url'},
|
|
{'target': 'ReceiptHandle', 'source': 'input'}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'Message': {
|
|
'identifiers': [
|
|
{'name': 'QueueUrl'},
|
|
{'name': 'ReceiptHandle'}
|
|
]
|
|
}
|
|
}
|
|
|
|
model = defs['Queue']
|
|
|
|
queue = self.load('Queue', model, defs)('url')
|
|
|
|
# Let's create a message and only give it a receipt handle
|
|
# The required queue_url identifier should be set from the
|
|
# queue itself.
|
|
message = queue.Message('receipt')
|
|
|
|
self.assertEqual(message.queue_url, 'url',
|
|
'Wrong queue URL set on the message resource instance')
|
|
self.assertEqual(message.receipt_handle, 'receipt',
|
|
'Wrong receipt handle set on the message resource instance')
|
|
|
|
def test_resource_meta_unique(self):
|
|
queue_cls = self.load('Queue')
|
|
|
|
queue1 = queue_cls()
|
|
queue2 = queue_cls()
|
|
|
|
self.assertEqual(queue1.meta, queue2.meta,
|
|
'Queue meta copies not equal after creation')
|
|
|
|
queue1.meta.data = {'id': 'foo'}
|
|
queue2.meta.data = {'id': 'bar'}
|
|
|
|
self.assertNotEqual(queue_cls.meta, queue1.meta,
|
|
'Modified queue instance data should not modify the class data')
|
|
self.assertNotEqual(queue1.meta, queue2.meta,
|
|
'Queue data should be unique to queue instance')
|
|
self.assertNotEqual(queue1.meta, 'bad-value')
|
|
|
|
def test_resource_meta_repr(self):
|
|
queue_cls = self.load('Queue')
|
|
queue = queue_cls()
|
|
self.assertEqual(repr(queue.meta),
|
|
'ResourceMeta(\'test\', identifiers=[])')
|
|
|
|
@mock.patch('boto3.resources.factory.ServiceAction')
|
|
def test_resource_calls_action(self, action_cls):
|
|
model = {
|
|
'actions': {
|
|
'GetMessageStatus': {
|
|
'request': {
|
|
'operation': 'DescribeMessageStatus'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
action = action_cls.return_value
|
|
|
|
queue = self.load('Queue', model)()
|
|
queue.get_message_status('arg1', arg2=2)
|
|
|
|
action.assert_called_with(queue, 'arg1', arg2=2)
|
|
|
|
@mock.patch('boto3.resources.factory.ServiceAction')
|
|
def test_resource_action_clears_data(self, action_cls):
|
|
model = {
|
|
'load': {
|
|
'request': {
|
|
'operation': 'DescribeQueue'
|
|
}
|
|
},
|
|
'actions': {
|
|
'GetMessageStatus': {
|
|
'request': {
|
|
'operation': 'DescribeMessageStatus'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
queue = self.load('Queue', model)()
|
|
|
|
# Simulate loaded data
|
|
queue.meta.data = {'some': 'data'}
|
|
|
|
# Perform a call
|
|
queue.get_message_status()
|
|
|
|
# Cached data should be cleared
|
|
self.assertIsNone(queue.meta.data)
|
|
|
|
@mock.patch('boto3.resources.factory.ServiceAction')
|
|
def test_resource_action_leaves_data(self, action_cls):
|
|
# This model has NO load method. Cached data should
|
|
# never be cleared since it cannot be reloaded!
|
|
model = {
|
|
'actions': {
|
|
'GetMessageStatus': {
|
|
'request': {
|
|
'operation': 'DescribeMessageStatus'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
queue = self.load('Queue', model)()
|
|
|
|
# Simulate loaded data
|
|
queue.meta.data = {'some': 'data'}
|
|
|
|
# Perform a call
|
|
queue.get_message_status()
|
|
|
|
# Cached data should not be cleared
|
|
self.assertEqual(queue.meta.data, {'some': 'data'})
|
|
|
|
@mock.patch('boto3.resources.factory.ServiceAction')
|
|
def test_resource_lazy_loads_properties(self, action_cls):
|
|
model = {
|
|
'shape': 'TestShape',
|
|
'identifiers': [
|
|
{'name': 'Url'}
|
|
],
|
|
'load': {
|
|
'request': {
|
|
'operation': 'DescribeTest',
|
|
}
|
|
}
|
|
}
|
|
shape = DenormalizedStructureBuilder().with_members({
|
|
'ETag': {
|
|
'type': 'string',
|
|
'shape_name': 'ETag'
|
|
},
|
|
'LastModified': {
|
|
'type': 'string',
|
|
'shape_name': 'LastModified'
|
|
},
|
|
'Url': {
|
|
'type': 'string',
|
|
'shape_name': 'Url'
|
|
}
|
|
}).build_model()
|
|
service_model = mock.Mock()
|
|
service_model.shape_for.return_value = shape
|
|
|
|
action = action_cls.return_value
|
|
action.return_value = {'ETag': 'tag', 'LastModified': 'never'}
|
|
|
|
resource = self.load(
|
|
'test', model, service_model=service_model)('url')
|
|
|
|
# Accessing an identifier should not call load, even if it's in
|
|
# the shape members.
|
|
resource.url
|
|
action.assert_not_called()
|
|
|
|
# Accessing a property should call load
|
|
self.assertEqual(resource.e_tag, 'tag',
|
|
'ETag property returned wrong value')
|
|
self.assertEqual(action.call_count, 1)
|
|
|
|
# Both params should have been loaded into the data bag
|
|
self.assertIn('ETag', resource.meta.data)
|
|
self.assertIn('LastModified', resource.meta.data)
|
|
|
|
# Accessing another property should use cached value
|
|
# instead of making a second call.
|
|
self.assertEqual(resource.last_modified, 'never',
|
|
'LastModified property returned wrong value')
|
|
self.assertEqual(action.call_count, 1)
|
|
|
|
@mock.patch('boto3.resources.factory.ServiceAction')
|
|
def test_resource_lazy_properties_missing_load(self, action_cls):
|
|
model = {
|
|
'shape': 'TestShape',
|
|
'identifiers': [
|
|
{'name': 'Url'}
|
|
]
|
|
# Note the lack of a `load` method. These resources
|
|
# are usually loaded via a call on a parent resource.
|
|
}
|
|
shape = DenormalizedStructureBuilder().with_members({
|
|
'ETag': {
|
|
'type': 'string',
|
|
},
|
|
'LastModified': {
|
|
'type': 'string'
|
|
},
|
|
'Url': {
|
|
'type': 'string'
|
|
}
|
|
}).build_model()
|
|
service_model = mock.Mock()
|
|
service_model.shape_for.return_value = shape
|
|
|
|
action = action_cls.return_value
|
|
action.return_value = {'ETag': 'tag', 'LastModified': 'never'}
|
|
|
|
resource = self.load(
|
|
'test', model, service_model=service_model)('url')
|
|
|
|
with self.assertRaises(ResourceLoadException):
|
|
resource.last_modified
|
|
|
|
@mock.patch('boto3.resources.factory.ServiceAction')
|
|
def test_resource_aliases_identifiers(self, action_cls):
|
|
model = {
|
|
'shape': 'TestShape',
|
|
'identifiers': [
|
|
{'name': 'id', 'memberName': 'foo_id'}
|
|
]
|
|
}
|
|
shape = DenormalizedStructureBuilder().with_members({
|
|
'foo_id': {
|
|
'type': 'string',
|
|
},
|
|
'bar': {
|
|
'type': 'string'
|
|
},
|
|
}).build_model()
|
|
service_model = mock.Mock()
|
|
service_model.shape_for.return_value = shape
|
|
|
|
shape_id = 'baz'
|
|
resource = self.load(
|
|
'test', model, service_model=service_model)(shape_id)
|
|
|
|
try:
|
|
self.assertEqual(resource.id, shape_id)
|
|
self.assertEqual(resource.foo_id, shape_id)
|
|
except ResourceLoadException:
|
|
self.fail("Load attempted on identifier alias.")
|
|
|
|
def test_resource_loads_references(self):
|
|
model = {
|
|
'shape': 'InstanceShape',
|
|
'identifiers': [{'name': 'GroupId'}],
|
|
'has': {
|
|
'Subnet': {
|
|
'resource': {
|
|
'type': 'Subnet',
|
|
'identifiers': [
|
|
{'target': 'Id', 'source': 'data',
|
|
'path': 'SubnetId'}
|
|
]
|
|
}
|
|
},
|
|
'Vpcs': {
|
|
'resource': {
|
|
'type': 'Vpc',
|
|
'identifiers': [
|
|
{'target': 'Id', 'source': 'data',
|
|
'path': 'Vpcs[].Id'}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
defs = {
|
|
'Subnet': {
|
|
'identifiers': [{'name': 'Id'}]
|
|
},
|
|
'Vpc': {
|
|
'identifiers': [{'name': 'Id'}]
|
|
}
|
|
}
|
|
service_model = ServiceModel({
|
|
'shapes': {
|
|
'InstanceShape': {
|
|
'type': 'structure',
|
|
'members': {
|
|
'SubnetId': {
|
|
'shape': 'String'
|
|
}
|
|
}
|
|
},
|
|
'String': {
|
|
'type': 'string'
|
|
}
|
|
}
|
|
})
|
|
|
|
resource = self.load('Instance', model, defs,
|
|
service_model)('group-id')
|
|
|
|
# Load the resource with no data
|
|
resource.meta.data = {}
|
|
|
|
self.assertTrue(
|
|
hasattr(resource, 'subnet'),
|
|
'Resource should have a subnet reference')
|
|
self.assertIsNone(
|
|
resource.subnet,
|
|
'Missing identifier, should return None')
|
|
self.assertIsNone(resource.vpcs)
|
|
|
|
# Load the resource with data to instantiate a reference
|
|
resource.meta.data = {
|
|
'SubnetId': 'abc123',
|
|
'Vpcs': [
|
|
{'Id': 'vpc1'},
|
|
{'Id': 'vpc2'}
|
|
]
|
|
}
|
|
|
|
self.assertIsInstance(resource.subnet, ServiceResource)
|
|
self.assertEqual(resource.subnet.id, 'abc123')
|
|
|
|
vpcs = resource.vpcs
|
|
self.assertIsInstance(vpcs, list)
|
|
self.assertEqual(len(vpcs), 2)
|
|
self.assertEqual(vpcs[0].id, 'vpc1')
|
|
self.assertEqual(vpcs[1].id, 'vpc2')
|
|
|
|
@mock.patch('boto3.resources.model.Collection')
|
|
def test_resource_loads_collections(self, mock_model):
|
|
model = {
|
|
'hasMany': {
|
|
u'Queues': {
|
|
'request': {
|
|
'operation': 'ListQueues'
|
|
},
|
|
'resource': {
|
|
'type': 'Queue'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
defs = {
|
|
'Queue': {}
|
|
}
|
|
service_model = ServiceModel({})
|
|
mock_model.return_value.name = 'queues'
|
|
|
|
resource = self.load('test', model, defs, service_model)()
|
|
|
|
self.assertTrue(hasattr(resource, 'queues'),
|
|
'Resource should expose queues collection')
|
|
self.assertIsInstance(resource.queues, CollectionManager,
|
|
'Queues collection should be a collection manager')
|
|
|
|
def test_resource_loads_waiters(self):
|
|
model = {
|
|
"waiters": {
|
|
"Exists": {
|
|
"waiterName": "BucketExists",
|
|
"params": [
|
|
{"target": "Bucket", "source": "identifier",
|
|
"name": "Name"}]
|
|
}
|
|
}
|
|
}
|
|
|
|
defs = {
|
|
'Bucket': {}
|
|
}
|
|
service_model = ServiceModel({})
|
|
|
|
resource = self.load('test', model, defs, service_model)()
|
|
|
|
self.assertTrue(hasattr(resource, 'wait_until_exists'),
|
|
'Resource should expose resource waiter: wait_until_exists')
|
|
|
|
@mock.patch('boto3.resources.factory.WaiterAction')
|
|
def test_resource_waiter_calls_waiter_method(self, waiter_action_cls):
|
|
model = {
|
|
"waiters": {
|
|
"Exists": {
|
|
"waiterName": "BucketExists",
|
|
"params": [
|
|
{"target": "Bucket", "source": "identifier",
|
|
"name": "Name"}]
|
|
}
|
|
}
|
|
}
|
|
|
|
defs = {
|
|
'Bucket': {}
|
|
}
|
|
service_model = ServiceModel({})
|
|
|
|
waiter_action = waiter_action_cls.return_value
|
|
resource = self.load('test', model, defs, service_model)()
|
|
|
|
resource.wait_until_exists('arg1', arg2=2)
|
|
waiter_action.assert_called_with(resource, 'arg1', arg2=2)
|
|
|
|
|
|
class TestResourceFactoryDanglingResource(BaseTestResourceFactory):
|
|
def setUp(self):
|
|
super(TestResourceFactoryDanglingResource, self).setUp()
|
|
|
|
self.model = {
|
|
'has': {
|
|
'Queue': {
|
|
'resource': {
|
|
'type': 'Queue',
|
|
'identifiers': [
|
|
{'target': 'Url', 'source': 'input'}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.defs = {
|
|
'Queue': {
|
|
'identifiers': [
|
|
{'name': 'Url'}
|
|
]
|
|
}
|
|
}
|
|
|
|
def test_dangling_resources_create_resource_instance(self):
|
|
resource = self.load('test', self.model, self.defs)()
|
|
q = resource.Queue('test')
|
|
|
|
self.assertIsInstance(q, ServiceResource,
|
|
'Dangling resource instance not a ServiceResource')
|
|
|
|
def test_dangling_resource_create_with_kwarg(self):
|
|
resource = self.load('test', self.model, self.defs)()
|
|
q = resource.Queue(url='test')
|
|
|
|
self.assertIsInstance(q, ServiceResource,
|
|
'Dangling resource created with kwargs is not a ServiceResource')
|
|
|
|
def test_dangling_resource_shares_client(self):
|
|
resource = self.load('test', self.model, self.defs)()
|
|
q = resource.Queue('test')
|
|
|
|
self.assertEqual(resource.meta.client, q.meta.client,
|
|
'Client was not shared to dangling resource instance')
|
|
|
|
def test_dangling_resource_requires_identifier(self):
|
|
resource = self.load('test', self.model, self.defs)()
|
|
|
|
with self.assertRaises(ValueError):
|
|
resource.Queue()
|
|
|
|
def test_dangling_resource_raises_for_unknown_arg(self):
|
|
resource = self.load('test', self.model, self.defs)()
|
|
|
|
with self.assertRaises(ValueError):
|
|
resource.Queue(url='foo', bar='baz')
|
|
|
|
def test_dangling_resource_identifier_is_immutable(self):
|
|
resource = self.load('test', self.model, self.defs)()
|
|
queue = resource.Queue('url')
|
|
# We should not be able to change the identifier's value
|
|
with self.assertRaises(AttributeError):
|
|
queue.url = 'foo'
|
|
|
|
def test_dangling_resource_equality(self):
|
|
resource = self.load('test', self.model, self.defs)()
|
|
|
|
q1 = resource.Queue('url')
|
|
q2 = resource.Queue('url')
|
|
|
|
self.assertEqual(q1, q2)
|
|
|
|
def test_dangling_resource_inequality(self):
|
|
self.defs = {
|
|
'Queue': {
|
|
'identifiers': [{'name': 'Url'}],
|
|
'has': {
|
|
'Message': {
|
|
'resource': {
|
|
'type': 'Message',
|
|
'identifiers': [
|
|
{'target': 'QueueUrl', 'source': 'identifier',
|
|
'name': 'Url'},
|
|
{'target': 'Handle', 'source': 'input'}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'Message': {
|
|
'identifiers': [{'name': 'QueueUrl'}, {'name': 'Handle'}]
|
|
}
|
|
}
|
|
|
|
resource = self.load('test', self.model, self.defs)()
|
|
|
|
q1 = resource.Queue('url')
|
|
q2 = resource.Queue('different')
|
|
m = q1.Message('handle')
|
|
|
|
self.assertNotEqual(q1, q2)
|
|
self.assertNotEqual(q1, m)
|
|
|
|
def test_dangling_resource_loads_data(self):
|
|
# Given a loadable resource instance that contains a reference
|
|
# to another resource which has a resource data path, the
|
|
# referenced resource should be loaded with all of the data
|
|
# contained at that path. This allows loading references
|
|
# which would otherwise not be loadable (missing load method)
|
|
# and prevents extra load calls for others when we already
|
|
# have the data available.
|
|
self.defs = {
|
|
'Instance': {
|
|
'identifiers': [{'name': 'Id'}],
|
|
'has': {
|
|
'NetworkInterface': {
|
|
'resource': {
|
|
'type': 'NetworkInterface',
|
|
'identifiers': [
|
|
{'target': 'Id', 'source': 'data',
|
|
'path': 'NetworkInterface.Id'}
|
|
],
|
|
'path': 'NetworkInterface'
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'NetworkInterface': {
|
|
'identifiers': [{'name': 'Id'}],
|
|
'shape': 'NetworkInterfaceShape'
|
|
}
|
|
}
|
|
self.model = self.defs['Instance']
|
|
shape = DenormalizedStructureBuilder().with_members({
|
|
'Id': {
|
|
'type': 'string',
|
|
},
|
|
'PublicIp': {
|
|
'type': 'string'
|
|
}
|
|
}).build_model()
|
|
service_model = mock.Mock()
|
|
service_model.shape_for.return_value = shape
|
|
|
|
cls = self.load('Instance', self.model, self.defs, service_model)
|
|
instance = cls('instance-id')
|
|
|
|
# Set some data as if we had completed a load action.
|
|
def set_meta_data():
|
|
instance.meta.data = {
|
|
'NetworkInterface': {
|
|
'Id': 'network-interface-id',
|
|
'PublicIp': '127.0.0.1'
|
|
}
|
|
}
|
|
instance.load = mock.Mock(side_effect=set_meta_data)
|
|
|
|
# Now, get the reference and make sure it has its data
|
|
# set as expected.
|
|
interface = instance.network_interface
|
|
self.assertIsNotNone(interface.meta.data)
|
|
self.assertEqual(interface.public_ip, '127.0.0.1')
|
|
|
|
|
|
class TestServiceResourceSubresources(BaseTestResourceFactory):
|
|
def setUp(self):
|
|
super(TestServiceResourceSubresources, self).setUp()
|
|
|
|
self.model = {
|
|
'has': {
|
|
'QueueObject': {
|
|
'resource': {
|
|
'type': 'Queue',
|
|
'identifiers': [
|
|
{'target': 'Url', 'source': 'input'}
|
|
]
|
|
}
|
|
},
|
|
'PriorityQueue': {
|
|
'resource': {
|
|
'type': 'Queue',
|
|
'identifiers': [
|
|
{'target': 'Url', 'source': 'input'}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.defs = {
|
|
'Queue': {
|
|
'identifiers': [
|
|
{'name': 'Url'}
|
|
]
|
|
},
|
|
'Message': {
|
|
'identifiers': [
|
|
{'name': 'QueueUrl'},
|
|
{'name': 'ReceiptHandle'}
|
|
]
|
|
}
|
|
}
|
|
|
|
def test_subresource_custom_name(self):
|
|
resource = self.load('test', self.model, self.defs)()
|
|
|
|
self.assertTrue(hasattr(resource, 'QueueObject'))
|
|
|
|
def test_contains_all_subresources(self):
|
|
resource = self.load('test', self.model, self.defs)()
|
|
|
|
self.assertIn('QueueObject', dir(resource))
|
|
self.assertIn('PriorityQueue', dir(resource))
|
|
self.assertIn('Message', dir(resource))
|
|
|
|
def test_get_available_subresources(self):
|
|
resource = self.load('test', self.model, self.defs)()
|
|
self.assertTrue(hasattr(resource, 'get_available_subresources'))
|
|
subresources = sorted(resource.get_available_subresources())
|
|
expected = sorted(['PriorityQueue', 'Message', 'QueueObject'])
|
|
self.assertEqual(subresources, expected)
|
|
|
|
def test_subresource_missing_all_subresources(self):
|
|
resource = self.load('test', self.model, self.defs)()
|
|
message = resource.Message('url', 'handle')
|
|
|
|
self.assertNotIn('QueueObject', dir(message))
|
|
self.assertNotIn('PriorityQueue', dir(message))
|
|
self.assertNotIn('Queue', dir(message))
|
|
self.assertNotIn('Message', dir(message))
|
|
|
|
def test_event_emitted_when_class_created(self):
|
|
self.load('test', self.model, self.defs)
|
|
self.assertTrue(self.emitter.emit.called)
|
|
call_args = self.emitter.emit.call_args
|
|
# Verify the correct event name emitted.
|
|
self.assertEqual(call_args[0][0],
|
|
'creating-resource-class.test.ServiceResource')
|
|
|
|
# Verify we send out the class attributes dict.
|
|
actual_class_attrs = sorted(call_args[1]['class_attributes'])
|
|
self.assertEqual(actual_class_attrs, [
|
|
'Message', 'PriorityQueue', 'QueueObject',
|
|
'get_available_subresources', 'meta'])
|
|
|
|
base_classes = sorted(call_args[1]['base_classes'])
|
|
self.assertEqual(base_classes, [ServiceResource])
|