# encoding: utf-8 """ Unit test suite for the docx.opc.rel module """ from __future__ import ( absolute_import, division, print_function, unicode_literals ) import pytest from docx.opc.oxml import CT_Relationships from docx.opc.packuri import PackURI from docx.opc.part import Part from docx.opc.rel import _Relationship, Relationships from ..unitutil.mock import ( call, class_mock, instance_mock, Mock, patch, PropertyMock ) class Describe_Relationship(object): def it_remembers_construction_values(self): # test data -------------------- rId = 'rId9' reltype = 'reltype' target = Mock(name='target_part') external = False # exercise --------------------- rel = _Relationship(rId, reltype, target, None, external) # verify ----------------------- assert rel.rId == rId assert rel.reltype == reltype assert rel.target_part == target assert rel.is_external == external def it_should_raise_on_target_part_access_on_external_rel(self): rel = _Relationship(None, None, None, None, external=True) with pytest.raises(ValueError): rel.target_part def it_should_have_target_ref_for_external_rel(self): rel = _Relationship(None, None, 'target', None, external=True) assert rel.target_ref == 'target' def it_should_have_relative_ref_for_internal_rel(self): """ Internal relationships (TargetMode == 'Internal' in the XML) should have a relative ref, e.g. '../slideLayouts/slideLayout1.xml', for the target_ref attribute. """ part = Mock(name='part', partname=PackURI('/ppt/media/image1.png')) baseURI = '/ppt/slides' rel = _Relationship(None, None, part, baseURI) # external=False assert rel.target_ref == '../media/image1.png' class DescribeRelationships(object): def it_can_add_a_relationship(self, _Relationship_): baseURI, rId, reltype, target, external = ( 'baseURI', 'rId9', 'reltype', 'target', False ) rels = Relationships(baseURI) rel = rels.add_relationship(reltype, target, rId, external) _Relationship_.assert_called_once_with( rId, reltype, target, baseURI, external ) assert rels[rId] == rel assert rel == _Relationship_.return_value def it_can_add_an_external_relationship(self, add_ext_rel_fixture_): rels, reltype, url = add_ext_rel_fixture_ rId = rels.get_or_add_ext_rel(reltype, url) rel = rels[rId] assert rel.is_external assert rel.target_ref == url assert rel.reltype == reltype def it_can_find_a_relationship_by_rId(self): rel = Mock(name='rel', rId='foobar') rels = Relationships(None) rels['foobar'] = rel assert rels['foobar'] == rel def it_can_find_or_add_a_relationship( self, rels_with_matching_rel_, rels_with_missing_rel_): rels, reltype, part, matching_rel = rels_with_matching_rel_ assert rels.get_or_add(reltype, part) == matching_rel rels, reltype, part, new_rel = rels_with_missing_rel_ assert rels.get_or_add(reltype, part) == new_rel def it_can_find_or_add_an_external_relationship( self, add_matching_ext_rel_fixture_): rels, reltype, url, rId = add_matching_ext_rel_fixture_ _rId = rels.get_or_add_ext_rel(reltype, url) assert _rId == rId assert len(rels) == 1 def it_can_find_a_related_part_by_rId(self, rels_with_known_target_part): rels, rId, known_target_part = rels_with_known_target_part part = rels.related_parts[rId] assert part is known_target_part def it_raises_on_related_part_not_found(self, rels): with pytest.raises(KeyError): rels.related_parts['rId666'] def it_can_find_a_related_part_by_reltype( self, rels_with_target_known_by_reltype): rels, reltype, known_target_part = rels_with_target_known_by_reltype part = rels.part_with_reltype(reltype) assert part is known_target_part def it_can_compose_rels_xml(self, rels, rels_elm): # exercise --------------------- rels.xml # verify ----------------------- rels_elm.assert_has_calls( [ call.add_rel( 'rId1', 'http://rt-hyperlink', 'http://some/link', True ), call.add_rel( 'rId2', 'http://rt-image', '../media/image1.png', False ), call.xml() ], any_order=True ) def it_knows_the_next_available_rId_to_help(self, rels_with_rId_gap): rels, expected_next_rId = rels_with_rId_gap next_rId = rels._next_rId assert next_rId == expected_next_rId # fixtures --------------------------------------------- @pytest.fixture def add_ext_rel_fixture_(self, reltype, url): rels = Relationships(None) return rels, reltype, url @pytest.fixture def add_matching_ext_rel_fixture_(self, request, reltype, url): rId = 'rId369' rels = Relationships(None) rels.add_relationship(reltype, url, rId, is_external=True) return rels, reltype, url, rId # fixture components ----------------------------------- @pytest.fixture def _baseURI(self): return '/baseURI' @pytest.fixture def _Relationship_(self, request): return class_mock(request, 'docx.opc.rel._Relationship') @pytest.fixture def _rel_with_target_known_by_reltype( self, _rId, reltype, _target_part, _baseURI): rel = _Relationship(_rId, reltype, _target_part, _baseURI) return rel, reltype, _target_part @pytest.fixture def rels(self): """ Populated Relationships instance that will exercise the rels.xml property. """ rels = Relationships('/baseURI') rels.add_relationship( reltype='http://rt-hyperlink', target='http://some/link', rId='rId1', is_external=True ) part = Mock(name='part') part.partname.relative_ref.return_value = '../media/image1.png' rels.add_relationship(reltype='http://rt-image', target=part, rId='rId2') return rels @pytest.fixture def rels_elm(self, request): """ Return a rels_elm mock that will be returned from CT_Relationships.new() """ # create rels_elm mock with a .xml property rels_elm = Mock(name='rels_elm') xml = PropertyMock(name='xml') type(rels_elm).xml = xml rels_elm.attach_mock(xml, 'xml') rels_elm.reset_mock() # to clear attach_mock call # patch CT_Relationships to return that rels_elm patch_ = patch.object(CT_Relationships, 'new', return_value=rels_elm) patch_.start() request.addfinalizer(patch_.stop) return rels_elm @pytest.fixture def _rel_with_known_target_part( self, _rId, reltype, _target_part, _baseURI): rel = _Relationship(_rId, reltype, _target_part, _baseURI) return rel, _rId, _target_part @pytest.fixture def rels_with_known_target_part(self, rels, _rel_with_known_target_part): rel, rId, target_part = _rel_with_known_target_part rels.add_relationship(None, target_part, rId) return rels, rId, target_part @pytest.fixture def rels_with_matching_rel_(self, request, rels): matching_reltype_ = instance_mock( request, str, name='matching_reltype_' ) matching_part_ = instance_mock( request, Part, name='matching_part_' ) matching_rel_ = instance_mock( request, _Relationship, name='matching_rel_', reltype=matching_reltype_, target_part=matching_part_, is_external=False ) rels[1] = matching_rel_ return rels, matching_reltype_, matching_part_, matching_rel_ @pytest.fixture def rels_with_missing_rel_(self, request, rels, _Relationship_): missing_reltype_ = instance_mock( request, str, name='missing_reltype_' ) missing_part_ = instance_mock( request, Part, name='missing_part_' ) new_rel_ = instance_mock( request, _Relationship, name='new_rel_', reltype=missing_reltype_, target_part=missing_part_, is_external=False ) _Relationship_.return_value = new_rel_ return rels, missing_reltype_, missing_part_, new_rel_ @pytest.fixture def rels_with_rId_gap(self, request): rels = Relationships(None) rel_with_rId1 = instance_mock( request, _Relationship, name='rel_with_rId1', rId='rId1' ) rel_with_rId3 = instance_mock( request, _Relationship, name='rel_with_rId3', rId='rId3' ) rels['rId1'] = rel_with_rId1 rels['rId3'] = rel_with_rId3 return rels, 'rId2' @pytest.fixture def rels_with_target_known_by_reltype( self, rels, _rel_with_target_known_by_reltype): rel, reltype, target_part = _rel_with_target_known_by_reltype rels[1] = rel return rels, reltype, target_part @pytest.fixture def reltype(self): return 'http://rel/type' @pytest.fixture def _rId(self): return 'rId6' @pytest.fixture def _target_part(self, request): return instance_mock(request, Part) @pytest.fixture def url(self): return 'https://github.com/scanny/python-docx'