Example #1
0
def test_skip_positive():
    """The ``feed_image`` hook can enforce handling in a positive
    manner, e.g. by instructing to continue with image processing right
    away while skipping other ``feed_image`` callbacks, which then won't
    have a chance to do their validation.
    """

    addin1 = count_addin()
    addin2 = count_addin()
    ADDINS = [handle_feed_images, addin1, addin2]

    class TestFeed(BaseImageTestFeed):
        def pass1(feed):
            # first pass, everything is normal
            assert addin1.feed_image == 1
            assert addin2.feed_image == 1
            assert addin1.feed_image_updated == 1
            assert addin2.feed_image_updated == 1

            # if we instruct addin1's ``feed_image`` hook to
            # force a succeed...
            addin1.returnvalue["feed_image"] = False

        def pass2(feed):
            # ...then this time addin2's ``feed_image`` hook will
            # never even be called.
            assert addin2.feed_image == 1
            assert addin1.feed_image == 2

            # although the other handlers in both addins are still triggered.
            assert addin1.feed_image_updated == 2
            assert addin2.feed_image_updated == 2

    feedev.testcaller()
def test_original_format():
    # handle_feed_images not specified, this tests the dependency
    ADDINS = [store_feed_images((tempdir, '%(model_id)s.%(extension)s'))]

    class TestImage(feedev.File):
        content = ValidPNGImage
        url = 'http://bla/stuff.png'

    class TestFeed(feedev.Feed):
        content = FeedWithImage % TestImage.url

        def pass1(feed):
            # check that the image was stored
            imgfile = path.join(tempdir, '%s.%s' % (feed.id, 'png'))
            assert path.exists(imgfile)

            # Make sure it was written correctly; this will usually not
            # be true if the image was saved using PIL, which is the
            # reason why we explicitely specify a url with an extension
            # to ``TestImage`` (otherwise, the image would be loaded into
            # PIL to defer the extension from the content), and PIL
            # subsequently used to save the file.
            assert open(imgfile, 'rb').read() == TestImage.content


    feedev.testcaller()
def test_restrict_mediatype():
    """Test mime type validation.
    """
    counter = image_hook_counter()
    ADDINS = [feed_image_restrict_mediatypes(('image/png', 'image/gif')), counter]

    class TestFeedImage(feedev.File):
        content = ""
        def headers(p):
            if p == 1:   return {'Content-Type': 'text/plain'}
            elif p == 2: return {'Content-Type': 'image/jpeg'}
            elif p == 3: return {'Content-Type': 'image/png; charset=ISO-8859-1'}  # charsets are correctly parsed out
            elif p == 4: return {'Content-Type': 'image/png'}

    class TestFeed(feedev.Feed):
        content = FeedWithImage % (TestFeedImage.url)

        def pass1(feed):
            assert counter.success == 0
        def pass2(feed):
            assert counter.success == 0
        def pass3(feed):
            assert counter.success == 1
        def pass4(feed):
            assert counter.success == 2

    feedev.testcaller()
def test_update_every():
    """Test timed update restriction.
    """
    counter = image_hook_counter()
    ADDINS = [feed_image_restrict_frequency(delta=20), counter]

    class TestFeedImage(feedev.File):
        pass

    class TestFeed(feedev.Feed):
        content = FeedWithImage % TestFeedImage.url

        def pass1(feed):
            assert counter.success == 1

        def pass2(feed):
            # we're not yet updating the image again...
            assert counter.success == 1
            # ...this also doesn't count as a failure (an existing images would be kept)
            assert counter.failure == 0
            # ...but let's advance the clock for the next pass
            datetime.datetime.modify(seconds=30)

        def pass3(feed):
            assert counter.success == 2

    MockDateTime.install()
    try:
        feedev.testcaller()
    finally:
        MockDateTime.uninstall()
def test_failure_reset():
    """Image fails if no valid extension (=no image).
    """

    counter = image_hook_counter()
    ADDINS = [feed_image_restrict_extensions(('png', 'gif')), counter]

    class ValidExtensionImage(feedev.File):
        url = 'http://images/image.png'
    class InvalidExtensionImage(feedev.File):
        url = 'http://images/image.jpeg'

    class TestFeed(feedev.Feed):
        def content(p):
            if p == 1:   image = ValidExtensionImage
            elif p == 2: image = InvalidExtensionImage
            return FeedWithImage % saxutils.escape(image.url)

        def pass1(feed):
            assert counter.failure == 0

        def pass2(feed):
            # invalid extension cause the image to fail completely
            assert counter.failure == 1

    feedev.testcaller()
def test_thumbnails():
    # handle_feed_images not specified, this tests the dependency
    ADDINS = [feed_image_thumbnails(
                ((100,100),),
                (tempdir, '%(model_id)s-thumb-%(size)s.gif'),
                format='gif')]

    class TestImage(feedev.File):
        content = ValidPNGImage
        url = 'http://bla/stuff.png'

    class TestFeed(feedev.Feed):
        content = FeedWithImage % TestImage.url

        def pass1(feed):
            # check that the image was stored
            imgfile = path.join(tempdir, '%s-thumb-%s.%s' % (feed.id, '100x100', 'gif'))
            assert path.exists(imgfile)

            # check that it was stored as a true gif file, as requested
            from PIL import Image
            i = Image.open(imgfile)
            assert i.format == 'GIF'
            # ... with the requested size
            assert i.size == (100,100)

    feedev.testcaller()
def test_enclosure_addin():
    """Test the problem through and in the context of the enclosure
    handling addins (enclosures are one of the problem areas with respect
    to this bug.
    """

    ADDINS = [
        # both plugins separately exhibited the bug in the passed,
        # though note that ``collect_enclosure_data`` depends on
        # ``store_encloures`` and cannot be used without it
        store_enclosures(),
        collect_enclosure_data('length', 'type'),
    ]

    class BozoFeed(feedev.Feed):
        content = """
            <rss>
            <channel>
                <item>
                    <guid>item1</guid>
                    <enclosure href="http://h.com/p/f.mp3" length="10" />
                <!-- item closing tag missing -->
            </channel></rss>
        """

        def pass1(feed):
            # If the patch were not applied, we'd expect a Storm
            # "unicode expected" exception to be raised during
            # addin execution, the assertion here is just for
            # completeness sake.
            href = feed.items.one().enclosures.one().href
            assert href == 'http://h.com/p/f.mp3'
            assert type(href) == unicode

    feedev.testcaller()
def test_max_size_by_content_length():
    """Test validation against ``Content-Length`` header.
    """
    counter = image_hook_counter()
    ADDINS = [feed_image_restrict_size(max_size=10), counter]

    class TestFeedImage(feedev.File):
        content = ""
        def headers(p):
            if p == 1: return {'Content-Length': '15'}  # nose that those are strings, just like real headers # TODO: make the testframework ensure that header values are always strings
            else: return {'Content-Length': '5'}

    class TestFeed(feedev.Feed):
        content = FeedWithImage % (TestFeedImage.url)

        def pass1(feed):
            # at this point, the image is too large and is ignored
            assert counter.success == 0
            # also, processing failed (=no image)
            assert counter.failure == 1

        def pass2(feed):
            # it's been fixed, we meet the limit
            assert counter.success == 1

    feedev.testcaller()
def test_max_size():
    """Test live validation of file size, effective in case a
    Content-Length header is missing.
    """
    counter = image_hook_counter()
    ADDINS = [feed_image_restrict_size(max_size=10), image_reader, counter]

    class TestFeedImage(feedev.File):
        def content(p):
            if p == 1: return "b"*15
            else: return "b"*5

    class TestFeed(feedev.Feed):
        content = FeedWithImage % TestFeedImage.url

        def pass1(feed):
            # at this point, the image is to large and is ignored
            assert counter.success == 0
            # also, processing failed (=no image)
            assert counter.failure == 1

        def pass2(feed):
            # it's been fixed, we meet the limit
            assert counter.success == 1

    feedev.testcaller()
def test_restrict_extensions():
    """Test file extension validation.
    """
    counter = image_hook_counter()
    ADDINS = [feed_image_restrict_extensions(('png', 'gif')), counter]

    class JpegImage(feedev.File):
        url = 'http://images/image.jpeg'
    class NonImageFile(feedev.File):
        url = 'http://documents/stuff.xml'
    class PngImage(feedev.File):
        url = 'http://images/image.png'
    class ImageWithQueryString(feedev.File):
        url = 'http://images/image.png?q=x&r=3'
    class ImageWitoutExtension(feedev.File):
        url = 'http://images/image'
    class ImageWithExtensionInQuery(feedev.File):
        url = 'http://images/image?x=66&filename=.png'
    class ImageWithoutExtensionButValidContent(feedev.File):
        url = 'http://images/validimage'
        content = ValidPNGImage

    class TestFeed(feedev.Feed):
        def content(p):
            if p == 1:   image = JpegImage
            elif p == 2: image = NonImageFile
            elif p == 3: image = PngImage
            elif p == 4: image = ImageWithQueryString
            elif p == 5: image = ImageWitoutExtension
            elif p == 6: image = ImageWithExtensionInQuery
            elif p == 7: image = ImageWithoutExtensionButValidContent
            return FeedWithImage % saxutils.escape(image.url)

        def pass1(feed):
            assert counter.success == 0
        def pass2(feed):
            assert counter.success == 0
        def pass3(feed):
            assert counter.success == 1
        def pass4(feed):
            assert counter.success == 2
        def pass5(feed):
            # Image has no extension in URL, and extension cannot be
            # determined via content type or content (since the former
            # is missing and the latter is invalid). In this case,
            # we fail, the image is not handled.
            assert counter.success == 2
        def pass6(feed):
            # As in pass5, no extension is available. The extension
            # from the querystring is currently ignored.
            assert counter.success == 2
        def pass7(feed):
            # the image is once again lacking an extension, but this
            # time the content is valid and the extension can be
            # deferred from it.
            assert counter.success == 3

    feedev.testcaller()
Example #11
0
def test_basic():
    counter = count_addin()
    ADDINS = [handle_feed_images, counter]

    class HookTestFeed(BaseImageTestFeed):
        content = """
        <rss><channel>
            <image><url>http://example.org/image.png</url></image>
        </channel></rss>
        """

        def pass1(feed):
            # pass1: test everything ok, no errors
            assert counter.feed_image == 1  # always called
            assert counter.update_feed_image == 1  # called bc prev success
            assert counter.feed_image_updated == 1  # called bc prev success
            assert counter.feed_image_failed == 0  # not called on success

            # pass2: test failure in feed_image
            counter.fail = "feed_image"

        def pass2(feed):
            assert counter.feed_image == 2  # always called
            assert counter.update_feed_image == 1  # unchanged bc error
            assert counter.feed_image_updated == 1  # unchanged bc error
            assert counter.feed_image_failed == 1  # called due to error

            # pass3: test failure in update_feed_image
            counter.fail = "update_feed_image"

        def pass3(feed):
            assert counter.feed_image == 3  # always called
            assert counter.update_feed_image == 2  # called bc success
            assert counter.feed_image_updated == 1  # unchanged bc error
            assert counter.feed_image_failed == 2  # called due to error

            # pass4: test failure in feed_image_updated
            # (although it really shouldn't happen)
            counter.fail = "feed_image_updated"

        def pass4(feed):
            assert counter.feed_image == 4  # always called
            assert counter.update_feed_image == 3  # called bc success
            assert counter.feed_image_updated == 2  # called bc success
            assert counter.feed_image_failed == 3  # called due to error

            # pass5: test feed_image non-error stop
            counter.returnvalue["feed_image"] = True

        def pass5(feed):
            assert counter.feed_image == 5  # always called
            assert counter.update_feed_image == 3  # unchanged bc skip
            assert counter.feed_image_updated == 2  # unchanged bc skip
            assert counter.feed_image_failed == 3  # not called on skip

    feedev.testcaller()
Example #12
0
def test_basic():
    """
    Only the feeds in this local namespace are used, ``GlobalFeed``
    is ignored.
    """

    class LocalFeed1(GlobalFeed):
        pass

    class LocalFeed2(GlobalFeed):
        pass

    feedev.testcaller()
    assert called == 2
def test_basic():
    ADDINS = [
        # some unicode fields, both "special" generated fields
        collect_feed_image_data('href', 'title', 'extension', 'filename')
    ]

    class TestFeed(feedev.Feed):
        content = """
        <rss><channel>
            <title>test-feed</title>
            <image>
                {% =1 %}
                <url>http://example.org/blog/image.jpg</url>
                <title>Example.org Cover</title>
                <link>http://example.org/</link>
                {% end %}
                {% =2 %}
                <url>http://example.org/new-cover.png</url>
                {% end %}
            </image>
        </channel></rss>
        """

        def pass1(feed):
            # initial values are picked up
            assert feed.image_title == 'Example.org Cover'
            assert feed.image_href == 'http://example.org/blog/image.jpg'
            assert feed.image_extension == 'jpg'
            assert feed.image_filename == 'image.jpg'

            # [bug] Ensure those fields we are checking are actually,
            # really, model fields, avaible in the database, not just
            # attributes, while the real fields are using their standard
            # name (i.e. "title" instead of "image_title").
            assert 'image_title' in [c._detect_attr_name(feed.__class__)
                                     for c in feed._storm_columns.keys()]

        def pass2(feed):
            # changed values are picked up
            assert feed.image_title == ''
            assert feed.image_href == 'http://example.org/new-cover.png'
            assert feed.image_extension == 'png'
            assert feed.image_filename == 'new-cover.png'

    feedev.testcaller()
def test_bozo():
    ADDINS = [collect_feed_image_data('href')]

    class BozoFeed(feedev.Feed):
        content = """
        <rss><channel>
            <title>test-feed</title>
            <image>
                <url>http://example.org/blog/image.jpg</url>
        </channel></rss>
        """

        def pass1(feed):
            # feed is bozo (image tag not closed), but addin is active
            # nevertheless.
            assert feed.image_href == 'http://example.org/blog/image.jpg'

    feedev.testcaller()
def test_unicode():
    """Regression test for a bug that caused storm to raise an
    "Expected unicode" TypeError in certain circumstances (the addin
    was trying to assign a str).
    """
    ADDINS = [collect_feed_image_data('href', 'extension')]

    class ImgWithExtByContent(feedev.File):
        url = 'http://images/validimage'
        content = ValidPNGImage

    class ImgWithExtByHeaders(feedev.File):
        url = 'http://images/validimage'
        headers = {'Content-Type': 'image/gif; charset=utf8'}

    class BozoFeed(feedev.Feed):
        content = """
        <rss><channel>
            <image>
                <!-- extension from url is unicode -->
                {% =1 %}<url>http://example.org/blog/image.jpg</url>{% end %}
                <!-- extension read from headers is unicode -->
                {% =2 %}<url>"""+ImgWithExtByHeaders.url+"""</url>{% end %}
                <!-- extension from pil content is unicode -->
                {% =3 %}<url>"""+ImgWithExtByContent.url+"""</url>{% end %}
            </image>
        </channel></rss>
        """

        # These assertions are here for completion's sake, but
        # normally shouldn't ever fail, since the bug we are
        # regression here really raises a TypeError.
        def pass1(feed):
            assert type(feed.image_href) == unicode
            assert type(feed.image_extension) == unicode

        def pass2(feed):
            assert type(feed.image_extension) == unicode

        def pass3(feed):
            assert type(feed.image_extension) == unicode


    feedev.testcaller()
def test_force_format():
    ADDINS = [handle_feed_images,
              store_feed_images((tempdir, '%(model_id)s.gif'), format='gif')]

    class TestImage(feedev.File):
        content = ValidPNGImage

    class TestFeed(feedev.Feed):
        content = FeedWithImage % TestImage.url

        def pass1(feed):
            # check that the image was stored
            imgfile = path.join(tempdir, '%s.%s' % (feed.id, 'gif'))
            assert path.exists(imgfile)

            # check that it was stored as a true gif file
            from PIL import Image
            assert Image.open(imgfile).format == 'GIF'


    feedev.testcaller()
def test_custom_fieldname():
    """[bug] Make sure that the extended syntax, that allows the collector
    to support a "standard field" field by the name X, but map it to a
    model field with a different name, works as expected.

    In particular we had a bug initially that caused the wrong field to be
    added to the generated models.
    """

    class my_collector(base_data_collector):
        model_name = 'feed'
        standard_fields = {
            'title': {'target': 'my_title_field', 'field': (Unicode, (), {})}
        }

    ADDINS = [my_collector('title')]

    class MyFeed(test.Feed):
        def pass1(feed):
            assert hasattr(feed, 'my_title_field')
            assert hasattr(db.models.Feed, 'my_title_field')

    test.testcaller()
def test_failure_reset():
    """Image fails if no valid extension (=no image).
    """

    counter = image_hook_counter()
    ADDINS = [feed_image_restrict_mediatypes(('image/png', 'image/gif')), counter]

    class TestFeedImage(feedev.File):
        content = ""
        def headers(p):
            if p == 1:   return {'Content-Type': 'image/png'}
            elif p == 2: return {'Content-Type': 'image/jpeg'}

    class TestFeed(feedev.Feed):
        content = FeedWithImage % TestFeedImage.url

        def pass1(feed):
            assert counter.failure == 0

        def pass2(feed):
            # invalid media types cause the image to fail completely
            assert counter.failure == 1

    feedev.testcaller()
Example #19
0
def test_download_chunk():
    """Sepcial test for the ``feed_image_download_chunk`` hook, that
    is triggered while an image is downloaded.
    """

    class force_download_dummy(addins.base):
        def on_update_feed_image(self, feed, image_dict, image):
            image.chunk_size = 10  # read 10 bytes at one time
            image.data.read()

    counter = count_addin()
    ADDINS = [handle_feed_images, force_download_dummy, counter]

    class TestImage(feedev.File):
        content = "a" * 35  # 35 bytes total size

    class TestFeed(feedev.Feed):
        content = FeedWithImage % TestImage.url

        def pass1(feed):
            # 34 bytes ala 10 byte blocks = 4 reads
            assert counter.feed_image_download_chunk == 4

    feedev.testcaller()
def test_unsupported_extensions():
    """Regression test for a bug that caused a ``KeyError`` to be raised
    when the extension of a target filename of a thumbnail did not refer
    to a valid image format, as supported by PIL.

    Instead, we want the thumbnail to be saved with whatever format the
    image originally has.

    The same bug also affected normal saving of a feed image (via the
    ``store_feed_images`` addin) under certain circumstances (when saved
    through PIL), and this is tested in ``test_remote_image.py``.
    """
    ADDINS = [feed_image_thumbnails(
                ((100,100),),
                (tempdir, '%(model_id)s-thumb'))]  # note: without extension

    class TestImage(feedev.File):
        content = ValidPNGImage
        url = 'http://bla/strange-extension.aspx'

    class TestFeed(feedev.Feed):
        content = FeedWithImage % TestImage.url

        def pass1(feed):
            # check that the image was stored
            imgfile = path.join(tempdir, '%s-thumb' % (feed.id))
            assert path.exists(imgfile)

            # check that it was stored as a png file, the source image format
            from PIL import Image
            i = Image.open(imgfile)
            assert i.format == 'PNG'

            # and most importantly, no exception is raised

    feedev.testcaller()
def test_special_fields():
    """Test the special fields that every subclass will be able to provide.
    """

    class my_collector(base_data_collector):
        model_name = 'feed'
        standard_fields = {}
        def on_after_parse(self, feed, data_dict):
            return self._process(feed, data_dict.feed)

    ADDINS = [my_collector(__now='last_processed')]

    class BozoFeed(test.Feed):
        # make sure the content here is not a valid feed, we want to make
        # sure that this works even when feed the is bozo.
        content = """<bozo>"""

        def pass1(feed):
            assert hasattr(feed, 'last_processed')
            assert hasattr(db.models.Feed, 'last_processed')
            # field should now have a value not too far from right now
            assert abs(feed.last_processed - datetime.datetime.utcnow()).seconds < 10

    test.testcaller()
def test_failure_reset():
    """Image data is cleared if the image fails to process.
    """

    class FailDummy(addins.base):
        active = False
        def on_update_feed_image(self, *a, **kw):
            if self.active:
                raise ImageError()
    fail_dummy = FailDummy
    ADDINS = [collect_feed_image_data('href', ), fail_dummy]

    class TestFeed(feedev.Feed):
        content = """
        <rss><channel>
            <title>test-feed</title>
            <image>
                <url>http://example.org/blog/image.jpg</url>
                <title>Example.org Cover</title>
                <link>http://example.org/</link>
            </image>
        </channel></rss>
        """

        def pass1(feed):
            # initially, the value is picked up
            assert feed.image_href == 'http://example.org/blog/image.jpg'

            # ...but in the next pass, image handling will fail...
            fail_dummy.active = True

        def pass2(feed):
            # ...and the value is removed.
            assert feed.image_href == ''

    feedev.testcaller()
def test_basic():
    """Test that hooks are triggered correctly, depending on whether a
    feed image is given or not.

    This is in addition to the specific tests for each hook.
    """
    counter = image_hook_counter()
    ADDINS = [handle_feed_images(), counter]

    class TestFeed(feedev.Feed):
        content = """
        <rss><channel>
            <title>test-feed</title>
            {% =2 %}<image></image>{% end %}
            {% =3 %}<image><url></url></image>{% end %}
            {% =4 %}<image><url>http://host/image.gif</url></image>{% end %}
        </channel></rss>
        """

        def pass1(feed):
            # no tag at all
            assert counter.called == 0

        def pass2(feed):
            # no url tag
            assert counter.called == 0

        def pass3(feed):
            # empty url
            assert counter.called == 0

        def pass4(feed):
            # finally, everything is ok!
            assert counter.called == 1

    feedev.testcaller()
Example #24
0
def test_no_passes():
    try:
        feedev.testcaller()
    except Exception, e:
        assert 'nothing to test' in str(e)