def test_semantic_analyzer_does_correctly_processes_valid_ast(self): manifest = RWPMManifest( metadata=PresentationMetadata("test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), reading_order=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([ Link(href="test", _type=RWPMMediaTypesRegistry.JPEG.key), ]), ), resources=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([ Link(href="test", _type=RWPMMediaTypesRegistry.JPEG.key), ]), ), ) media_types_registry = RWPMMediaTypesRegistry() link_relations_registry = RWPMLinkRelationsRegistry() collection_roles_registry = RWPMCollectionRolesRegistry() semantic_analyzer = RWPMSemanticAnalyzer(media_types_registry, link_relations_registry, collection_roles_registry) # Act semantic_analyzer.visit(manifest)
): break else: self.fail( "Expected error for {0} node's property '{1}' with error message \"{2}\" wasn't raised".format( expected_error.node.__class__, expected_error.node_property.key if expected_error.node_property else "", expected_error.error_message, ) ) MANIFEST = Manifestlike( metadata=PresentationMetadata(title="Manifest # 1"), links=LinkList( [ Link( href="http://example.com", rels=["test"], ) ] ), ) RWPM_MANIFEST = RWPMManifest( metadata=PresentationMetadata("test"), links=LinkList( [ Link(
class SemanticAnalyzerTest(TestCase): @parameterized.expand([ ( "when_feed_does_not_contain_neither_publications_nor_navigation_nor_groups", OPDS2Feed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), ), MISSING_REQUIRED_FEED_SUB_COLLECTIONS, ), ( "when_navigation_link_does_not_contain_title", OPDS2Feed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), navigation=OPDS2Navigation( links=LinkList([Link(href="http://example.com")])), ), MISSING_NAVIGATION_LINK_TITLE_ERROR, ), ( "when_publication_does_not_contain_acquisition_link", OPDS2Feed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), publications=CollectionList([ OPDS2Publication( metadata=PresentationMetadata(title="Publication 1"), links=LinkList([Link(href="http://example.com")]), ) ]), ), MISSING_ACQUISITION_LINK, ), ( "when_navigation_contains_both_links_and_publications", OPDS2Feed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), groups=CollectionList([ OPDS2Group( navigation=OPDS2Navigation(links=LinkList([ Link( href="http://example.com", rels=["current"], ) ])), publications=CollectionList([ OPDS2Publication( metadata=PresentationMetadata( title="Publication 1"), links=LinkList( [Link(href="http://example.com")]), ) ]), ) ]), ), WRONG_GROUP_STRUCTURE, ), ]) def test_semantic_analyzer_raises_error(self, _, manifest, expected_error): # Arrange media_types_registry = OPDS2MediaTypesRegistry() link_relations_registry = OPDS2LinkRelationsRegistry() collection_roles_registry = OPDS2CollectionRolesRegistry() semantic_analyzer = OPDS2SemanticAnalyzer(media_types_registry, link_relations_registry, collection_roles_registry) # Act with assert_raises(expected_error.__class__) as assert_raises_context: semantic_analyzer.visit(manifest) # Assert eq_(str(assert_raises_context.exception), str(expected_error)) def test_semantic_analyzer_does_correctly_processes_valid_ast(self): # Arrange feed = OPDS2Feed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), publications=CollectionList([ OPDS2Publication( metadata=PresentationMetadata(title="Publication 1"), links=LinkList([ Link( href="http://example.com", rels=[OPDS2LinkRelationsRegistry.ACQUISITION.key], ) ]), ) ]), navigation=OPDS2Navigation(links=LinkList([ Link( href="/new", title="New Publications", _type=OPDS2MediaTypesRegistry.OPDS_FEED, rels=["current"], ) ])), groups=CollectionList([ OPDS2Group( metadata=OPDS2FeedMetadata(title="Group 1"), publications=CollectionList([ OPDS2Publication( metadata=PresentationMetadata( title="Publication 1.1"), links=LinkList([ Link( href="http://example.com", rels=[ OPDS2LinkRelationsRegistry.ACQUISITION. key ], ) ]), ) ]), ) ]), ) media_types_registry = OPDS2MediaTypesRegistry() link_relations_registry = OPDS2LinkRelationsRegistry() collection_roles_registry = OPDS2CollectionRolesRegistry() semantic_analyzer = OPDS2SemanticAnalyzer(media_types_registry, link_relations_registry, collection_roles_registry) semantic_analyzer.visit = MagicMock( side_effect=semantic_analyzer.visit) # Act semantic_analyzer.visit(feed) # Assert semantic_analyzer.visit.assert_has_calls( calls=[ call(feed), call(feed.metadata), call(feed.links), call(feed.links[0]), call(feed.sub_collections), call(feed.publications), call(feed.publications[0]), call(feed.publications[0].metadata), call(feed.publications[0].links), call(feed.publications[0].links[0]), call(feed.publications[0].sub_collections), call(feed.navigation), call(feed.navigation), call(feed.navigation.links), call(feed.navigation.links[0]), call(feed.groups), call(feed.groups[0]), call(feed.groups[0].metadata), call(feed.groups[0].publications), call(feed.groups[0].publications[0]), call(feed.groups[0].publications[0].metadata), call(feed.groups[0].publications[0].links), call(feed.groups[0].publications[0].links[0]), ], any_order=False, )
def test_semantic_analyzer_does_correctly_processes_valid_ast(self): # Arrange feed = OPDS2Feed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), publications=CollectionList([ OPDS2Publication( metadata=PresentationMetadata(title="Publication 1"), links=LinkList([ Link( href="http://example.com", rels=[OPDS2LinkRelationsRegistry.ACQUISITION.key], ) ]), ) ]), navigation=OPDS2Navigation(links=LinkList([ Link( href="/new", title="New Publications", _type=OPDS2MediaTypesRegistry.OPDS_FEED, rels=["current"], ) ])), groups=CollectionList([ OPDS2Group( metadata=OPDS2FeedMetadata(title="Group 1"), publications=CollectionList([ OPDS2Publication( metadata=PresentationMetadata( title="Publication 1.1"), links=LinkList([ Link( href="http://example.com", rels=[ OPDS2LinkRelationsRegistry.ACQUISITION. key ], ) ]), ) ]), ) ]), ) media_types_registry = OPDS2MediaTypesRegistry() link_relations_registry = OPDS2LinkRelationsRegistry() collection_roles_registry = OPDS2CollectionRolesRegistry() semantic_analyzer = OPDS2SemanticAnalyzer(media_types_registry, link_relations_registry, collection_roles_registry) semantic_analyzer.visit = MagicMock( side_effect=semantic_analyzer.visit) # Act semantic_analyzer.visit(feed) # Assert semantic_analyzer.visit.assert_has_calls( calls=[ call(feed), call(feed.metadata), call(feed.links), call(feed.links[0]), call(feed.sub_collections), call(feed.publications), call(feed.publications[0]), call(feed.publications[0].metadata), call(feed.publications[0].links), call(feed.publications[0].links[0]), call(feed.publications[0].sub_collections), call(feed.navigation), call(feed.navigation), call(feed.navigation.links), call(feed.navigation.links[0]), call(feed.groups), call(feed.groups[0]), call(feed.groups[0].metadata), call(feed.groups[0].publications), call(feed.groups[0].publications[0]), call(feed.groups[0].publications[0].metadata), call(feed.groups[0].publications[0].links), call(feed.groups[0].publications[0].links[0]), ], any_order=False, )
class SemanticAnalyzerTest(TestCase): @parameterized.expand([ ( "when_reading_order_link_does_not_have_type_property", RWPMManifest( metadata=PresentationMetadata("test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), reading_order=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([Link(href="test")]), ), ), MISSING_READING_ORDER_LINK_TYPE_PROPERTY_ERROR, ), ( "when_resources_link_does_not_have_type_property", RWPMManifest( metadata=PresentationMetadata("test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), reading_order=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([ Link(href="test", _type=RWPMMediaTypesRegistry.JPEG.key) ]), ), resources=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([Link(href="test")]), ), ), MISSING_RESOURCES_LINK_TYPE_PROPERTY_ERROR, ), ]) def test_semantic_analyzer_raises_error(self, _, manifest, expected_error): # Arrange media_types_registry = RWPMMediaTypesRegistry() link_relations_registry = RWPMLinkRelationsRegistry() collection_roles_registry = RWPMCollectionRolesRegistry() semantic_analyzer = RWPMSemanticAnalyzer(media_types_registry, link_relations_registry, collection_roles_registry) # Act with assert_raises(expected_error.__class__) as assert_raises_context: semantic_analyzer.visit(manifest) # Assert eq_(str(assert_raises_context.exception), str(expected_error)) def test_semantic_analyzer_does_correctly_processes_valid_ast(self): manifest = RWPMManifest( metadata=PresentationMetadata("test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), reading_order=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([ Link(href="test", _type=RWPMMediaTypesRegistry.JPEG.key), ]), ), resources=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([ Link(href="test", _type=RWPMMediaTypesRegistry.JPEG.key), ]), ), ) media_types_registry = RWPMMediaTypesRegistry() link_relations_registry = RWPMLinkRelationsRegistry() collection_roles_registry = RWPMCollectionRolesRegistry() semantic_analyzer = RWPMSemanticAnalyzer(media_types_registry, link_relations_registry, collection_roles_registry) # Act semantic_analyzer.visit(manifest)
elif isinstance(property_value, datetime.datetime): property_value = property_value.isoformat() + "Z" if isinstance(rwpm_item, list): result.append(property_value) else: result[property_object.key] = property_value elif isinstance(rwpm_item, RegistryItem): result = rwpm_item.key return result PROQUEST_PUBLICATION_1 = OPDS2Publication( metadata=PresentationMetadata( identifier="urn:proquest.com/document-id/1", title="Publićation # 1", modified=datetime_utc(2020, 1, 31, 0, 0, 0), ), links=LinkList([ Link( href="https://feed.org/document-id/1", rels=[OPDS2LinkRelationsRegistry.ACQUISITION], ) ]), ) PROQUEST_PUBLICATION_2 = OPDS2Publication( metadata=PresentationMetadata( identifier="urn:proquest.com/document-id/2", title="Publication # 2", modified=datetime_utc(2020, 1, 30, 0, 0, 0),
class RWPMSemanticAnalyzerTest(AnalyzerTest): @parameterized.expand([ ( "when_reading_order_link_does_not_have_type_property", RWPMManifest( metadata=PresentationMetadata("test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), reading_order=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([Link(href="test")]), ), ), [ READING_ORDER_SUBCOLLECTION_LINK_MISSING_TYPE_PROPERTY_ERROR( node=Link(href="test"), node_property=Link.type) ], ), ( "when_resources_link_does_not_have_type_property", RWPMManifest( metadata=PresentationMetadata("test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), reading_order=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([ Link(href="test", _type=RWPMMediaTypesRegistry.JPEG.key) ]), ), resources=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([Link(href="test")]), ), ), [ RESOURCES_SUBCOLLECTION_MISSING_LINK_TYPE_PROPERTY_ERROR( node=Link(href="test"), node_property=Link.type) ], ), ( "when_reading_order_and_resource_links_do_not_have_type_property_and_self_link_has_incorrect_href", RWPMManifest( metadata=PresentationMetadata("test"), links=LinkList([ Link( href="example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), reading_order=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([Link(href="test")]), ), resources=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([Link(href="test")]), ), ), [ MANIFEST_SELF_LINK_WRONG_HREF_FORMAT_ERROR( node=Link(href="example.com"), node_property=Link.href), READING_ORDER_SUBCOLLECTION_LINK_MISSING_TYPE_PROPERTY_ERROR( node=Link(href="test"), node_property=Link.type), RESOURCES_SUBCOLLECTION_MISSING_LINK_TYPE_PROPERTY_ERROR( node=Link(href="test"), node_property=Link.type), ], ), ]) def test_semantic_analyzer_raises_error(self, _, manifest, expected_errors): """Ensure that the base semantic analyzer correctly raises errors and saves them in the current context. :param manifest: AST object containing the RWPM-like manifest :type manifest: webpub_manifest_parser.core.ast.Node :param expected_errors: List of expected semantic errors :type expected_errors: List[webpub_manifest_parser.core.analyzer.BaseAnalyzerError] """ # Arrange media_types_registry = RWPMMediaTypesRegistry() link_relations_registry = RWPMLinkRelationsRegistry() collection_roles_registry = RWPMCollectionRolesRegistry() semantic_analyzer = RWPMSemanticAnalyzer(media_types_registry, link_relations_registry, collection_roles_registry) # Act semantic_analyzer.visit(manifest) # Assert self.check_analyzer_errors(semantic_analyzer.context.errors, expected_errors, SemanticAnalyzerError) def test_semantic_analyzer_does_correctly_processes_valid_ast(self): manifest = RWPMManifest( metadata=PresentationMetadata("test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), reading_order=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([ Link(href="test", _type=RWPMMediaTypesRegistry.JPEG.key), ]), ), resources=CompactCollection( role=RWPMCollectionRolesRegistry.READING_ORDER.key, links=LinkList([ Link(href="test", _type=RWPMMediaTypesRegistry.JPEG.key), ]), ), ) media_types_registry = RWPMMediaTypesRegistry() link_relations_registry = RWPMLinkRelationsRegistry() collection_roles_registry = RWPMCollectionRolesRegistry() semantic_analyzer = RWPMSemanticAnalyzer(media_types_registry, link_relations_registry, collection_roles_registry) # Act semantic_analyzer.visit(manifest)
class OPDS2SemanticAnalyzerTest(AnalyzerTest): @parameterized.expand([ ( "when_feed_does_not_contain_neither_publications_nor_navigation_nor_groups", OPDS2Feed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), ), [ MISSING_REQUIRED_FEED_SUB_COLLECTIONS(node=OPDS2Feed(), node_property=None) ], ), ( "when_navigation_link_does_not_contain_title", OPDS2Feed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), navigation=OPDS2Navigation( links=LinkList([Link(href="http://example.com")])), ), [ MISSING_NAVIGATION_LINK_TITLE_ERROR(node=Link(), node_property=Link.title) ], ), ( "when_publication_does_not_contain_acquisition_link", OPDS2Feed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), publications=CollectionList([ OPDS2Publication( metadata=PresentationMetadata(title="Publication 1"), links=LinkList([Link(href="http://example.com")]), ) ]), ), [ MISSING_ACQUISITION_LINK(node=OPDS2Publication(), node_property=None) ], ), ( "when_navigation_contains_both_links_and_publications", OPDS2Feed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), groups=CollectionList([ OPDS2Group( navigation=OPDS2Navigation(links=LinkList([ Link( href="http://example.com", rels=["current"], ) ])), publications=CollectionList([ OPDS2Publication( metadata=PresentationMetadata( title="Publication 1"), links=LinkList( [Link(href="http://example.com")]), ) ]), ) ]), ), [ WRONG_GROUP_STRUCTURE(node=OPDS2Group(), node_property=None), MISSING_ACQUISITION_LINK(node=OPDS2Publication(), node_property=None), MISSING_NAVIGATION_LINK_TITLE_ERROR(node=Link(), node_property=Link.title), ], ), ]) def test_semantic_analyzer_raises_error(self, _, manifest, expected_errors): """Ensure that the OPDS 2.x semantic analyzer correctly raises errors and saves them in the current context. :param manifest: AST object containing the RWPM-like manifest :type manifest: webpub_manifest_parser.core.ast.Node :param expected_errors: List of expected semantic errors :type expected_errors: List[webpub_manifest_parser.core.analyzer.BaseAnalyzerError] """ # Arrange media_types_registry = OPDS2MediaTypesRegistry() link_relations_registry = OPDS2LinkRelationsRegistry() collection_roles_registry = OPDS2CollectionRolesRegistry() semantic_analyzer = OPDS2SemanticAnalyzer(media_types_registry, link_relations_registry, collection_roles_registry) # Act semantic_analyzer.visit(manifest) # Assert self.check_analyzer_errors(semantic_analyzer.context.errors, expected_errors, SemanticAnalyzerError) def test_semantic_analyzer_does_correctly_processes_valid_ast(self): # Arrange feed = OPDS2Feed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList([ Link( href="http://example.com", rels=[RWPMLinkRelationsRegistry.SELF.key], ) ]), publications=CollectionList([ OPDS2Publication( metadata=PresentationMetadata(title="Publication 1"), links=LinkList([ Link( href="http://example.com", rels=[OPDS2LinkRelationsRegistry.ACQUISITION.key], ) ]), ) ]), navigation=OPDS2Navigation(links=LinkList([ Link( href="/new", title="New Publications", _type=OPDS2MediaTypesRegistry.OPDS_FEED, rels=["current"], ) ])), groups=CollectionList([ OPDS2Group( metadata=OPDS2FeedMetadata(title="Group 1"), publications=CollectionList([ OPDS2Publication( metadata=PresentationMetadata( title="Publication 1.1"), links=LinkList([ Link( href="http://example.com", rels=[ OPDS2LinkRelationsRegistry.ACQUISITION. key ], ) ]), ) ]), ) ]), ) media_types_registry = OPDS2MediaTypesRegistry() link_relations_registry = OPDS2LinkRelationsRegistry() collection_roles_registry = OPDS2CollectionRolesRegistry() semantic_analyzer = OPDS2SemanticAnalyzer(media_types_registry, link_relations_registry, collection_roles_registry) semantic_analyzer.visit = MagicMock( side_effect=semantic_analyzer.visit) # Act semantic_analyzer.visit(feed) # Assert semantic_analyzer.visit.assert_has_calls( calls=[ call(feed), call(feed.metadata), call(feed.links), call(feed.links[0]), call(feed.sub_collections), call(feed.publications), call(feed.publications[0]), call(feed.publications[0].metadata), call(feed.publications[0].links), call(feed.publications[0].links[0]), call(feed.publications[0].sub_collections), call(feed.navigation), call(feed.navigation), call(feed.navigation.links), call(feed.navigation.links[0]), call(feed.groups), call(feed.groups[0]), call(feed.groups[0].metadata), call(feed.groups[0].publications), call(feed.groups[0].publications[0]), call(feed.groups[0].publications[0].metadata), call(feed.groups[0].publications[0].links), call(feed.groups[0].publications[0].links[0]), ], any_order=False, )
class ODLSemanticAnalyzerTest(AnalyzerTest): @parameterized.expand( [ ( "when_feed_does_not_contain_publications", ODLFeed(metadata=OPDS2FeedMetadata(title="test"), links=LinkList()), [ ODL_FEED_MISSING_PUBLICATIONS_SUBCOLLECTION_ERROR( node=ODLFeed(metadata=OPDS2FeedMetadata(title="test")), node_property=ODLFeed.publications, ), MANIFEST_MISSING_SELF_LINK_ERROR( node=ODLFeed(metadata=OPDS2FeedMetadata(title="test")), node_property=None, ), ], ), ( "when_feed_contains_groups", ODLFeed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList(), groups=CollectionList([OPDS2Group()]), ), [ ODL_FEED_MISSING_PUBLICATIONS_SUBCOLLECTION_ERROR( node=ODLFeed(metadata=OPDS2FeedMetadata(title="test")), node_property=ODLFeed.publications, ), MANIFEST_MISSING_SELF_LINK_ERROR( node=ODLFeed(metadata=OPDS2FeedMetadata(title="test")), node_property=None, ), ODL_FEED_CONTAINS_REDUNDANT_GROUPS_SUBCOLLECTIONS_ERROR( node=ODLFeed(metadata=OPDS2FeedMetadata(title="test")), node_property=ODLFeed.groups, ), ], ), ( "when_feed_contains_facets", ODLFeed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList(), facets=CollectionList([OPDS2Facet()]), ), [ ODL_FEED_MISSING_PUBLICATIONS_SUBCOLLECTION_ERROR( node=ODLFeed(metadata=OPDS2FeedMetadata(title="test")), node_property=ODLFeed.publications, ), MANIFEST_MISSING_SELF_LINK_ERROR( node=ODLFeed(metadata=OPDS2FeedMetadata(title="test")), node_property=None, ), ODL_FEED_CONTAINS_REDUNDANT_FACETS_SUBCOLLECTIONS_ERROR( node=ODLFeed(metadata=OPDS2FeedMetadata(title="test")), node_property=ODLFeed.facets, ), ], ), ( "when_feed_contains_navigation", ODLFeed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList(), navigation=OPDS2Navigation(), ), [ ODL_FEED_MISSING_PUBLICATIONS_SUBCOLLECTION_ERROR( node=ODLFeed(metadata=OPDS2FeedMetadata(title="test")), node_property=ODLFeed.publications, ), MANIFEST_MISSING_SELF_LINK_ERROR( node=ODLFeed(metadata=OPDS2FeedMetadata(title="test")), node_property=None, ), ODL_FEED_CONTAINS_REDUNDANT_NAVIGATION_SUBCOLLECTION_ERROR( node=ODLFeed(metadata=OPDS2FeedMetadata(title="test")), node_property=ODLFeed.navigation, ), ], ), ( "when_publication_does_not_contain_neither_licenses_nor_oa_link", ODLFeed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList( [ Link( href="http://example.com", rels=[LinkRelationsRegistry.SELF.key], ) ] ), publications=CollectionList( [ ODLPublication( metadata=PresentationMetadata(title="Publication 1"), licenses=CollectionList(), ) ] ), ), [ ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR( node=ODLPublication( metadata=PresentationMetadata(title="Publication 1") ), node_property=None, ) ], ), ( "when_publication_does_not_contain_neither_licenses_nor_oa_link", ODLFeed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList( [ Link( href="http://example.com", rels=[LinkRelationsRegistry.SELF.key], ) ] ), publications=CollectionList( [ ODLPublication( metadata=PresentationMetadata(title="Publication 1"), links=LinkList([Link(href="http://example.com")]), ) ] ), ), [ ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR( node=ODLPublication( metadata=PresentationMetadata(title="Publication 1") ), node_property=None, ) ], ), ( "when_license_does_not_contain_self_link_and_borrow_link", ODLFeed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList( [ Link( href="http://example.com", rels=[LinkRelationsRegistry.SELF.key], ) ] ), publications=CollectionList( [ ODLPublication( metadata=PresentationMetadata(title="Publication 1"), links=LinkList([Link(href="http://example.com")]), licenses=CollectionList( [ ODLLicense( metadata=ODLLicenseMetadata( identifier="license-1", formats=["text/html"], created=datetime.datetime( 2021, 1, 1, 0, 0, 0 ), ) ) ] ), ) ] ), ), [ ODL_LICENSE_MUST_CONTAIN_SELF_LINK_TO_LICENSE_INFO_DOCUMENT_ERROR( node=ODLLicense( metadata=ODLLicenseMetadata(identifier="license-1") ), node_property=None, ), ODL_LICENSE_MUST_CONTAIN_CHECKOUT_LINK_TO_LICENSE_STATUS_DOCUMENT_ERROR( node=ODLLicense( metadata=ODLLicenseMetadata(identifier="license-1") ), node_property=None, ), ], ), ] ) def test_semantic_analyzer_raises_error(self, _, manifest, expected_errors): """Ensure that the ODL 2.x semantic analyzer correctly raises errors and saves them in the current context. :param manifest: AST object containing the RWPM-like manifest :type manifest: webpub_manifest_parser.odl.ast.ODLFeed :param expected_errors: List of expected semantic errors :type expected_errors: List[webpub_manifest_parser.core.analyzer.BaseAnalyzerError] """ # Arrange media_types_registry = OPDS2MediaTypesRegistry() link_relations_registry = OPDS2LinkRelationsRegistry() collection_roles_registry = OPDS2CollectionRolesRegistry() semantic_analyzer = ODLSemanticAnalyzer( media_types_registry, link_relations_registry, collection_roles_registry ) # Act semantic_analyzer.visit(manifest) # Assert self.check_analyzer_errors( semantic_analyzer.context.errors, expected_errors, SemanticAnalyzerError )
class SemanticAnalyzerTest(AnalyzerTest): @parameterized.expand([ ( "when_manifest_link_rel_property_is_missing", Manifestlike( metadata=PresentationMetadata(title="Manifest # 1"), links=LinkList([Link(href="http://example.com")]), ), [ MANIFEST_LINK_MISSING_REL_PROPERTY_ERROR( node=Link(href="http://example.com"), node_property=Link.rels), MANIFEST_MISSING_SELF_LINK_ERROR( node=Manifestlike(metadata=PresentationMetadata( identifier="Manifest # 1")), node_property=None, ), ], ), ( "when_manifest_self_link_is_missing", Manifestlike( metadata=PresentationMetadata(title="Manifest # 1"), links=LinkList( [Link( href="http://example.com", rels=["test"], )]), ), [ MANIFEST_MISSING_SELF_LINK_ERROR( node=Manifestlike(metadata=PresentationMetadata( title="Manifest # 1")), node_property=None, ) ], ), ( "when_manifest_self_link_has_wrong_href", Manifestlike( metadata=PresentationMetadata(title="Manifest # 1"), links=LinkList([ Link( href="example.com", rels=[LinkRelationsRegistry.SELF.key], ) ]), ), [ MANIFEST_SELF_LINK_WRONG_HREF_FORMAT_ERROR( node=Link(href="example.com"), node_property=Link.href) ], ), ( "when_manifest_link_rel_property_is_missing_and_self_link_has_incorrect_href", Manifestlike( metadata=PresentationMetadata(title="Manifest # 1"), links=LinkList([ Link(href="http://example.com"), Link( href="example.com", rels=[LinkRelationsRegistry.SELF.key], ), ]), ), [ MANIFEST_LINK_MISSING_REL_PROPERTY_ERROR( node=Link(href="http://example.com"), node_property=Link.rels), MANIFEST_SELF_LINK_WRONG_HREF_FORMAT_ERROR( node=Link(href="example.com"), node_property=Link.href), ], ), ]) def test_semantic_analyzer_raises_error(self, _, manifest, expected_errors): """Ensure that the base semantic analyzer correctly raises errors and saves them in the current context. :param manifest: AST object containing the RWPM-like manifest :type manifest: webpub_manifest_parser.core.ast.Manifestlike :param expected_errors: List of expected semantic errors :type expected_errors: List[webpub_manifest_parser.core.analyzer.BaseAnalyzerError] """ # Arrange media_types_registry = Registry() link_relations_registry = Registry() collection_roles_registry = Registry() semantic_analyzer = SemanticAnalyzer(media_types_registry, link_relations_registry, collection_roles_registry) # Act semantic_analyzer.visit(manifest) # Assert self.check_analyzer_errors(semantic_analyzer.context.errors, expected_errors, SemanticAnalyzerError)
def test_import(self, importer, mock_get, datasource, db): """Ensure that ODL2Importer2 correctly processes and imports the ODL feed encoded using OPDS 2.x. NOTE: `freeze_time` decorator is required to treat the licenses in the ODL feed as non-expired. """ # Arrange moby_dick_license = LicenseInfoHelper( license=LicenseHelper( identifier="urn:uuid:f7847120-fc6f-11e3-8158-56847afe9799", concurrency=10, checkouts=30, expires="2016-04-25T12:25:21+02:00", ), left=30, available=10, ) mock_get.add(moby_dick_license) feed = self.get_data("feed.json") configuration_storage = ConfigurationStorage(importer) configuration_factory = ConfigurationFactory() with configuration_factory.create( configuration_storage, db, ODL2APIConfiguration ) as configuration: configuration.skipped_license_formats = json.dumps(["text/html"]) # Act imported_editions, pools, works, failures = importer.import_from_feed(feed) # Assert # 1. Make sure that there is a single edition only assert isinstance(imported_editions, list) assert 1 == len(imported_editions) [moby_dick_edition] = imported_editions assert isinstance(moby_dick_edition, Edition) assert moby_dick_edition.primary_identifier.identifier == "978-3-16-148410-0" assert moby_dick_edition.primary_identifier.type == "ISBN" assert u"Moby-Dick" == moby_dick_edition.title assert u"eng" == moby_dick_edition.language assert u"eng" == moby_dick_edition.language assert EditionConstants.BOOK_MEDIUM == moby_dick_edition.medium assert u"Herman Melville" == moby_dick_edition.author assert 1 == len(moby_dick_edition.author_contributors) [moby_dick_author] = moby_dick_edition.author_contributors assert isinstance(moby_dick_author, Contributor) assert u"Herman Melville" == moby_dick_author.display_name assert u"Melville, Herman" == moby_dick_author.sort_name assert 1 == len(moby_dick_author.contributions) [moby_dick_author_author_contribution] = moby_dick_author.contributions assert isinstance(moby_dick_author_author_contribution, Contribution) assert moby_dick_author == moby_dick_author_author_contribution.contributor assert moby_dick_edition == moby_dick_author_author_contribution.edition assert Contributor.AUTHOR_ROLE == moby_dick_author_author_contribution.role assert datasource == moby_dick_edition.data_source assert u"Test Publisher" == moby_dick_edition.publisher assert datetime.date(2015, 9, 29) == moby_dick_edition.published assert u"http://example.org/cover.jpg" == moby_dick_edition.cover_full_url assert ( u"http://example.org/cover-small.jpg" == moby_dick_edition.cover_thumbnail_url ) # 2. Make sure that license pools have correct configuration assert isinstance(pools, list) assert 1 == len(pools) [moby_dick_license_pool] = pools assert isinstance(moby_dick_license_pool, LicensePool) assert moby_dick_license_pool.identifier.identifier == "978-3-16-148410-0" assert moby_dick_license_pool.identifier.type == "ISBN" assert not moby_dick_license_pool.open_access assert 30 == moby_dick_license_pool.licenses_owned assert 10 == moby_dick_license_pool.licenses_available assert 2 == len(moby_dick_license_pool.delivery_mechanisms) moby_dick_epub_adobe_drm_delivery_mechanism = ( self._get_delivery_mechanism_by_drm_scheme_and_content_type( moby_dick_license_pool.delivery_mechanisms, MediaTypes.EPUB_MEDIA_TYPE, DeliveryMechanism.ADOBE_DRM, ) ) assert moby_dick_epub_adobe_drm_delivery_mechanism is not None moby_dick_epub_lcp_drm_delivery_mechanism = ( self._get_delivery_mechanism_by_drm_scheme_and_content_type( moby_dick_license_pool.delivery_mechanisms, MediaTypes.EPUB_MEDIA_TYPE, DeliveryMechanism.LCP_DRM, ) ) assert moby_dick_epub_lcp_drm_delivery_mechanism is not None assert 1 == len(moby_dick_license_pool.licenses) [moby_dick_license] = moby_dick_license_pool.licenses assert ( "urn:uuid:f7847120-fc6f-11e3-8158-56847afe9799" == moby_dick_license.identifier ) assert ( "http://www.example.com/get{?id,checkout_id,expires,patron_id,passphrase,hint,hint_url,notification_url}" == moby_dick_license.checkout_url ) assert "http://www.example.com/status/294024" == moby_dick_license.status_url assert ( datetime.datetime(2016, 4, 25, 10, 25, 21, tzinfo=datetime.timezone.utc) == moby_dick_license.expires ) assert 30 == moby_dick_license.checkouts_left assert 10 == moby_dick_license.checkouts_available # 3. Make sure that work objects contain all the required metadata assert isinstance(works, list) assert 1 == len(works) [moby_dick_work] = works assert isinstance(moby_dick_work, Work) assert moby_dick_edition == moby_dick_work.presentation_edition assert 1 == len(moby_dick_work.license_pools) assert moby_dick_license_pool == moby_dick_work.license_pools[0] # 4. Make sure that the failure is covered assert 1 == len(failures) huck_finn_failures = failures["9781234567897"] assert 1 == len(huck_finn_failures) [huck_finn_failure] = huck_finn_failures assert isinstance(huck_finn_failure, CoverageFailure) assert "9781234567897" == huck_finn_failure.obj.identifier huck_finn_semantic_error = ( ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR( node=ODLPublication( metadata=PresentationMetadata(identifier="urn:isbn:9781234567897") ), node_property=None, ) ) assert str(huck_finn_semantic_error) == huck_finn_failure.exception
class SyntaxAnalyzerTest(AnalyzerTest): @parameterized.expand([ # 1. Ensure that the syntax analyzer correctly restores # from the error related to top missing `links` property and continues parsing `metadata` property. ( "top_required_links_property_is_missing", { "metadata": { "modified": "2021-01-01T00:00:00Z" } }, Manifestlike(metadata=PresentationMetadata( modified=datetime.datetime(2021, 1, 1, tzinfo=tzutc()), languages=[], authors=[], translators=[], editors=[], artists=[], illustrators=[], letterers=[], pencilers=[], colorists=[], inkers=[], narrators=[], contributors=[], publishers=[], imprints=[], subjects=[], )), [ MissingPropertyError(Manifestlike(), Manifestlike.links), MissingPropertyError(PresentationMetadata(), Metadata.title), ], ), # 2. Ensure that the syntax analyzer restores from the error related to incorrect `modified` property # and continues parsing other nested properties (`subtitle`, etc.). ( "incorrect_metadata_modified_property", { "metadata": { "subtitle": "Subtitle", "modified": "202X" } }, Manifestlike(metadata=PresentationMetadata( subtitle="Subtitle", modified=None, languages=[], authors=[], translators=[], editors=[], artists=[], illustrators=[], letterers=[], pencilers=[], colorists=[], inkers=[], narrators=[], contributors=[], publishers=[], imprints=[], subjects=[], )), [ MissingPropertyError(Manifestlike(), Manifestlike.links), MissingPropertyError(PresentationMetadata(), Metadata.title), SyntaxAnalyzerError( PresentationMetadata(), Metadata.modified, u"Value '202X' is not a correct date & time value: " u"it does not comply with ISO 8601 date & time formatting rules", ), ], ), # 3. Ensure that the syntax analyzer correctly parses required property `title` # even though one of the nested properties (`modified`) is not correct. ( "incorrect_metadata_modified_property", { "metadata": { "title": "Title", "subtitle": "Subtitle", "modified": "202X", } }, Manifestlike(metadata=PresentationMetadata( title="Title", subtitle="Subtitle", modified=None, languages=[], authors=[], translators=[], editors=[], artists=[], illustrators=[], letterers=[], pencilers=[], colorists=[], inkers=[], narrators=[], contributors=[], publishers=[], imprints=[], subjects=[], )), [ MissingPropertyError(Manifestlike(), Manifestlike.links), SyntaxAnalyzerError( PresentationMetadata(), Metadata.modified, u"Value '202X' is not a correct date & time value: " u"it does not comply with ISO 8601 date & time formatting rules", ), ], ), ]) def test(self, _, raw_manifest, expected_manifest_ast, expected_errors): """Ensure that syntax analyzer correctly recovers from syntax errors. :param raw_manifest: Python dictionary containing an RWPM-like manifest :type raw_manifest: Dict :param expected_manifest_ast: AST object representing an RWPM-like manifest parsed from `raw_manifest` :type expected_manifest_ast: Node :param expected_errors: List of expected syntax errors :type expected_errors: List[webpub_manifest_parser.core.analyzer.BaseAnalyzerError] """ # Arrange analyzer = TestSyntaxAnalyzer() manifest_ast = analyzer.analyze(raw_manifest) # Act self.assertEqual(expected_manifest_ast, manifest_ast) # Assert self.check_analyzer_errors(analyzer.context.errors, expected_errors, SyntaxAnalyzerError)