Skip to content

Commit 0fb2f51

Browse files
authored
Merge pull request #537 from pypa/refactor/auth-options
Extract auth options resolver
2 parents 0a0f11d + 563d6c4 commit 0fb2f51

File tree

5 files changed

+276
-299
lines changed

5 files changed

+276
-299
lines changed

tests/test_auth.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import pytest
2+
3+
from twine import (
4+
auth,
5+
exceptions,
6+
utils,
7+
)
8+
9+
10+
cred = auth.CredentialInput.new
11+
12+
13+
@pytest.fixture
14+
def config() -> utils.RepositoryConfig:
15+
return dict(repository='system')
16+
17+
18+
def test_get_password_keyring_overrides_prompt(monkeypatch, config):
19+
class MockKeyring:
20+
@staticmethod
21+
def get_password(system, user):
22+
return '{user}@{system} sekure pa55word'.format(**locals())
23+
24+
monkeypatch.setattr(auth, 'keyring', MockKeyring)
25+
26+
pw = auth.Resolver(config, cred('user')).password
27+
assert pw == 'user@system sekure pa55word'
28+
29+
30+
def test_get_password_keyring_defers_to_prompt(
31+
monkeypatch, entered_password, config):
32+
class MockKeyring:
33+
@staticmethod
34+
def get_password(system, user):
35+
return
36+
37+
monkeypatch.setattr(auth, 'keyring', MockKeyring)
38+
39+
pw = auth.Resolver(config, cred('user')).password
40+
assert pw == 'entered pw'
41+
42+
43+
def test_no_password_defers_to_prompt(monkeypatch, entered_password, config):
44+
config.update(password=None)
45+
pw = auth.Resolver(config, cred('user')).password
46+
assert pw == 'entered pw'
47+
48+
49+
def test_empty_password_bypasses_prompt(
50+
monkeypatch, entered_password, config):
51+
config.update(password='')
52+
pw = auth.Resolver(config, cred('user')).password
53+
assert pw == ''
54+
55+
56+
def test_no_username_non_interactive_aborts(config):
57+
with pytest.raises(exceptions.NonInteractive):
58+
auth.Private(config, cred('user')).password
59+
60+
61+
def test_no_password_non_interactive_aborts(config):
62+
with pytest.raises(exceptions.NonInteractive):
63+
auth.Private(config, cred('user')).password
64+
65+
66+
def test_get_username_and_password_keyring_overrides_prompt(
67+
monkeypatch, config):
68+
69+
class MockKeyring:
70+
@staticmethod
71+
def get_credential(system, user):
72+
return cred(
73+
'real_user',
74+
'real_user@{system} sekure pa55word'.format(**locals())
75+
)
76+
77+
@staticmethod
78+
def get_password(system, user):
79+
cred = MockKeyring.get_credential(system, user)
80+
if user != cred.username:
81+
raise RuntimeError("unexpected username")
82+
return cred.password
83+
84+
monkeypatch.setattr(auth, 'keyring', MockKeyring)
85+
86+
res = auth.Resolver(config, cred())
87+
assert res.username == 'real_user'
88+
assert res.password == 'real_user@system sekure pa55word'
89+
90+
91+
@pytest.fixture
92+
def keyring_missing_get_credentials(monkeypatch):
93+
"""
94+
Simulate keyring prior to 15.2 that does not have the
95+
'get_credential' API.
96+
"""
97+
monkeypatch.delattr(auth.keyring, 'get_credential')
98+
99+
100+
@pytest.fixture
101+
def entered_username(monkeypatch):
102+
monkeypatch.setattr(
103+
auth, 'input', lambda prompt: 'entered user', raising=False)
104+
105+
106+
@pytest.fixture
107+
def entered_password(monkeypatch):
108+
monkeypatch.setattr(auth.getpass, 'getpass', lambda prompt: 'entered pw')
109+
110+
111+
def test_get_username_keyring_missing_get_credentials_prompts(
112+
entered_username, keyring_missing_get_credentials, config):
113+
assert auth.Resolver(config, cred()).username == 'entered user'
114+
115+
116+
def test_get_username_keyring_missing_non_interactive_aborts(
117+
entered_username, keyring_missing_get_credentials, config):
118+
with pytest.raises(exceptions.NonInteractive):
119+
auth.Private(config, cred()).username
120+
121+
122+
def test_get_password_keyring_missing_non_interactive_aborts(
123+
entered_username, keyring_missing_get_credentials, config):
124+
with pytest.raises(exceptions.NonInteractive):
125+
auth.Private(config, cred('user')).password
126+
127+
128+
@pytest.fixture
129+
def keyring_no_backends(monkeypatch):
130+
"""
131+
Simulate that keyring has no available backends. When keyring
132+
has no backends for the system, the backend will be a
133+
fail.Keyring, which raises RuntimeError on get_password.
134+
"""
135+
class FailKeyring:
136+
@staticmethod
137+
def get_password(system, username):
138+
raise RuntimeError("fail!")
139+
monkeypatch.setattr(auth, 'keyring', FailKeyring())
140+
141+
142+
@pytest.fixture
143+
def keyring_no_backends_get_credential(monkeypatch):
144+
"""
145+
Simulate that keyring has no available backends. When keyring
146+
has no backends for the system, the backend will be a
147+
fail.Keyring, which raises RuntimeError on get_credential.
148+
"""
149+
class FailKeyring:
150+
@staticmethod
151+
def get_credential(system, username):
152+
raise RuntimeError("fail!")
153+
monkeypatch.setattr(auth, 'keyring', FailKeyring())
154+
155+
156+
def test_get_username_runtime_error_suppressed(
157+
entered_username, keyring_no_backends_get_credential, recwarn,
158+
config):
159+
assert auth.Resolver(config, cred()).username == 'entered user'
160+
assert len(recwarn) == 1
161+
warning = recwarn.pop(UserWarning)
162+
assert 'fail!' in str(warning)
163+
164+
165+
def test_get_password_runtime_error_suppressed(
166+
entered_password, keyring_no_backends, recwarn, config):
167+
assert auth.Resolver(config, cred('user')).password == 'entered pw'
168+
assert len(recwarn) == 1
169+
warning = recwarn.pop(UserWarning)
170+
assert 'fail!' in str(warning)

tests/test_utils.py

Lines changed: 0 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -221,161 +221,6 @@ def test_default_to_environment_action(env_name, default, environ, expected):
221221
assert action.default == expected
222222

223223

224-
def test_get_password_keyring_overrides_prompt(monkeypatch):
225-
class MockKeyring:
226-
@staticmethod
227-
def get_password(system, user):
228-
return '{user}@{system} sekure pa55word'.format(**locals())
229-
230-
monkeypatch.setattr(utils, 'keyring', MockKeyring)
231-
232-
pw = utils.get_password('system', 'user', None, {})
233-
assert pw == 'user@system sekure pa55word'
234-
235-
236-
def test_get_password_keyring_defers_to_prompt(monkeypatch):
237-
monkeypatch.setattr(utils, 'password_prompt', lambda prompt: 'entered pw')
238-
239-
class MockKeyring:
240-
@staticmethod
241-
def get_password(system, user):
242-
return
243-
244-
monkeypatch.setattr(utils, 'keyring', MockKeyring)
245-
246-
pw = utils.get_password('system', 'user', None, {})
247-
assert pw == 'entered pw'
248-
249-
250-
def test_no_password_defers_to_prompt(monkeypatch):
251-
monkeypatch.setattr(utils, 'password_prompt', lambda prompt: 'entered pw')
252-
pw = utils.get_password('system', 'user', None, {'password': None})
253-
assert pw == 'entered pw'
254-
255-
256-
def test_empty_password_bypasses_prompt(monkeypatch):
257-
monkeypatch.setattr(utils, 'password_prompt', lambda prompt: 'entered pw')
258-
pw = utils.get_password('system', 'user', None, {'password': ''})
259-
assert pw == ''
260-
261-
262-
def test_no_username_non_interactive_aborts():
263-
with pytest.raises(exceptions.NonInteractive):
264-
utils.get_username('system', None, {'username': None}, True)
265-
266-
267-
def test_no_password_non_interactive_aborts():
268-
with pytest.raises(exceptions.NonInteractive):
269-
utils.get_password('system', 'user', None, {'password': None}, True)
270-
271-
272-
def test_get_username_and_password_keyring_overrides_prompt(monkeypatch):
273-
import collections
274-
Credential = collections.namedtuple('Credential', 'username password')
275-
276-
class MockKeyring:
277-
@staticmethod
278-
def get_credential(system, user):
279-
return Credential(
280-
'real_user',
281-
'real_user@{system} sekure pa55word'.format(**locals())
282-
)
283-
284-
@staticmethod
285-
def get_password(system, user):
286-
cred = MockKeyring.get_credential(system, user)
287-
if user != cred.username:
288-
raise RuntimeError("unexpected username")
289-
return cred.password
290-
291-
monkeypatch.setattr(utils, 'keyring', MockKeyring)
292-
293-
user = utils.get_username('system', None, {})
294-
assert user == 'real_user'
295-
pw = utils.get_password('system', user, None, {})
296-
assert pw == 'real_user@system sekure pa55word'
297-
298-
299-
@pytest.fixture
300-
def keyring_missing_get_credentials(monkeypatch):
301-
"""
302-
Simulate keyring prior to 15.2 that does not have the
303-
'get_credential' API.
304-
"""
305-
monkeypatch.delattr(utils.keyring, 'get_credential')
306-
307-
308-
@pytest.fixture
309-
def entered_username(monkeypatch):
310-
monkeypatch.setattr(utils, 'input_func', lambda prompt: 'entered user')
311-
312-
313-
@pytest.fixture
314-
def entered_password(monkeypatch):
315-
monkeypatch.setattr(utils, 'password_prompt', lambda prompt: 'entered pw')
316-
317-
318-
def test_get_username_keyring_missing_get_credentials_prompts(
319-
entered_username, keyring_missing_get_credentials):
320-
assert utils.get_username('system', None, {}) == 'entered user'
321-
322-
323-
def test_get_username_keyring_missing_non_interactive_aborts(
324-
entered_username, keyring_missing_get_credentials):
325-
with pytest.raises(exceptions.NonInteractive):
326-
utils.get_username('system', None, {}, True)
327-
328-
329-
def test_get_password_keyring_missing_non_interactive_aborts(
330-
entered_username, keyring_missing_get_credentials):
331-
with pytest.raises(exceptions.NonInteractive):
332-
utils.get_password('system', 'user', None, {}, True)
333-
334-
335-
@pytest.fixture
336-
def keyring_no_backends(monkeypatch):
337-
"""
338-
Simulate that keyring has no available backends. When keyring
339-
has no backends for the system, the backend will be a
340-
fail.Keyring, which raises RuntimeError on get_password.
341-
"""
342-
class FailKeyring:
343-
@staticmethod
344-
def get_password(system, username):
345-
raise RuntimeError("fail!")
346-
monkeypatch.setattr(utils, 'keyring', FailKeyring())
347-
348-
349-
@pytest.fixture
350-
def keyring_no_backends_get_credential(monkeypatch):
351-
"""
352-
Simulate that keyring has no available backends. When keyring
353-
has no backends for the system, the backend will be a
354-
fail.Keyring, which raises RuntimeError on get_credential.
355-
"""
356-
class FailKeyring:
357-
@staticmethod
358-
def get_credential(system, username):
359-
raise RuntimeError("fail!")
360-
monkeypatch.setattr(utils, 'keyring', FailKeyring())
361-
362-
363-
def test_get_username_runtime_error_suppressed(
364-
entered_username, keyring_no_backends_get_credential, recwarn):
365-
assert utils.get_username('system', None, {}) == 'entered user'
366-
assert len(recwarn) == 1
367-
warning = recwarn.pop(UserWarning)
368-
assert 'fail!' in str(warning)
369-
370-
371-
def test_get_password_runtime_error_suppressed(
372-
entered_password, keyring_no_backends, recwarn):
373-
assert utils.get_password('system', 'user', None, {}) == 'entered pw'
374-
assert len(recwarn) == 1
375-
warning = recwarn.pop(UserWarning)
376-
assert 'fail!' in str(warning)
377-
378-
379224
@pytest.mark.parametrize('repo_url', [
380225
"https://pypi.python.org",
381226
"https://testpypi.python.org"

0 commit comments

Comments
 (0)