Skip to content

Commit

Permalink
Add type annotations to tests
Browse files Browse the repository at this point in the history
Even though type annotations are nasty, they do bring one significant
benefit - the code completion in text editors becomes much better. This
patch adds type annotations to python modules in the 'tests' directory.
  • Loading branch information
ikalnytskyi committed May 8, 2024
1 parent f715a8d commit 3e58b10
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 106 deletions.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ target-version = "py38"

[tool.ruff.lint]
select = ["ALL"]
ignore = ["ANN", "D", "PTH", "PLR", "PT005", "ISC001", "INP001", "S603", "S607", "COM812"]
ignore = ["D", "PTH", "PLR", "PT005", "ISC001", "INP001", "S603", "S607", "COM812", "FA100", "ANN101"]

[tool.ruff.lint.per-file-ignores]
"src/*" = ["ANN"]
"src/httpie_credential_store/_keychain.py" = ["S602"]
"tests/*" = ["S101", "INP001"]

Expand Down
7 changes: 6 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import pathlib
import typing

import pytest


@pytest.fixture(scope="session", autouse=True)
def _httpie_config_dir(tmp_path_factory: pytest.TempPathFactory):
def _httpie_config_dir(
tmp_path_factory: pytest.TempPathFactory,
) -> typing.Generator[pathlib.Path, None, None]:
"""Set path to HTTPie configuration directory."""

# HTTPie can optionally read a path to configuration directory from
Expand Down
17 changes: 11 additions & 6 deletions tests/test_keychain_password_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@
import sys
import tempfile
import textwrap
import typing

import pytest


if typing.TYPE_CHECKING:
from httpie_credential_store._keychain import PasswordStoreKeychain


_is_macos = sys.platform == "darwin"


Expand All @@ -29,13 +34,13 @@
# override 'tmp_path' fixture to return much shorter path to a temporary
# directory.
@pytest.fixture()
def tmp_path():
def tmp_path() -> typing.Generator[pathlib.Path, None, None]:
with tempfile.TemporaryDirectory() as path:
yield pathlib.Path(path)


@pytest.fixture()
def gpg_key_id(monkeypatch, tmp_path):
def gpg_key_id(monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path) -> str:
"""Return a Key ID of just generated GPG key."""

gpghome = tmp_path.joinpath(".gnupg")
Expand Down Expand Up @@ -68,7 +73,7 @@ def gpg_key_id(monkeypatch, tmp_path):


@pytest.fixture(autouse=True)
def password_store_dir(monkeypatch, tmp_path):
def password_store_dir(monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path) -> pathlib.Path:
"""Set password-store home directory to a temporary one."""

passstore = tmp_path.joinpath(".password-store")
Expand All @@ -77,7 +82,7 @@ def password_store_dir(monkeypatch, tmp_path):


@pytest.fixture()
def testkeychain():
def testkeychain() -> "PasswordStoreKeychain":
"""Keychain instance under test."""

# For the same reasons as in tests/test_plugin.py, all imports that trigger
Expand All @@ -88,7 +93,7 @@ def testkeychain():
return _keychain.PasswordStoreKeychain()


def test_secret_retrieved(testkeychain, gpg_key_id):
def test_secret_retrieved(testkeychain: "PasswordStoreKeychain", gpg_key_id: str) -> None:
"""The keychain returns stored secret, no bullshit."""

subprocess.check_call(["pass", "init", gpg_key_id])
Expand All @@ -97,7 +102,7 @@ def test_secret_retrieved(testkeychain, gpg_key_id):
assert testkeychain.get(name="service/user") == "f00b@r"


def test_secret_not_found(testkeychain):
def test_secret_not_found(testkeychain: "PasswordStoreKeychain") -> None:
"""LookupError is raised when no secrets are found in the keychain."""

with pytest.raises(LookupError) as excinfo:
Expand Down
20 changes: 15 additions & 5 deletions tests/test_keychain_shell.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
"""Tests shell keychain provider."""

import os
import pathlib
import typing

import pytest


if typing.TYPE_CHECKING:
from httpie_credential_store._keychain import ShellKeychain


@pytest.fixture()
def testkeychain():
def testkeychain() -> "ShellKeychain":
"""Keychain instance under test."""

# For the same reasons as in tests/test_plugin.py, all imports that trigger
Expand All @@ -17,15 +23,15 @@ def testkeychain():
return _keychain.ShellKeychain()


def test_secret_retrieved(testkeychain, tmp_path):
def test_secret_retrieved(testkeychain: "ShellKeychain", tmp_path: pathlib.Path) -> None:
"""The keychain returns stored secret, no bullshit."""

secrettxt = tmp_path.joinpath("secret.txt")
secrettxt.write_text("p@ss", encoding="UTF-8")
assert testkeychain.get(command=f"cat {secrettxt}") == "p@ss"


def test_secret_retrieved_pipe(testkeychain, tmp_path):
def test_secret_retrieved_pipe(testkeychain: "ShellKeychain", tmp_path: pathlib.Path) -> None:
"""The keychain returns stored secret even when pipes are used."""

secrettxt = tmp_path.joinpath("secret.txt")
Expand All @@ -35,7 +41,7 @@ def test_secret_retrieved_pipe(testkeychain, tmp_path):
assert testkeychain.get(command=command) == "p@ss"


def test_secret_not_found(testkeychain, tmp_path):
def test_secret_not_found(testkeychain: "ShellKeychain", tmp_path: pathlib.Path) -> None:
"""LookupError is raised when no secrets are found in the keychain."""

secrettxt = tmp_path.joinpath("secret.txt")
Expand All @@ -49,6 +55,10 @@ def test_secret_not_found(testkeychain, tmp_path):


@pytest.mark.parametrize(("args", "kwargs"), [pytest.param(["echo p@ss"], {}, id="args")])
def test_keywords_only_arguments(testkeychain, args, kwargs):
def test_keywords_only_arguments(
testkeychain: "ShellKeychain",
args: typing.List[str],
kwargs: typing.Mapping[str, str],
) -> None:
with pytest.raises(TypeError):
testkeychain.get(*args, **kwargs)
36 changes: 27 additions & 9 deletions tests/test_keychain_system.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
"""Tests system keychain provider."""

import typing

import keyring
import keyring.backend
import keyring.compat
import pytest


if typing.TYPE_CHECKING:
from httpie_credential_store._keychain import SystemKeychain


class _InmemoryKeyring(keyring.backend.KeyringBackend):
"""Keyring backend that stores secrets in-memory."""

priority = 1
@keyring.compat.properties.classproperty
def priority(self) -> float:
return 1.0

def __init__(self):
def __init__(self) -> None:
self._keyring = {}

def get_password(self, service, username):
def get_password(self, service: str, username: str) -> typing.Optional[str]:
return self._keyring.get((service, username))

def set_password(self, service, username, password):
def set_password(self, service: str, username: str, password: str) -> None:
self._keyring[(service, username)] = password


@pytest.fixture(autouse=True)
def keyring_backend():
def keyring_backend() -> typing.Generator[keyring.backend.KeyringBackend, None, None]:
"""Temporary set in-memory keyring as current backend."""

prev_backend = keyring.get_keyring()
Expand All @@ -30,7 +40,7 @@ def keyring_backend():


@pytest.fixture()
def testkeychain():
def testkeychain() -> "SystemKeychain":
"""Keychain instance under test."""

# For the same reasons as in tests/test_plugin.py, all imports that trigger
Expand All @@ -41,14 +51,17 @@ def testkeychain():
return _keychain.SystemKeychain()


def test_secret_retrieved(testkeychain, keyring_backend):
def test_secret_retrieved(
testkeychain: "SystemKeychain",
keyring_backend: keyring.backend.KeyringBackend,
) -> None:
"""The keychain returns stored secret, no bullshit."""

keyring_backend.set_password("testsvc", "testuser", "p@ss")
assert testkeychain.get(service="testsvc", username="testuser") == "p@ss"


def test_secret_not_found(testkeychain):
def test_secret_not_found(testkeychain: "SystemKeychain") -> None:
"""LookupError is raised when no secrets are found in the keychain."""

with pytest.raises(LookupError) as excinfo:
Expand All @@ -66,7 +79,12 @@ def test_secret_not_found(testkeychain):
pytest.param(["testsvc"], {"username": "testuser"}, id="args-kwargs"),
],
)
def test_keywords_only_arguments(testkeychain, keyring_backend, args, kwargs):
def test_keywords_only_arguments(
testkeychain: "SystemKeychain",
keyring_backend: keyring.backend.KeyringBackend,
args: typing.List[str],
kwargs: typing.Mapping[str, str],
) -> None:
keyring_backend.set_password("testsvc", "testuser", "p@ss")

with pytest.raises(TypeError):
Expand Down
Loading

0 comments on commit 3e58b10

Please sign in to comment.