python-boto3/tests/unit/resources/test_factory.py

846 lines
27 KiB
Python
Raw Permalink Normal View History

2015-11-27 23:25:33 +01:00
# 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
#
2021-09-22 18:34:33 +02:00
# https://aws.amazon.com/apache2.0/
2015-11-27 23:25:33 +01:00
#
# 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.
2021-10-04 19:51:32 +02:00
import pytest
2015-11-27 23:25:33 +01:00
from botocore.model import DenormalizedStructureBuilder, ServiceModel
from boto3.exceptions import ResourceLoadException
from boto3.resources.base import ServiceResource
from boto3.resources.collection import CollectionManager
from boto3.resources.factory import ResourceFactory
2022-05-26 01:13:54 +02:00
from boto3.utils import ServiceContext
from tests import BaseTestCase, mock
2015-11-27 23:25:33 +01:00
class BaseTestResourceFactory(BaseTestCase):
def setUp(self):
2022-05-26 01:13:54 +02:00
super().setUp()
2015-11-27 23:25:33 +01:00
self.emitter = mock.Mock()
self.factory = ResourceFactory(self.emitter)
2022-05-26 01:13:54 +02:00
def load(
self,
resource_name,
resource_json_definition=None,
resource_json_definitions=None,
service_model=None,
):
2015-11-27 23:25:33 +01:00
if resource_json_definition is None:
resource_json_definition = {}
if resource_json_definitions is None:
resource_json_definitions = {}
2021-11-03 18:27:47 +01:00
service_context = ServiceContext(
2015-11-27 23:25:33 +01:00
service_name='test',
resource_json_definitions=resource_json_definitions,
service_model=service_model,
2022-05-26 01:13:54 +02:00
service_waiter_model=None,
2015-11-27 23:25:33 +01:00
)
2021-11-03 18:27:47 +01:00
2015-11-27 23:25:33 +01:00
return self.factory.load_from_definition(
resource_name=resource_name,
single_resource_json_definition=resource_json_definition,
2022-05-26 01:13:54 +02:00
service_context=service_context,
2015-11-27 23:25:33 +01:00
)
class TestResourceFactory(BaseTestResourceFactory):
def test_get_service_returns_resource_class(self):
TestResource = self.load('test')
2021-10-04 19:51:32 +02:00
assert ServiceResource in TestResource.__bases__
2015-11-27 23:25:33 +01:00
def test_get_resource_returns_resource_class(self):
QueueResource = self.load('Queue')
2021-10-04 19:51:32 +02:00
assert ServiceResource in QueueResource.__bases__
2015-11-27 23:25:33 +01:00
def test_factory_sets_service_name(self):
QueueResource = self.load('Queue')
2021-10-04 19:51:32 +02:00
assert QueueResource.meta.service_name == 'test'
2015-11-27 23:25:33 +01:00
def test_factory_sets_identifiers(self):
model = {
'identifiers': [
{'name': 'QueueUrl'},
{'name': 'ReceiptHandle'},
],
}
MessageResource = self.load('Message', model)
2021-10-04 19:51:32 +02:00
assert 'queue_url' in MessageResource.meta.identifiers
assert 'receipt_handle' in MessageResource.meta.identifiers
2015-11-27 23:25:33 +01:00
def test_identifiers_in_repr(self):
model = {
'identifiers': [
{'name': 'QueueUrl'},
{'name': 'ReceiptHandle'},
],
}
2022-05-26 01:13:54 +02:00
defs = {'Message': model}
2015-11-27 23:25:33 +01:00
resource = self.load('Message', model, defs)('url', 'handle')
# Class name
2021-10-04 19:51:32 +02:00
assert 'test.Message' in repr(resource)
2015-11-27 23:25:33 +01:00
# Identifier names and values
2021-10-04 19:51:32 +02:00
assert 'queue_url' in repr(resource)
assert "'url'" in repr(resource)
assert 'receipt_handle' in repr(resource)
assert "'handle'" in repr(resource)
2015-11-27 23:25:33 +01:00
def test_factory_creates_dangling_resources(self):
model = {
'has': {
'Queue': {
'resource': {
'type': 'Queue',
2022-05-26 01:13:54 +02:00
'identifiers': [{'target': 'Url', 'source': 'input'}],
2015-11-27 23:25:33 +01:00
}
},
'Message': {
'resource': {
'type': 'Message',
'identifiers': [
{'target': 'QueueUrl', 'source': 'input'},
2022-05-26 01:13:54 +02:00
{'target': 'Handle', 'source': 'input'},
],
2015-11-27 23:25:33 +01:00
}
2022-05-26 01:13:54 +02:00
},
2015-11-27 23:25:33 +01:00
}
}
2022-05-26 01:13:54 +02:00
defs = {'Queue': {}, 'Message': {}}
2015-11-27 23:25:33 +01:00
TestResource = self.load('test', model, defs)
2021-10-04 19:51:32 +02:00
assert hasattr(TestResource, 'Queue')
assert hasattr(TestResource, 'Message')
2015-11-27 23:25:33 +01:00
def test_factory_creates_properties(self):
model = {
'shape': 'TestShape',
'load': {
'request': {
'operation': 'DescribeTest',
}
},
2022-05-26 01:13:54 +02:00
}
shape = (
DenormalizedStructureBuilder()
.with_members(
{
'ETag': {
'type': 'string',
},
'LastModified': {'type': 'string'},
}
)
.build_model()
)
2015-11-27 23:25:33 +01:00
service_model = mock.Mock()
service_model.shape_for.return_value = shape
TestResource = self.load('test', model, service_model=service_model)
2021-10-04 19:51:32 +02:00
assert hasattr(TestResource, 'e_tag')
assert hasattr(TestResource, 'last_modified')
2015-11-27 23:25:33 +01:00
def test_factory_renames_on_clobber_identifier(self):
2022-05-26 01:13:54 +02:00
model = {'identifiers': [{'name': 'Meta'}]}
2015-11-27 23:25:33 +01:00
# Each resource has a ``meta`` defined, so this identifier
# must be renamed.
cls = self.load('test', model)
2021-10-04 19:51:32 +02:00
assert hasattr(cls, 'meta_identifier')
2015-11-27 23:25:33 +01:00
def test_factory_fails_on_clobber_action(self):
model = {
2022-05-26 01:13:54 +02:00
'identifiers': [{'name': 'Test'}, {'name': 'TestAction'}],
'actions': {'Test': {'request': {'operation': 'GetTest'}}},
2015-11-27 23:25:33 +01:00
}
# This fails because the resource has an identifier
# that would be clobbered by the action name.
2021-10-04 19:51:32 +02:00
with pytest.raises(ValueError) as cm:
2015-11-27 23:25:33 +01:00
self.load('test', model)
2021-10-04 19:51:32 +02:00
assert 'test' in str(cm.exception)
assert 'action' in str(cm.exception)
2015-11-27 23:25:33 +01:00
def test_can_instantiate_service_resource(self):
TestResource = self.load('test')
resource = TestResource()
2021-10-04 19:51:32 +02:00
assert isinstance(resource, ServiceResource)
2015-11-27 23:25:33 +01:00
def test_non_service_resource_missing_defs(self):
# Only services should get dangling defs
defs = {
2022-05-26 01:13:54 +02:00
'Queue': {'identifiers': [{'name': 'Url'}]},
2015-11-27 23:25:33 +01:00
'Message': {
'identifiers': [
{'name': 'QueueUrl'},
2022-05-26 01:13:54 +02:00
{'name': 'ReceiptHandle'},
2015-11-27 23:25:33 +01:00
]
2022-05-26 01:13:54 +02:00
},
2015-11-27 23:25:33 +01:00
}
model = defs['Queue']
queue = self.load('Queue', model, defs)('url')
2021-10-04 19:51:32 +02:00
assert not hasattr(queue, 'Queue')
assert not hasattr(queue, 'Message')
2015-11-27 23:25:33 +01:00
def test_subresource_requires_only_identifier(self):
defs = {
'Queue': {
2022-05-26 01:13:54 +02:00
'identifiers': [{'name': 'Url'}],
2015-11-27 23:25:33 +01:00
'has': {
'Message': {
'resource': {
'type': 'Message',
'identifiers': [
2022-05-26 01:13:54 +02:00
{
'target': 'QueueUrl',
'source': 'identifier',
'name': 'Url',
},
{'target': 'ReceiptHandle', 'source': 'input'},
],
2015-11-27 23:25:33 +01:00
}
}
2022-05-26 01:13:54 +02:00
},
2015-11-27 23:25:33 +01:00
},
'Message': {
'identifiers': [
{'name': 'QueueUrl'},
2022-05-26 01:13:54 +02:00
{'name': 'ReceiptHandle'},
2015-11-27 23:25:33 +01:00
]
2022-05-26 01:13:54 +02:00
},
2015-11-27 23:25:33 +01:00
}
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')
2021-10-04 19:51:32 +02:00
assert message.queue_url == 'url'
assert message.receipt_handle == 'receipt'
2015-11-27 23:25:33 +01:00
def test_resource_meta_unique(self):
queue_cls = self.load('Queue')
queue1 = queue_cls()
queue2 = queue_cls()
2021-10-04 19:51:32 +02:00
assert queue1.meta == queue2.meta
2015-11-27 23:25:33 +01:00
queue1.meta.data = {'id': 'foo'}
queue2.meta.data = {'id': 'bar'}
2021-10-04 19:51:32 +02:00
assert queue_cls.meta != queue1.meta
assert queue1.meta != queue2.meta
assert queue1.meta != 'bad-value'
2015-11-27 23:25:33 +01:00
def test_resource_meta_repr(self):
queue_cls = self.load('Queue')
queue = queue_cls()
2021-10-04 19:51:32 +02:00
assert repr(queue.meta) == 'ResourceMeta(\'test\', identifiers=[])'
2015-11-27 23:25:33 +01:00
@mock.patch('boto3.resources.factory.ServiceAction')
def test_resource_calls_action(self, action_cls):
model = {
'actions': {
'GetMessageStatus': {
2022-05-26 01:13:54 +02:00
'request': {'operation': 'DescribeMessageStatus'}
2015-11-27 23:25:33 +01:00
}
}
}
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 = {
2022-05-26 01:13:54 +02:00
'load': {'request': {'operation': 'DescribeQueue'}},
2015-11-27 23:25:33 +01:00
'actions': {
'GetMessageStatus': {
2022-05-26 01:13:54 +02:00
'request': {'operation': 'DescribeMessageStatus'}
2015-11-27 23:25:33 +01:00
}
2022-05-26 01:13:54 +02:00
},
2015-11-27 23:25:33 +01:00
}
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
2021-10-04 19:51:32 +02:00
assert queue.meta.data is None
2015-11-27 23:25:33 +01:00
@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': {
2022-05-26 01:13:54 +02:00
'request': {'operation': 'DescribeMessageStatus'}
2015-11-27 23:25:33 +01:00
}
}
}
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
2021-10-04 19:51:32 +02:00
assert queue.meta.data == {'some': 'data'}
2015-11-27 23:25:33 +01:00
@mock.patch('boto3.resources.factory.ServiceAction')
def test_resource_lazy_loads_properties(self, action_cls):
model = {
'shape': 'TestShape',
2022-05-26 01:13:54 +02:00
'identifiers': [{'name': 'Url'}],
2015-11-27 23:25:33 +01:00
'load': {
'request': {
'operation': 'DescribeTest',
}
},
2022-05-26 01:13:54 +02:00
}
shape = (
DenormalizedStructureBuilder()
.with_members(
{
'ETag': {'type': 'string', 'shape_name': 'ETag'},
'LastModified': {
'type': 'string',
'shape_name': 'LastModified',
},
'Url': {'type': 'string', 'shape_name': 'Url'},
}
)
.build_model()
)
2015-11-27 23:25:33 +01:00
service_model = mock.Mock()
service_model.shape_for.return_value = shape
action = action_cls.return_value
action.return_value = {'ETag': 'tag', 'LastModified': 'never'}
2022-05-26 01:13:54 +02:00
resource = self.load('test', model, service_model=service_model)('url')
2015-11-27 23:25:33 +01:00
# 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
2021-10-04 19:51:32 +02:00
assert resource.e_tag == 'tag'
assert action.call_count == 1
2015-11-27 23:25:33 +01:00
# Both params should have been loaded into the data bag
2021-10-04 19:51:32 +02:00
assert 'ETag' in resource.meta.data
assert 'LastModified' in resource.meta.data
2015-11-27 23:25:33 +01:00
# Accessing another property should use cached value
# instead of making a second call.
2021-10-04 19:51:32 +02:00
assert resource.last_modified == 'never'
assert action.call_count == 1
2015-11-27 23:25:33 +01:00
@mock.patch('boto3.resources.factory.ServiceAction')
def test_resource_lazy_properties_missing_load(self, action_cls):
model = {
'shape': 'TestShape',
2022-05-26 01:13:54 +02:00
'identifiers': [{'name': 'Url'}]
2015-11-27 23:25:33 +01:00
# Note the lack of a `load` method. These resources
# are usually loaded via a call on a parent resource.
}
2022-05-26 01:13:54 +02:00
shape = (
DenormalizedStructureBuilder()
.with_members(
{
'ETag': {
'type': 'string',
},
'LastModified': {'type': 'string'},
'Url': {'type': 'string'},
}
)
.build_model()
)
2015-11-27 23:25:33 +01:00
service_model = mock.Mock()
service_model.shape_for.return_value = shape
action = action_cls.return_value
action.return_value = {'ETag': 'tag', 'LastModified': 'never'}
2022-05-26 01:13:54 +02:00
resource = self.load('test', model, service_model=service_model)('url')
2015-11-27 23:25:33 +01:00
2021-10-04 19:51:32 +02:00
with pytest.raises(ResourceLoadException):
2015-11-27 23:25:33 +01:00
resource.last_modified
2016-05-22 04:03:29 +02:00
@mock.patch('boto3.resources.factory.ServiceAction')
def test_resource_aliases_identifiers(self, action_cls):
model = {
'shape': 'TestShape',
2022-05-26 01:13:54 +02:00
'identifiers': [{'name': 'id', 'memberName': 'foo_id'}],
2016-05-22 04:03:29 +02:00
}
2022-05-26 01:13:54 +02:00
shape = (
DenormalizedStructureBuilder()
.with_members(
{
'foo_id': {
'type': 'string',
},
'bar': {'type': 'string'},
}
)
.build_model()
)
2016-05-22 04:03:29 +02:00
service_model = mock.Mock()
service_model.shape_for.return_value = shape
shape_id = 'baz'
2022-05-26 01:13:54 +02:00
resource = self.load('test', model, service_model=service_model)(
shape_id
)
2016-05-22 04:03:29 +02:00
try:
2021-10-04 19:51:32 +02:00
assert resource.id == shape_id
assert resource.foo_id == shape_id
2016-05-22 04:03:29 +02:00
except ResourceLoadException:
self.fail("Load attempted on identifier alias.")
2015-11-27 23:25:33 +01:00
def test_resource_loads_references(self):
model = {
'shape': 'InstanceShape',
'identifiers': [{'name': 'GroupId'}],
'has': {
'Subnet': {
'resource': {
'type': 'Subnet',
'identifiers': [
2022-05-26 01:13:54 +02:00
{
'target': 'Id',
'source': 'data',
'path': 'SubnetId',
}
],
2015-11-27 23:25:33 +01:00
}
},
'Vpcs': {
'resource': {
'type': 'Vpc',
'identifiers': [
2022-05-26 01:13:54 +02:00
{
'target': 'Id',
'source': 'data',
'path': 'Vpcs[].Id',
}
],
2015-11-27 23:25:33 +01:00
}
2022-05-26 01:13:54 +02:00
},
},
2015-11-27 23:25:33 +01:00
}
defs = {
2022-05-26 01:13:54 +02:00
'Subnet': {'identifiers': [{'name': 'Id'}]},
'Vpc': {'identifiers': [{'name': 'Id'}]},
2015-11-27 23:25:33 +01:00
}
2022-05-26 01:13:54 +02:00
service_model = ServiceModel(
{
'shapes': {
'InstanceShape': {
'type': 'structure',
'members': {'SubnetId': {'shape': 'String'}},
},
'String': {'type': 'string'},
2015-11-27 23:25:33 +01:00
}
}
2022-05-26 01:13:54 +02:00
)
2015-11-27 23:25:33 +01:00
2022-05-26 01:13:54 +02:00
resource = self.load('Instance', model, defs, service_model)(
'group-id'
)
2015-11-27 23:25:33 +01:00
# Load the resource with no data
resource.meta.data = {}
2021-10-04 19:51:32 +02:00
assert hasattr(resource, 'subnet')
assert resource.subnet is None
assert resource.vpcs is None
2015-11-27 23:25:33 +01:00
# Load the resource with data to instantiate a reference
resource.meta.data = {
'SubnetId': 'abc123',
2022-05-26 01:13:54 +02:00
'Vpcs': [{'Id': 'vpc1'}, {'Id': 'vpc2'}],
2015-11-27 23:25:33 +01:00
}
2021-10-04 19:51:32 +02:00
assert isinstance(resource.subnet, ServiceResource)
assert resource.subnet.id == 'abc123'
2015-11-27 23:25:33 +01:00
vpcs = resource.vpcs
2021-10-04 19:51:32 +02:00
assert isinstance(vpcs, list)
assert len(vpcs) == 2
assert vpcs[0].id == 'vpc1'
assert vpcs[1].id == 'vpc2'
2015-11-27 23:25:33 +01:00
@mock.patch('boto3.resources.model.Collection')
def test_resource_loads_collections(self, mock_model):
model = {
'hasMany': {
2022-05-26 01:13:54 +02:00
'Queues': {
'request': {'operation': 'ListQueues'},
'resource': {'type': 'Queue'},
2015-11-27 23:25:33 +01:00
}
}
}
2022-05-26 01:13:54 +02:00
defs = {'Queue': {}}
2015-11-27 23:25:33 +01:00
service_model = ServiceModel({})
mock_model.return_value.name = 'queues'
resource = self.load('test', model, defs, service_model)()
2021-10-04 19:51:32 +02:00
# Resource must expose queues collection
assert hasattr(resource, 'queues')
assert isinstance(resource.queues, CollectionManager)
2015-11-27 23:25:33 +01:00
def test_resource_loads_waiters(self):
model = {
"waiters": {
"Exists": {
2021-11-03 18:27:47 +01:00
"waiterName": "BucketExists",
"params": [
{
"target": "Bucket",
"source": "identifier",
2022-05-26 01:13:54 +02:00
"name": "Name",
2021-11-03 18:27:47 +01:00
}
2022-05-26 01:13:54 +02:00
],
2015-11-27 23:25:33 +01:00
}
}
}
2022-05-26 01:13:54 +02:00
defs = {'Bucket': {}}
2015-11-27 23:25:33 +01:00
service_model = ServiceModel({})
resource = self.load('test', model, defs, service_model)()
2021-10-04 19:51:32 +02:00
assert hasattr(resource, 'wait_until_exists')
2015-11-27 23:25:33 +01:00
@mock.patch('boto3.resources.factory.WaiterAction')
def test_resource_waiter_calls_waiter_method(self, waiter_action_cls):
model = {
"waiters": {
"Exists": {
2021-11-03 18:27:47 +01:00
"waiterName": "BucketExists",
"params": [
{
"target": "Bucket",
"source": "identifier",
2022-05-26 01:13:54 +02:00
"name": "Name",
2021-11-03 18:27:47 +01:00
}
2022-05-26 01:13:54 +02:00
],
2015-11-27 23:25:33 +01:00
}
}
}
2022-05-26 01:13:54 +02:00
defs = {'Bucket': {}}
2015-11-27 23:25:33 +01:00
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):
2022-05-26 01:13:54 +02:00
super().setUp()
2015-11-27 23:25:33 +01:00
self.model = {
'has': {
'Queue': {
'resource': {
'type': 'Queue',
2022-05-26 01:13:54 +02:00
'identifiers': [{'target': 'Url', 'source': 'input'}],
2015-11-27 23:25:33 +01:00
}
}
}
}
2022-05-26 01:13:54 +02:00
self.defs = {'Queue': {'identifiers': [{'name': 'Url'}]}}
2015-11-27 23:25:33 +01:00
def test_dangling_resources_create_resource_instance(self):
resource = self.load('test', self.model, self.defs)()
q = resource.Queue('test')
2021-10-04 19:51:32 +02:00
assert isinstance(q, ServiceResource)
2015-11-27 23:25:33 +01:00
2018-07-11 07:39:36 +02:00
def test_hash_resource_equal(self):
resource = self.load('test', self.model, self.defs)()
p = resource.Queue('test')
q = resource.Queue('test')
2021-10-04 19:51:32 +02:00
assert p == q
assert hash(p) == hash(q)
2018-07-11 07:39:36 +02:00
def test_hash_resource_not_equal(self):
resource = self.load('test', self.model, self.defs)()
p = resource.Queue('test1')
q = resource.Queue('test2')
2021-10-04 19:51:32 +02:00
assert p != q
assert hash(p) != hash(q)
2018-07-11 07:39:36 +02:00
2015-11-27 23:25:33 +01:00
def test_dangling_resource_create_with_kwarg(self):
resource = self.load('test', self.model, self.defs)()
q = resource.Queue(url='test')
2021-10-04 19:51:32 +02:00
assert isinstance(q, ServiceResource)
2015-11-27 23:25:33 +01:00
def test_dangling_resource_shares_client(self):
resource = self.load('test', self.model, self.defs)()
q = resource.Queue('test')
2021-10-04 19:51:32 +02:00
assert resource.meta.client == q.meta.client
2015-11-27 23:25:33 +01:00
def test_dangling_resource_requires_identifier(self):
resource = self.load('test', self.model, self.defs)()
2021-10-04 19:51:32 +02:00
with pytest.raises(ValueError):
2015-11-27 23:25:33 +01:00
resource.Queue()
def test_dangling_resource_raises_for_unknown_arg(self):
resource = self.load('test', self.model, self.defs)()
2021-10-04 19:51:32 +02:00
with pytest.raises(ValueError):
2015-11-27 23:25:33 +01:00
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
2021-10-04 19:51:32 +02:00
with pytest.raises(AttributeError):
2015-11-27 23:25:33 +01:00
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')
2021-10-04 19:51:32 +02:00
assert q1 == q2
2015-11-27 23:25:33 +01:00
def test_dangling_resource_inequality(self):
self.defs = {
'Queue': {
'identifiers': [{'name': 'Url'}],
'has': {
'Message': {
'resource': {
'type': 'Message',
'identifiers': [
2022-05-26 01:13:54 +02:00
{
'target': 'QueueUrl',
'source': 'identifier',
'name': 'Url',
},
{'target': 'Handle', 'source': 'input'},
],
2015-11-27 23:25:33 +01:00
}
}
2022-05-26 01:13:54 +02:00
},
2015-11-27 23:25:33 +01:00
},
'Message': {
'identifiers': [{'name': 'QueueUrl'}, {'name': 'Handle'}]
2022-05-26 01:13:54 +02:00
},
2015-11-27 23:25:33 +01:00
}
resource = self.load('test', self.model, self.defs)()
q1 = resource.Queue('url')
q2 = resource.Queue('different')
m = q1.Message('handle')
2021-10-04 19:51:32 +02:00
assert q1 != q2
assert q1 != m
2015-11-27 23:25:33 +01:00
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': [
2022-05-26 01:13:54 +02:00
{
'target': 'Id',
'source': 'data',
'path': 'NetworkInterface.Id',
}
2015-11-27 23:25:33 +01:00
],
2022-05-26 01:13:54 +02:00
'path': 'NetworkInterface',
2015-11-27 23:25:33 +01:00
}
}
2022-05-26 01:13:54 +02:00
},
2015-11-27 23:25:33 +01:00
},
'NetworkInterface': {
'identifiers': [{'name': 'Id'}],
2022-05-26 01:13:54 +02:00
'shape': 'NetworkInterfaceShape',
},
2015-11-27 23:25:33 +01:00
}
self.model = self.defs['Instance']
2022-05-26 01:13:54 +02:00
shape = (
DenormalizedStructureBuilder()
.with_members(
{
'Id': {
'type': 'string',
},
'PublicIp': {'type': 'string'},
}
)
.build_model()
)
2015-11-27 23:25:33 +01:00
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',
2022-05-26 01:13:54 +02:00
'PublicIp': '127.0.0.1',
2015-11-27 23:25:33 +01:00
}
}
2022-05-26 01:13:54 +02:00
2015-11-27 23:25:33 +01:00
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
2021-10-04 19:51:32 +02:00
assert interface.meta.data is not None
assert interface.public_ip == '127.0.0.1'
2015-11-27 23:25:33 +01:00
class TestServiceResourceSubresources(BaseTestResourceFactory):
def setUp(self):
2022-05-26 01:13:54 +02:00
super().setUp()
2015-11-27 23:25:33 +01:00
self.model = {
'has': {
'QueueObject': {
'resource': {
'type': 'Queue',
2022-05-26 01:13:54 +02:00
'identifiers': [{'target': 'Url', 'source': 'input'}],
2015-11-27 23:25:33 +01:00
}
},
'PriorityQueue': {
'resource': {
'type': 'Queue',
2022-05-26 01:13:54 +02:00
'identifiers': [{'target': 'Url', 'source': 'input'}],
2015-11-27 23:25:33 +01:00
}
2022-05-26 01:13:54 +02:00
},
2015-11-27 23:25:33 +01:00
}
}
self.defs = {
2022-05-26 01:13:54 +02:00
'Queue': {'identifiers': [{'name': 'Url'}]},
2015-11-27 23:25:33 +01:00
'Message': {
'identifiers': [
{'name': 'QueueUrl'},
2022-05-26 01:13:54 +02:00
{'name': 'ReceiptHandle'},
2015-11-27 23:25:33 +01:00
]
2022-05-26 01:13:54 +02:00
},
2015-11-27 23:25:33 +01:00
}
def test_subresource_custom_name(self):
resource = self.load('test', self.model, self.defs)()
2021-10-04 19:51:32 +02:00
assert hasattr(resource, 'QueueObject')
2015-11-27 23:25:33 +01:00
def test_contains_all_subresources(self):
resource = self.load('test', self.model, self.defs)()
2021-10-04 19:51:32 +02:00
assert 'QueueObject' in dir(resource)
assert 'PriorityQueue' in dir(resource)
assert 'Message' in dir(resource)
2015-11-27 23:25:33 +01:00
2016-05-22 04:03:29 +02:00
def test_get_available_subresources(self):
resource = self.load('test', self.model, self.defs)()
2021-10-04 19:51:32 +02:00
assert hasattr(resource, 'get_available_subresources')
2016-05-22 04:03:29 +02:00
subresources = sorted(resource.get_available_subresources())
expected = sorted(['PriorityQueue', 'Message', 'QueueObject'])
2021-10-04 19:51:32 +02:00
assert subresources == expected
2016-05-22 04:03:29 +02:00
2015-11-27 23:25:33 +01:00
def test_subresource_missing_all_subresources(self):
resource = self.load('test', self.model, self.defs)()
message = resource.Message('url', 'handle')
2021-10-04 19:51:32 +02:00
assert 'QueueObject' not in dir(message)
assert 'PriorityQueue' not in dir(message)
assert 'Queue' not in dir(message)
assert 'Message' not in dir(message)
2015-11-27 23:25:33 +01:00
def test_event_emitted_when_class_created(self):
self.load('test', self.model, self.defs)
2021-10-04 19:51:32 +02:00
assert self.emitter.emit.called
2015-11-27 23:25:33 +01:00
call_args = self.emitter.emit.call_args
# Verify the correct event name emitted.
2022-05-26 01:13:54 +02:00
assert (
call_args[0][0] == 'creating-resource-class.test.ServiceResource'
)
2015-11-27 23:25:33 +01:00
# Verify we send out the class attributes dict.
actual_class_attrs = sorted(call_args[1]['class_attributes'])
2021-10-04 19:51:32 +02:00
assert actual_class_attrs == [
2022-05-26 01:13:54 +02:00
'Message',
'PriorityQueue',
'QueueObject',
'get_available_subresources',
'meta',
]
2015-11-27 23:25:33 +01:00
base_classes = sorted(call_args[1]['base_classes'])
2021-10-04 19:51:32 +02:00
assert base_classes == [ServiceResource]