Skip to content

Commit 8d9ea8b

Browse files
committed
Add tests for new code paths
1 parent 3a15e01 commit 8d9ea8b

File tree

1 file changed

+141
-1
lines changed

1 file changed

+141
-1
lines changed

tests/unit/test_network_auth.py

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import functools
22
import sys
3-
from typing import Any, Iterable, List, Optional, Tuple
3+
from typing import Any, Dict, Iterable, List, Optional, Tuple
44

55
import pytest
66

@@ -334,3 +334,143 @@ def test_broken_keyring_disables_keyring(monkeypatch: pytest.MonkeyPatch) -> Non
334334
url, allow_netrc=False, allow_keyring=True
335335
) == (None, None)
336336
assert keyring_broken._call_count == 1
337+
338+
339+
class KeyringSubprocessResult(KeyringModuleV1):
340+
"""Represents the subprocess call to keyring"""
341+
342+
returncode = 0 # Default to zero retcode
343+
344+
def __call__(
345+
self,
346+
cmd: List[str],
347+
*,
348+
env: Dict[str, str],
349+
stdin: Optional[Any] = None,
350+
capture_output: Optional[bool] = None,
351+
input: Optional[bytes] = None,
352+
) -> Any:
353+
if cmd[1] == "get":
354+
assert stdin == -3 # subprocess.DEVNULL
355+
assert capture_output is True
356+
assert env["PYTHONIOENCODING"] == "utf-8"
357+
358+
password = self.get_password(*cmd[2:])
359+
if password is None:
360+
# Expect non-zero returncode if no password present
361+
self.returncode = 1
362+
else:
363+
# Passwords are returned encoded with a newline appended
364+
self.stdout = password.encode("utf-8") + b"\n"
365+
366+
if cmd[1] == "set":
367+
assert stdin is None
368+
assert capture_output is None
369+
assert env["PYTHONIOENCODING"] == "utf-8"
370+
assert input is not None
371+
372+
# Input from stdin is encoded
373+
self.set_password(cmd[2], cmd[3], input.decode("utf-8").strip("\n"))
374+
375+
return self
376+
377+
def check_returncode(self) -> None:
378+
if self.returncode:
379+
raise Exception()
380+
381+
382+
@pytest.mark.parametrize(
383+
"url, expect",
384+
(
385+
("http://example.com/path1", (None, None)),
386+
# path1 URLs will be resolved by netloc
387+
("http://[email protected]/path1", ("user", "user!netloc")),
388+
("http://[email protected]/path1", ("user2", "user2!netloc")),
389+
# path2 URLs will be resolved by index URL
390+
("http://example.com/path2/path3", (None, None)),
391+
("http://[email protected]/path2/path3", ("foo", "foo!url")),
392+
),
393+
)
394+
def test_keyring_cli_get_password(
395+
monkeypatch: pytest.MonkeyPatch,
396+
url: str,
397+
expect: Tuple[Optional[str], Optional[str]],
398+
) -> None:
399+
monkeypatch.setattr(pip._internal.network.auth.shutil, "which", lambda x: "keyring")
400+
monkeypatch.setattr(
401+
pip._internal.network.auth.subprocess, "run", KeyringSubprocessResult()
402+
)
403+
auth = MultiDomainBasicAuth(index_urls=["http://example.com/path2"])
404+
405+
actual = auth._get_new_credentials(url, allow_netrc=False, allow_keyring=True)
406+
assert actual == expect
407+
408+
409+
@pytest.mark.parametrize(
410+
"response_status, creds, expect_save",
411+
(
412+
(403, ("user", "pass", True), False),
413+
(
414+
200,
415+
("user", "pass", True),
416+
True,
417+
),
418+
(
419+
200,
420+
("user", "pass", False),
421+
False,
422+
),
423+
),
424+
)
425+
def test_keyring_cli_set_password(
426+
monkeypatch: pytest.MonkeyPatch,
427+
response_status: int,
428+
creds: Tuple[str, str, bool],
429+
expect_save: bool,
430+
) -> None:
431+
monkeypatch.setattr(pip._internal.network.auth.shutil, "which", lambda x: "keyring")
432+
keyring = KeyringSubprocessResult()
433+
monkeypatch.setattr(pip._internal.network.auth.subprocess, "run", keyring)
434+
auth = MultiDomainBasicAuth(prompting=True)
435+
monkeypatch.setattr(auth, "_get_url_and_credentials", lambda u: (u, None, None))
436+
monkeypatch.setattr(auth, "_prompt_for_password", lambda *a: creds)
437+
if creds[2]:
438+
# when _prompt_for_password indicates to save, we should save
439+
def should_save_password_to_keyring(*a: Any) -> bool:
440+
return True
441+
442+
else:
443+
# when _prompt_for_password indicates not to save, we should
444+
# never call this function
445+
def should_save_password_to_keyring(*a: Any) -> bool:
446+
assert False, "_should_save_password_to_keyring should not be called"
447+
448+
monkeypatch.setattr(
449+
auth, "_should_save_password_to_keyring", should_save_password_to_keyring
450+
)
451+
452+
req = MockRequest("https://example.com")
453+
resp = MockResponse(b"")
454+
resp.url = req.url
455+
connection = MockConnection()
456+
457+
def _send(sent_req: MockRequest, **kwargs: Any) -> MockResponse:
458+
assert sent_req is req
459+
assert "Authorization" in sent_req.headers
460+
r = MockResponse(b"")
461+
r.status_code = response_status
462+
return r
463+
464+
# https://github.com/python/mypy/issues/2427
465+
connection._send = _send # type: ignore[assignment]
466+
467+
resp.request = req
468+
resp.status_code = 401
469+
resp.connection = connection
470+
471+
auth.handle_401(resp)
472+
473+
if expect_save:
474+
assert keyring.saved_passwords == [("example.com", creds[0], creds[1])]
475+
else:
476+
assert keyring.saved_passwords == []

0 commit comments

Comments
 (0)