1# Copyright 2013 Donald Stufft 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import os.path 16 17import pretend 18import pytest 19import requests 20 21from twine import exceptions 22from twine import utils 23 24from . import helpers 25 26 27def test_get_config(write_config_file): 28 config_file = write_config_file( 29 """ 30 [distutils] 31 index-servers = pypi 32 33 [pypi] 34 username = testuser 35 password = testpassword 36 """ 37 ) 38 39 assert utils.get_config(config_file) == { 40 "pypi": { 41 "repository": utils.DEFAULT_REPOSITORY, 42 "username": "testuser", 43 "password": "testpassword", 44 }, 45 } 46 47 48def test_get_config_no_distutils(write_config_file): 49 """Upload by default to PyPI if an index server is not set in .pypirc.""" 50 config_file = write_config_file( 51 """ 52 [pypi] 53 username = testuser 54 password = testpassword 55 """ 56 ) 57 58 assert utils.get_config(config_file) == { 59 "pypi": { 60 "repository": utils.DEFAULT_REPOSITORY, 61 "username": "testuser", 62 "password": "testpassword", 63 }, 64 "testpypi": { 65 "repository": utils.TEST_REPOSITORY, 66 "username": None, 67 "password": None, 68 }, 69 } 70 71 72def test_get_config_no_section(write_config_file): 73 config_file = write_config_file( 74 """ 75 [distutils] 76 index-servers = pypi foo 77 78 [pypi] 79 username = testuser 80 password = testpassword 81 """ 82 ) 83 84 assert utils.get_config(config_file) == { 85 "pypi": { 86 "repository": utils.DEFAULT_REPOSITORY, 87 "username": "testuser", 88 "password": "testpassword", 89 }, 90 } 91 92 93def test_get_config_override_pypi_url(write_config_file): 94 config_file = write_config_file( 95 """ 96 [pypi] 97 repository = http://pypiproxy 98 """ 99 ) 100 101 assert utils.get_config(config_file)["pypi"]["repository"] == "http://pypiproxy" 102 103 104def test_get_config_missing(config_file): 105 assert utils.get_config(config_file) == { 106 "pypi": { 107 "repository": utils.DEFAULT_REPOSITORY, 108 "username": None, 109 "password": None, 110 }, 111 "testpypi": { 112 "repository": utils.TEST_REPOSITORY, 113 "username": None, 114 "password": None, 115 }, 116 } 117 118 119def test_empty_userpass(write_config_file): 120 """Suppress prompts if empty username and password are provided in .pypirc.""" 121 config_file = write_config_file( 122 """ 123 [pypi] 124 username= 125 password= 126 """ 127 ) 128 129 config = utils.get_config(config_file) 130 pypi = config["pypi"] 131 132 assert pypi["username"] == pypi["password"] == "" 133 134 135def test_get_repository_config_missing(config_file): 136 repository_url = "https://notexisting.python.org/pypi" 137 exp = { 138 "repository": repository_url, 139 "username": None, 140 "password": None, 141 } 142 assert utils.get_repository_from_config(config_file, "foo", repository_url) == exp 143 assert utils.get_repository_from_config(config_file, "pypi", repository_url) == exp 144 145 exp = { 146 "repository": utils.DEFAULT_REPOSITORY, 147 "username": None, 148 "password": None, 149 } 150 assert utils.get_repository_from_config(config_file, "pypi") == exp 151 152 153@pytest.mark.parametrize( 154 "repo_url, message", 155 [ 156 ( 157 "ftp://test.pypi.org", 158 r"scheme was required to be one of \['http', 'https'\]", 159 ), 160 ("https:/", "host was required but missing."), 161 ("//test.pypi.org", "scheme was required but missing."), 162 ("foo.bar", "host, scheme were required but missing."), 163 ], 164) 165def test_get_repository_config_with_invalid_url(config_file, repo_url, message): 166 """Raise an exception for a URL with an invalid/missing scheme and/or host.""" 167 with pytest.raises( 168 exceptions.UnreachableRepositoryURLDetected, 169 match=message, 170 ): 171 utils.get_repository_from_config(config_file, "pypi", repo_url) 172 173 174def test_get_repository_config_missing_repository(write_config_file): 175 """Raise an exception when a custom repository isn't defined in .pypirc.""" 176 config_file = write_config_file("") 177 with pytest.raises( 178 exceptions.InvalidConfiguration, 179 match="Missing 'missing-repository'", 180 ): 181 utils.get_repository_from_config(config_file, "missing-repository") 182 183 184@pytest.mark.parametrize("repository", ["pypi", "missing-repository"]) 185def test_get_repository_config_missing_file(repository): 186 """Raise an exception when a custom config file doesn't exist.""" 187 with pytest.raises( 188 exceptions.InvalidConfiguration, 189 match=r"No such file.*missing-file", 190 ): 191 utils.get_repository_from_config("missing-file", repository) 192 193 194def test_get_config_deprecated_pypirc(): 195 tests_dir = os.path.dirname(os.path.abspath(__file__)) 196 deprecated_pypirc_path = os.path.join(tests_dir, "fixtures", "deprecated-pypirc") 197 198 assert utils.get_config(deprecated_pypirc_path) == { 199 "pypi": { 200 "repository": utils.DEFAULT_REPOSITORY, 201 "username": "testusername", 202 "password": "testpassword", 203 }, 204 "testpypi": { 205 "repository": utils.TEST_REPOSITORY, 206 "username": "testusername", 207 "password": "testpassword", 208 }, 209 } 210 211 212@pytest.mark.parametrize( 213 ("cli_value", "config", "key", "strategy", "expected"), 214 ( 215 ("cli", {}, "key", lambda: "fallback", "cli"), 216 (None, {"key": "value"}, "key", lambda: "fallback", "value"), 217 (None, {}, "key", lambda: "fallback", "fallback"), 218 ), 219) 220def test_get_userpass_value(cli_value, config, key, strategy, expected): 221 ret = utils.get_userpass_value(cli_value, config, key, strategy) 222 assert ret == expected 223 224 225@pytest.mark.parametrize( 226 ("env_name", "default", "environ", "expected"), 227 [ 228 ("MY_PASSWORD", None, {}, None), 229 ("MY_PASSWORD", None, {"MY_PASSWORD": "foo"}, "foo"), 230 ("URL", "https://example.org", {}, "https://example.org"), 231 ("URL", "https://example.org", {"URL": "https://pypi.org"}, "https://pypi.org"), 232 ], 233) 234def test_default_to_environment_action(env_name, default, environ, expected): 235 option_strings = ("-x", "--example") 236 dest = "example" 237 with helpers.set_env(**environ): 238 action = utils.EnvironmentDefault( 239 env=env_name, 240 default=default, 241 option_strings=option_strings, 242 dest=dest, 243 ) 244 assert action.env == env_name 245 assert action.default == expected 246 247 248@pytest.mark.parametrize( 249 "repo_url", ["https://pypi.python.org", "https://testpypi.python.org"] 250) 251def test_check_status_code_for_deprecated_pypi_url(repo_url): 252 response = pretend.stub(status_code=410, url=repo_url) 253 254 # value of Verbose doesn't matter for this check 255 with pytest.raises(exceptions.UploadToDeprecatedPyPIDetected): 256 utils.check_status_code(response, False) 257 258 259@pytest.mark.parametrize( 260 "repo_url", 261 ["https://pypi.python.org", "https://testpypi.python.org"], 262) 263@pytest.mark.parametrize( 264 "verbose", 265 [True, False], 266) 267def test_check_status_code_for_missing_status_code( 268 capsys, repo_url, verbose, make_settings 269): 270 """Print HTTP errors based on verbosity level.""" 271 response = pretend.stub( 272 status_code=403, 273 url=repo_url, 274 raise_for_status=pretend.raiser(requests.HTTPError), 275 text="Forbidden", 276 ) 277 278 make_settings(verbose=verbose) 279 280 with pytest.raises(requests.HTTPError): 281 utils.check_status_code(response, verbose) 282 283 captured = capsys.readouterr() 284 285 if verbose: 286 assert captured.out.count("Content received from server:\nForbidden\n") == 1 287 else: 288 assert captured.out.count("--verbose option") == 1 289 290 291@pytest.mark.parametrize( 292 ("size_in_bytes, formatted_size"), 293 [(3704, "3.6 KB"), (1153433, "1.1 MB"), (21412841, "20.4 MB")], 294) 295def test_get_file_size(size_in_bytes, formatted_size, monkeypatch): 296 """Get the size of file as a string with units.""" 297 monkeypatch.setattr(os.path, "getsize", lambda _: size_in_bytes) 298 299 file_size = utils.get_file_size(size_in_bytes) 300 301 assert file_size == formatted_size 302