1"""Tests for distutils.command.register."""
2import os
3import unittest
4import getpass
5import urllib
6import warnings
7
8from test.support import run_unittest
9
10from .py38compat import check_warnings
11
12from distutils.command import register as register_module
13from distutils.command.register import register
14from distutils.errors import DistutilsSetupError
15from distutils.log import INFO
16
17from distutils.tests.test_config import BasePyPIRCCommandTestCase
18
19try:
20    import docutils
21except ImportError:
22    docutils = None
23
24PYPIRC_NOPASSWORD = """\
25[distutils]
26
27index-servers =
28    server1
29
30[server1]
31username:me
32"""
33
34WANTED_PYPIRC = """\
35[distutils]
36index-servers =
37    pypi
38
39[pypi]
40username:tarek
41password:password
42"""
43
44class Inputs(object):
45    """Fakes user inputs."""
46    def __init__(self, *answers):
47        self.answers = answers
48        self.index = 0
49
50    def __call__(self, prompt=''):
51        try:
52            return self.answers[self.index]
53        finally:
54            self.index += 1
55
56class FakeOpener(object):
57    """Fakes a PyPI server"""
58    def __init__(self):
59        self.reqs = []
60
61    def __call__(self, *args):
62        return self
63
64    def open(self, req, data=None, timeout=None):
65        self.reqs.append(req)
66        return self
67
68    def read(self):
69        return b'xxx'
70
71    def getheader(self, name, default=None):
72        return {
73            'content-type': 'text/plain; charset=utf-8',
74            }.get(name.lower(), default)
75
76
77class RegisterTestCase(BasePyPIRCCommandTestCase):
78
79    def setUp(self):
80        super(RegisterTestCase, self).setUp()
81        # patching the password prompt
82        self._old_getpass = getpass.getpass
83        def _getpass(prompt):
84            return 'password'
85        getpass.getpass = _getpass
86        urllib.request._opener = None
87        self.old_opener = urllib.request.build_opener
88        self.conn = urllib.request.build_opener = FakeOpener()
89
90    def tearDown(self):
91        getpass.getpass = self._old_getpass
92        urllib.request._opener = None
93        urllib.request.build_opener = self.old_opener
94        super(RegisterTestCase, self).tearDown()
95
96    def _get_cmd(self, metadata=None):
97        if metadata is None:
98            metadata = {'url': 'xxx', 'author': 'xxx',
99                        'author_email': 'xxx',
100                        'name': 'xxx', 'version': 'xxx'}
101        pkg_info, dist = self.create_dist(**metadata)
102        return register(dist)
103
104    def test_create_pypirc(self):
105        # this test makes sure a .pypirc file
106        # is created when requested.
107
108        # let's create a register instance
109        cmd = self._get_cmd()
110
111        # we shouldn't have a .pypirc file yet
112        self.assertFalse(os.path.exists(self.rc))
113
114        # patching input and getpass.getpass
115        # so register gets happy
116        #
117        # Here's what we are faking :
118        # use your existing login (choice 1.)
119        # Username : 'tarek'
120        # Password : 'password'
121        # Save your login (y/N)? : 'y'
122        inputs = Inputs('1', 'tarek', 'y')
123        register_module.input = inputs.__call__
124        # let's run the command
125        try:
126            cmd.run()
127        finally:
128            del register_module.input
129
130        # we should have a brand new .pypirc file
131        self.assertTrue(os.path.exists(self.rc))
132
133        # with the content similar to WANTED_PYPIRC
134        f = open(self.rc)
135        try:
136            content = f.read()
137            self.assertEqual(content, WANTED_PYPIRC)
138        finally:
139            f.close()
140
141        # now let's make sure the .pypirc file generated
142        # really works : we shouldn't be asked anything
143        # if we run the command again
144        def _no_way(prompt=''):
145            raise AssertionError(prompt)
146        register_module.input = _no_way
147
148        cmd.show_response = 1
149        cmd.run()
150
151        # let's see what the server received : we should
152        # have 2 similar requests
153        self.assertEqual(len(self.conn.reqs), 2)
154        req1 = dict(self.conn.reqs[0].headers)
155        req2 = dict(self.conn.reqs[1].headers)
156
157        self.assertEqual(req1['Content-length'], '1374')
158        self.assertEqual(req2['Content-length'], '1374')
159        self.assertIn(b'xxx', self.conn.reqs[1].data)
160
161    def test_password_not_in_file(self):
162
163        self.write_file(self.rc, PYPIRC_NOPASSWORD)
164        cmd = self._get_cmd()
165        cmd._set_config()
166        cmd.finalize_options()
167        cmd.send_metadata()
168
169        # dist.password should be set
170        # therefore used afterwards by other commands
171        self.assertEqual(cmd.distribution.password, 'password')
172
173    def test_registering(self):
174        # this test runs choice 2
175        cmd = self._get_cmd()
176        inputs = Inputs('2', 'tarek', 'tarek@ziade.org')
177        register_module.input = inputs.__call__
178        try:
179            # let's run the command
180            cmd.run()
181        finally:
182            del register_module.input
183
184        # we should have send a request
185        self.assertEqual(len(self.conn.reqs), 1)
186        req = self.conn.reqs[0]
187        headers = dict(req.headers)
188        self.assertEqual(headers['Content-length'], '608')
189        self.assertIn(b'tarek', req.data)
190
191    def test_password_reset(self):
192        # this test runs choice 3
193        cmd = self._get_cmd()
194        inputs = Inputs('3', 'tarek@ziade.org')
195        register_module.input = inputs.__call__
196        try:
197            # let's run the command
198            cmd.run()
199        finally:
200            del register_module.input
201
202        # we should have send a request
203        self.assertEqual(len(self.conn.reqs), 1)
204        req = self.conn.reqs[0]
205        headers = dict(req.headers)
206        self.assertEqual(headers['Content-length'], '290')
207        self.assertIn(b'tarek', req.data)
208
209    @unittest.skipUnless(docutils is not None, 'needs docutils')
210    def test_strict(self):
211        # testing the script option
212        # when on, the register command stops if
213        # the metadata is incomplete or if
214        # long_description is not reSt compliant
215
216        # empty metadata
217        cmd = self._get_cmd({})
218        cmd.ensure_finalized()
219        cmd.strict = 1
220        self.assertRaises(DistutilsSetupError, cmd.run)
221
222        # metadata are OK but long_description is broken
223        metadata = {'url': 'xxx', 'author': 'xxx',
224                    'author_email': 'éxéxé',
225                    'name': 'xxx', 'version': 'xxx',
226                    'long_description': 'title\n==\n\ntext'}
227
228        cmd = self._get_cmd(metadata)
229        cmd.ensure_finalized()
230        cmd.strict = 1
231        self.assertRaises(DistutilsSetupError, cmd.run)
232
233        # now something that works
234        metadata['long_description'] = 'title\n=====\n\ntext'
235        cmd = self._get_cmd(metadata)
236        cmd.ensure_finalized()
237        cmd.strict = 1
238        inputs = Inputs('1', 'tarek', 'y')
239        register_module.input = inputs.__call__
240        # let's run the command
241        try:
242            cmd.run()
243        finally:
244            del register_module.input
245
246        # strict is not by default
247        cmd = self._get_cmd()
248        cmd.ensure_finalized()
249        inputs = Inputs('1', 'tarek', 'y')
250        register_module.input = inputs.__call__
251        # let's run the command
252        try:
253            cmd.run()
254        finally:
255            del register_module.input
256
257        # and finally a Unicode test (bug #12114)
258        metadata = {'url': 'xxx', 'author': '\u00c9ric',
259                    'author_email': 'xxx', 'name': 'xxx',
260                    'version': 'xxx',
261                    'description': 'Something about esszet \u00df',
262                    'long_description': 'More things about esszet \u00df'}
263
264        cmd = self._get_cmd(metadata)
265        cmd.ensure_finalized()
266        cmd.strict = 1
267        inputs = Inputs('1', 'tarek', 'y')
268        register_module.input = inputs.__call__
269        # let's run the command
270        try:
271            cmd.run()
272        finally:
273            del register_module.input
274
275    @unittest.skipUnless(docutils is not None, 'needs docutils')
276    def test_register_invalid_long_description(self):
277        description = ':funkie:`str`'  # mimic Sphinx-specific markup
278        metadata = {'url': 'xxx', 'author': 'xxx',
279                    'author_email': 'xxx',
280                    'name': 'xxx', 'version': 'xxx',
281                    'long_description': description}
282        cmd = self._get_cmd(metadata)
283        cmd.ensure_finalized()
284        cmd.strict = True
285        inputs = Inputs('2', 'tarek', 'tarek@ziade.org')
286        register_module.input = inputs
287        self.addCleanup(delattr, register_module, 'input')
288
289        self.assertRaises(DistutilsSetupError, cmd.run)
290
291    def test_check_metadata_deprecated(self):
292        # makes sure make_metadata is deprecated
293        cmd = self._get_cmd()
294        with check_warnings() as w:
295            warnings.simplefilter("always")
296            cmd.check_metadata()
297            self.assertEqual(len(w.warnings), 1)
298
299    def test_list_classifiers(self):
300        cmd = self._get_cmd()
301        cmd.list_classifiers = 1
302        cmd.run()
303        results = self.get_logs(INFO)
304        self.assertEqual(results, ['running check', 'xxx'])
305
306    def test_show_response(self):
307        # test that the --show-response option return a well formatted response
308        cmd = self._get_cmd()
309        inputs = Inputs('1', 'tarek', 'y')
310        register_module.input = inputs.__call__
311        cmd.show_response = 1
312        try:
313            cmd.run()
314        finally:
315            del register_module.input
316
317        results = self.get_logs(INFO)
318        self.assertEqual(results[3], 75 * '-' + '\nxxx\n' + 75 * '-')
319
320
321def test_suite():
322    return unittest.makeSuite(RegisterTestCase)
323
324if __name__ == "__main__":
325    run_unittest(test_suite())
326