def test_image_album_refresh_error(app, error, is_album):
    """Test Image.refresh() and Album.refresh() with bad JSON.

    Asserting line number of: parsed = response.json()
    Asserting line number of: return self._parse(response['data'])

    :param app: conftest fixture.
    :param str error: Error to test for.
    :param bool is_album: Test Album instead of Image.
    """
    url = API_URL.format(type='album' if is_album else 'image', id='imgur_id')
    if error == 'not json':
        body = '<html></html>'
    elif error == 'no data':
        body = '{"success":true}'
    else:
        body = '{"success":true, "data":{"id":"imgur_id"}}'
    httpretty.register_uri(httpretty.GET, url, body=body)

    # Test.
    instance = Album('imgur_id') if is_album else Image('imgur_id')
    with pytest.raises(APIError):
        instance.refresh(app, 'client_id', 30)

    # Verify log.
    if error == 'not json':
        assert app.messages[-1][:2] == ['warn', 'failed to parse JSON from {}'.format(url)]
        assert re.search(r'sphinxcontrib[/\\]imgur[/\\]imgur_api\.pyc?:60$', app.messages[-1][2])
    elif error == 'no data':
        assert app.messages[-1][:2] == ['warn', "unexpected JSON for imgur_id: KeyError('data',)"]
        assert re.search(r'sphinxcontrib[/\\]imgur[/\\]imgur_api\.pyc?:135$', app.messages[-1][2])
    else:
        assert app.messages[-1][:2] == ['warn', "unexpected JSON for imgur_id: KeyError('description',)"]
        assert re.search(r'sphinxcontrib[/\\]imgur[/\\]imgur_api\.pyc?:135$', app.messages[-1][2])
def test_query_api_not_success(app, bad_json):
    """Test non-successful replies or unexpected JSON data.

    Asserting line number of: raise APIError('query unsuccessful from {}...

    :param app: conftest fixture.
    :param bool bad_json: Unexpected JSON.
    """
    if bad_json:
        body = '{}'
        status = 200
        error = 'no "data" key in JSON'
    else:
        body = '{"data":{"error":"Authentication required","method":"GET"},"success":false,"status":401}'
        status = 401
        error = 'Authentication required'
    url = API_URL.format(type='album', id='imgur_id')
    httpretty.register_uri(httpretty.GET, url, body=body, status=status)

    # Test.
    with pytest.raises(APIError):
        query_api(app, 'client_id', 'imgur_id', True)

    # Verify log.
    assert app.messages[0] == ['info', 'querying {}'.format(url)]
    assert app.messages[1] == ['debug2', 'Imgur API responded with: %s' % body]
    assert app.messages[2][:2] == ['warn', 'query unsuccessful from {}: {}'.format(url, error)]
    assert re.search(r'sphinxcontrib[/\\]imgur[/\\]imgur_api\.pyc?:72$', app.messages[-1][2])
def test_update_cache_error_keep_previous(app):
    """Make sure error handling preserves previous data in cache.

    :param app: conftest fixture.
    """
    httpretty.register_uri(httpretty.GET, API_URL.format(type='album', id='album'), body='{"data": {}}', status=500)
    album_cache, image_cache = initialize(dict(), dict(), ['album'], [])
    album_cache['album'].description = 'Old'
    album_cache['album'].title = 'Old'

    update_cache(album_cache, image_cache, app, 'client_id', 30, list(), list())

    assert album_cache['album'].description == 'Old'
    assert album_cache['album'].title == 'Old'
    assert album_cache['album'].mod_time == 0

    merged = '\n'.join('\t'.join(m) for m in app.messages)
    assert 'query unsuccessful from https://api.imgur.com/3/album/album: no "error" key in JSON' in merged
def test_query_api_non_json(app):
    """Test when API returns something other than JSON.

    Asserting line number of: parsed = response.json()

    :param app: conftest fixture.
    """
    url = API_URL.format(type='image', id='imgur_id')
    httpretty.register_uri(httpretty.GET, url, body='<html></html>')

    # Test.
    with pytest.raises(APIError):
        query_api(app, 'client_id', 'imgur_id', False)

    # Verify log.
    assert app.messages[0] == ['info', 'querying {}'.format(url)]
    assert app.messages[1] == ['debug2', 'Imgur API responded with: <html></html>']
    assert app.messages[2][:2] == ['warn', 'failed to parse JSON from {}'.format(url)]
    assert re.search(r'sphinxcontrib[/\\]imgur[/\\]imgur_api\.pyc?:60$', app.messages[-1][2])
def test_album_minor_error(app):
    """Test Album.refresh() with bad images JSON.

    Asserting line number of: return self._parse(response['data'])

    :param app: conftest fixture.
    """
    url = API_URL.format(type='album', id='imgur_id')
    body = ('{"success":true, "data":{"id":"imgur_id", "cover": "imgur_id", "title": null, "description": null, '
            '"in_gallery": false, "images": [{}]}}')
    httpretty.register_uri(httpretty.GET, url, body=body)

    # Test.
    instance = Album('imgur_id')
    with pytest.raises(APIError):
        instance.refresh(app, 'client_id', 30)

    # Verify log.
    assert app.messages[-1][:2] == ['warn', "unexpected JSON for imgur_id: KeyError('id',)"]
    assert re.search(r'sphinxcontrib[/\\]imgur[/\\]imgur_api\.pyc?:135$', app.messages[-1][2])
def test_missing_api_data(tmpdir, docs):
    """Test handling of missing data in the cache. Will behave like the built-in image directive with external URLs.

    :param tmpdir: pytest fixture.
    :param docs: conftest fixture.
    """
    httpretty_mock = {
        API_URL.format(type='album', id='imgur0id'): '{}',
        API_URL.format(type='image', id='imgur0id'): '{}',
    }
    for url, body in httpretty_mock.items():
        httpretty.register_uri(httpretty.GET, url, body=body)

    pytest.add_page(docs, 'no_scale', (
        'SEP\n\n'
        '.. image:: https://i.imgur.com/imgur0idh.jpg\n    :width: 300px\n\nSEP\n\n'
        '.. imgur-image:: imgur0id\n    :width: 300px\n\nSEP\n\n'
        '.. image:: https://i.imgur.com/imgur0idh.jpg\n    :width: 30%\n\nSEP\n\n'
        '.. imgur-image:: imgur0id\n    :width: 30%\n\nSEP\n\n'
        '.. image:: https://i.imgur.com/imgur0idh.jpg\n    :height: 300px\n\nSEP\n\n'
        '.. imgur-image:: imgur0id\n    :height: 300px\n\nSEP\n\n'
    ))
    pytest.add_page(docs, 'scale', (
        'SEP\n\n'
        '.. image:: https://i.imgur.com/imgur0idh.jpg\n    :scale: 25%\n\nSEP\n\n'
        '.. imgur-image:: imgur0id\n    :scale: 25%\n\nSEP\n\n'
        '.. image:: https://i.imgur.com/imgur0idh.jpg\n    :width: 300px\n    :scale: 25%\n\nSEP\n\n'
        '.. imgur-image:: imgur0id\n    :width: 300px\n    :scale: 25%\n\nSEP\n\n'
        '.. image:: https://i.imgur.com/imgur0idh.jpg\n    :width: 30%\n    :scale: 25%\n\nSEP\n\n'
        '.. imgur-image:: imgur0id\n    :width: 30%\n    :scale: 25%\n\nSEP\n\n'
        '.. image:: https://i.imgur.com/imgur0idh.jpg\n    :height: 300px\n    :scale: 25%\n\nSEP\n\n'
        '.. imgur-image:: imgur0id\n    :height: 300px\n    :scale: 25%\n\nSEP\n\n'
    ))
    pytest.add_page(docs, 'album', (
        'SEP\n\n'
        '.. imgur-image:: a/imgur0id\n\nSEP\n\n'
    ))
    html = tmpdir.join('html')
    result, stderr = pytest.build_isolated(docs, html, httpretty_mock)[::2]

    assert result == 0
    lines = [l.split('WARNING: ')[-1].strip() for l in stderr.splitlines()]
    expected = [
        'query unsuccessful from https://api.imgur.com/3/album/imgur0id: no "data" key in JSON',
        'query unsuccessful from https://api.imgur.com/3/image/imgur0id: no "data" key in JSON',
        'nonlocal image URI found: https://i.imgur.com/imgur0idh.jpg',
        'nonlocal image URI found: https://i.imgur.com/imgur0idh.jpg',
        'nonlocal image URI found: https://i.imgur.com/imgur0idh.jpg',
        'nonlocal image URI found: https://i.imgur.com/imgur0idh.jpg',
        'nonlocal image URI found: https://i.imgur.com/imgur0idh.jpg',
        'nonlocal image URI found: https://i.imgur.com/imgur0idh.jpg',
        'nonlocal image URI found: https://i.imgur.com/imgur0idh.jpg',
        'Album cover Imgur ID for imgur0id not available in local cache.',
        'Could not obtain image size. :scale: option is ignored.',
        'Could not obtain image height. :scale: option is partially ignored.',
        'Could not obtain image height. :scale: option is partially ignored.',
        'Could not obtain image width. :scale: option is partially ignored.',
        'Could not obtain image size. :scale: option is ignored.',
        'Could not obtain image size. :scale: option is ignored.',
        'Could not obtain image size. :scale: option is ignored.',
        'Could not obtain image size. :scale: option is ignored.',
    ]
    assert lines == expected

    href_i = ('<a class="reference internal image-reference" href="https://i.imgur.com/imgur0idh.jpg">'
              '<img alt="https://i.imgur.com/imgur0idh.jpg" %s /></a>')
    href = ('<a class="reference external image-reference" href="//i.imgur.com/imgur0id.jpg">'
            '<img alt="i.imgur.com/imgur0idh.jpg" %s></a>')
    contents = [c.strip() for c in html.join('no_scale.html').read().split('<p>SEP</p>')[1:-1]]
    assert contents[0] == href_i % 'src="https://i.imgur.com/imgur0idh.jpg" style="width: 300px;"'
    assert contents[1] == href % 'src="//i.imgur.com/imgur0idh.jpg" style="width: 300px"'
    assert contents[2] == href_i % 'src="https://i.imgur.com/imgur0idh.jpg" style="width: 30%;"'
    assert contents[3] == href % 'src="//i.imgur.com/imgur0idh.jpg" style="width: 30%"'
    assert contents[4] == href_i % 'src="https://i.imgur.com/imgur0idh.jpg" style="height: 300px;"'
    assert contents[5] == href % 'src="//i.imgur.com/imgur0idh.jpg" style="height: 300px"'

    contents = [c.strip() for c in html.join('scale.html').read().split('<p>SEP</p>')[1:-1]]
    assert contents[0] == href_i % 'src="https://i.imgur.com/imgur0idh.jpg"'
    assert contents[1] == href % 'src="//i.imgur.com/imgur0idh.jpg"'
    assert contents[2] == href_i % 'src="https://i.imgur.com/imgur0idh.jpg" style="width: 75.0px;"'
    assert contents[3] == href % 'src="//i.imgur.com/imgur0idh.jpg" style="width: 75px"'
    assert contents[4] == href_i % 'src="https://i.imgur.com/imgur0idh.jpg" style="width: 7.5%;"'
    assert contents[5] == href % 'src="//i.imgur.com/imgur0idh.jpg" style="width: 7%"'
    assert contents[6] == href_i % 'src="https://i.imgur.com/imgur0idh.jpg" style="height: 75.0px;"'
    assert contents[7] == href % 'src="//i.imgur.com/imgur0idh.jpg" style="height: 75px"'

    contents = [c.strip() for c in html.join('album.html').read().split('<p>SEP</p>')[1:-1]]
    assert contents == ['']
def test_update_cache_error_handling(app, album):
    """Test error handling.

    :param app: conftest fixture.
    :param bool album: Error on album instead of image.
    """
    if album:
        httpretty.register_uri(httpretty.GET, API_URL.format(type='album', id='album'), body='{}', status=500)
        album_cache, image_cache = initialize(dict(), dict(), ['album'], ['611EovQ'])
    else:
        httpretty.register_uri(httpretty.GET, API_URL.format(type='image', id='image'), body='{}', status=500)
        album_cache, image_cache = initialize(dict(), dict(), ['V76cJ'], ['image'])
    before = {k: v.__dict__.copy() for k, v in album_cache.items()}
    before.update({k: v.__dict__.copy() for k, v in image_cache.items()})

    update_cache(album_cache, image_cache, app, 'client_id', 30, list(), list())
    after = {k: v.__dict__.copy() for k, v in album_cache.items()}
    after.update({k: v.__dict__.copy() for k, v in image_cache.items()})
    assert after != before

    if album:
        assert sorted(album_cache) == ['album']
        assert sorted(image_cache) == ['611EovQ']
        assert image_cache['611EovQ'].mod_time > 0
        assert image_cache['611EovQ'].description.startswith('Right before I moved desks for the 6th time in 1.5 years')
        assert image_cache['611EovQ'].title == 'Work, June 1st, 2016: Uber'
        assert album_cache['album'].mod_time == 0
        assert album_cache['album'].description == ''
        assert album_cache['album'].title == ''
        assert album_cache['album'].image_ids == list()
    else:
        assert sorted(album_cache) == ['V76cJ']
        assert sorted(image_cache) == ['image', 'mGQBV', 'ojGG7', 'pc8hc']
        assert album_cache['V76cJ'].mod_time > 0
        assert album_cache['V76cJ'].description.startswith('Installing a Qi wireless induction charger inside my door ')
        assert album_cache['V76cJ'].title == '2010 JSW, 2012 Projects'
        assert album_cache['V76cJ'].image_ids == ['mGQBV', 'pc8hc', 'ojGG7']
        assert image_cache['image'].mod_time == 0
        assert image_cache['image'].description == ''
        assert image_cache['image'].title == ''
        assert image_cache['mGQBV'].mod_time > 0
        assert image_cache['mGQBV'].description.startswith('Removed door panel from my car and tested whether my idea ')
        assert image_cache['mGQBV'].title == 'Wireless Charging 1: Testing'
        assert image_cache['ojGG7'].mod_time > 0
        assert image_cache['ojGG7'].description.startswith('Setting my phone in my door pocket charges it! The door pa')
        assert image_cache['ojGG7'].title == 'Wireless Charging 3: Works'
        assert image_cache['pc8hc'].mod_time > 0
        assert image_cache['pc8hc'].description == 'Closeup of Nokia DT-900 charger wedged in my door panel.'
        assert image_cache['pc8hc'].title == 'Wireless Charging 2: Testing Closeup'

    # Verify log.
    merged = '\n'.join('\t'.join(m) for m in app.messages)
    if album:
        assert 'query unsuccessful from https://api.imgur.com/3/album/album: no "data" key in JSON' in merged
        assert '"id": "611EovQ"' in merged
    else:
        assert '"id": "V76cJ"' in merged
        assert '"id": "mGQBV"' in merged
        assert '"id": "ojGG7"' in merged
        assert '"id": "pc8hc"' in merged
        assert 'query unsuccessful from https://api.imgur.com/3/image/image: no "data" key in JSON' in merged