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(
Example #3
0
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,
        )
Example #4
0
    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)
Example #6
0
            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),
Example #7
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)
Example #8
0
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)
Example #11
0
    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)