1# -*- coding: utf-8 -*-
2# Copyright (C) 2004-2008 Tristan Seligmann and Jonathan Jacobs
3# Copyright (C) 2012-2014 Bastian Kleineidam
4# Copyright (C) 2015-2020 Tobias Gruetzmacher
5
6from __future__ import absolute_import, division, print_function
7
8import json
9import os
10import re
11
12import pytest
13import responses
14
15import dosagelib.cmd
16import httpmocks
17
18
19def cmd(*options):
20    """'Fake' run dosage with given options."""
21    return dosagelib.cmd.main(('--allow-multiple',) + options)
22
23
24def cmd_ok(*options):
25    assert cmd(*options) == 0
26
27
28def cmd_err(*options):
29    assert cmd(*options) == 1
30
31
32@pytest.mark.usefixtures("nosleep")
33class TestDosage(object):
34    """Test the dosage commandline client."""
35
36    # This shouldn't hit the network at all, so add responses without mocks to
37    # make sure it doesn't do that
38    @responses.activate
39    def test_list_comics(self):
40        for option in ("-l", "--list", "--singlelist"):
41            cmd_ok(option)
42
43    @responses.activate
44    def test_display_version(self):
45        cmd_ok("--version")
46
47    @responses.activate
48    def test_update_available(self, capsys):
49        responses.add(responses.GET, re.compile(r'https://api\.github\.com/'),
50            json={'tag_name': '9999.0', 'assets': [
51                {'browser_download_url': 'TEST.whl'},
52                {'browser_download_url': 'TEST.exe'},
53            ]})
54        cmd_ok('--version', '-v')
55        captured = capsys.readouterr()
56        best = 'TEST.exe' if os.name == 'nt' else 'TEST.whl'
57        assert best in captured.out
58        assert 'A new version' in captured.out
59
60    @responses.activate
61    def test_no_update_available(self, capsys):
62        responses.add(responses.GET, re.compile(r'https://api\.github\.com/'),
63            json={'tag_name': '1.0'})
64        cmd_ok('--version', '-v')
65        captured = capsys.readouterr()
66        assert 'Detected local or development' in captured.out
67
68    @responses.activate
69    def test_current(self, capsys):
70        responses.add(responses.GET, re.compile(r'https://api\.github\.com/'),
71            json={'tag_name': dosagelib.__version__})
72        cmd_ok('--version', '-v')
73        captured = capsys.readouterr()
74        assert captured.out.endswith('issues\n')
75
76    @responses.activate
77    def test_update_broken(self, capsys):
78        responses.add(responses.GET, re.compile(r'https://api\.github\.com/'),
79            json={})
80        cmd_ok('--version', '-v')
81        captured = capsys.readouterr()
82        assert 'invalid update file' in captured.out
83
84    def test_display_help(self):
85        for option in ("-h", "--help"):
86            with pytest.raises(SystemExit):
87                cmd(option)
88
89    def test_module_help(self):
90        cmd_ok("-m", "xkcd")
91
92    def test_no_comics_specified(self):
93        cmd_err()
94
95    def test_unknown_option(self):
96        with pytest.raises(SystemExit):
97            cmd('--imadoofus')
98
99    def test_multiple_comics_match(self):
100        cmd_err('Garfield')
101
102    @responses.activate
103    def test_fetch_html_and_rss_json(self, tmpdir):
104        httpmocks.xkcd()
105        cmd_ok("-n", "2", "-v", "-b", str(tmpdir), "-o", "html", "-o", "rss",
106               "-o", "json", "--no-downscale", "xkcd")
107
108    @responses.activate
109    def test_fetch_html_and_rss_2(self, tmp_path):
110        httpmocks.page('http://www.bloomingfaeries.com/', 'bf-home')
111        httpmocks.page(re.compile('http://www.*faeries-405/'), 'bf-405')
112        httpmocks.png(re.compile(r'http://www\.blooming.*405.*jpg'))
113        httpmocks.png(re.compile(r'http://www\.blooming.*406.*jpg'), 'tall')
114
115        cmd_ok("--numstrips", "2", "--baseurl", "bla", "--basepath",
116            str(tmp_path), "--output", "rss", "--output", "html", "--adult",
117            "BloomingFaeries")
118
119        html = next((tmp_path / 'html').glob('*.html')).read_text()
120        assert "width=" in html
121
122    @responses.activate
123    def test_fetch_html_broken_img(self, tmp_path):
124        httpmocks.page('http://www.bloomingfaeries.com/', 'bf-home')
125        httpmocks.page(re.compile('http://www.*faeries-405/'), 'bf-405')
126        responses.add(responses.GET, re.compile(r'.*\.jpg'), body=b'\377\330',
127            content_type='image/jpeg')
128
129        cmd_ok("--numstrips", "2", "--baseurl", "bla", "--basepath",
130            str(tmp_path), "--output", "html", "--adult", "BloomingFaeries")
131
132        html = next((tmp_path / 'html').glob('*.html')).read_text()
133        assert "width=" not in html
134
135    @responses.activate
136    def test_fetch_indexed(self, tmpdir):
137        httpmocks.xkcd()
138        cmd_ok("-n", "2", "-v", "-b", str(tmpdir), "xkcd:303")
139
140    @responses.activate
141    def test_json_page_key_bounce_and_multi_image(self, tmpdir):
142        httpmocks.page('https://zenpencils.com/', 'zp-home')
143        httpmocks.page('https://zenpencils.com/comic/missing/', 'zp-223')
144        httpmocks.page('https://zenpencils.com/comic/lifejacket/', 'zp-222')
145        httpmocks.jpeg(re.compile(r'https://cdn-.*\.jpg'))
146
147        cmd_ok("-v", "-b", str(tmpdir), "-o", "json", "ZenPencils")
148
149        directory = tmpdir.join('ZenPencils')
150        f = directory.join('dosage.json').open(encoding='utf-8')
151        data = json.load(f)
152        f.close()
153
154        pages = data['pages']
155        assert len(pages) == 1
156
157        page = list(pages.keys())[0]
158        assert page == 'https://zenpencils.com/comic/missing/'
159
160        images = data['pages'][page]['images']
161        assert len(images) == 2
162
163        for imgurl, imgfile in images.items():
164            assert directory.join(imgfile).check(file=1)
165