Skip to content

Commit

Permalink
test(decrypt): add integration tests (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
athith-g authored Aug 8, 2024
1 parent dd74dc9 commit 40c1ab7
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 1 deletion.
2 changes: 1 addition & 1 deletion crypt4gh_middleware/decrypt.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def decrypt_files(file_paths: list[Path], private_keys: list[bytes]):
logger.info(f"Decrypted {file_path} successfully")
except ValueError as e:
if str(e) != "Not a CRYPT4GH formatted file":
print(f"Private key for {file_path} not provided")
logger.critical(f"Private key for {file_path.name} not provided")
continue


Expand Down
Binary file added inputs/hello3.c4gh
Binary file not shown.
19 changes: 19 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Shared fixtures for tests."""

import shutil

import pytest

from tests.utils import INPUT_DIR


@pytest.fixture(name="encrypted_files")
def fixture_encrypted_files(tmp_path):
"""Returns temporary copies of encrypted files.
First two files are encrypted with alice.pk. Third file is encrypted with bob.pk.
"""
encrypted_files = [INPUT_DIR/"hello.c4gh", INPUT_DIR/"hello2.c4gh", INPUT_DIR/"hello3.c4gh"]
temp_files = [tmp_path/f.name for f in encrypted_files]
for src, dest in zip(encrypted_files, temp_files):
shutil.copy(src, dest)
return temp_files
117 changes: 117 additions & 0 deletions tests/decryption/test_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""Integration tests for decrypt.py."""

import logging
import os
from pathlib import Path
import shutil
from unittest import mock

import pytest

from crypt4gh_middleware.decrypt import main
from tests.utils import INPUT_DIR, INPUT_TEXT, patch_cli


@pytest.fixture(name="secret_keys")
def fixture_secret_keys(tmp_path):
"""Returns temporary copies of secret keys."""
encrypted_files = [INPUT_DIR/"alice.sec", INPUT_DIR/"bob.sec"]
temp_files = [tmp_path/"alice.sec", tmp_path/"bob.sec"]
for src, dest in zip(encrypted_files, temp_files):
shutil.copy(src, dest)
return temp_files


@pytest.fixture(name="string_paths")
def fixture_string_paths(encrypted_files, secret_keys):
"""Returns string version of file paths for use in patch_cli."""
return [str(f) for f in (encrypted_files + secret_keys)]


def files_decrypted_successfully(encrypted_files, tmp_path):
"""Check if encrypted files have been decrypted."""
for filename in encrypted_files:
with open(tmp_path/filename, encoding="utf-8") as f:
if f.read() != INPUT_TEXT:
return False
return True


def test_decryption(encrypted_files, string_paths, tmp_path):
"""Test that files are decrypted successfully."""
with patch_cli(["decrypt.py", "--output-dir", str(tmp_path)] + string_paths):
main()
assert files_decrypted_successfully(encrypted_files=encrypted_files, tmp_path=tmp_path)


def test_default_dir(encrypted_files, string_paths, tmp_path):
"""Test that $TMPDIR is used when no output dir is provided."""
with (patch_cli(["decrypt.py"] + string_paths),
mock.patch.dict(os.environ, {"TMPDIR": str(tmp_path)})):
main()
assert files_decrypted_successfully(encrypted_files=encrypted_files, tmp_path=tmp_path)


def test_missing_tmpdir(encrypted_files, string_paths, monkeypatch):
"""Test that ./tmpdir is used when no output dir is specified and $TMPDIR is not set."""
monkeypatch.delenv("TMPDIR", raising=False)
with patch_cli(["decrypt.py"] + string_paths):
main()
assert files_decrypted_successfully(encrypted_files=encrypted_files,
tmp_path=Path("tmpdir"))


def test_no_args():
"""Test that an exception is thrown when no arguments are provided."""
with patch_cli(["decrypt.py"]) as patched_cli, pytest.raises(SystemExit) as exc_info:
main()
assert "usage:" in str(exc_info.value)
assert patched_cli.stderr.getvalue().startswith("usage:")


@pytest.mark.parametrize("keys", [[], "secret_keys"])
def test_no_valid_sk_provided(encrypted_files, caplog, keys, request):
"""Test that error messages are printed when no keys or invalid secret keys are provided."""
# Fixture names passed to pytest.mark.parametrize are strings, so get value
if isinstance(keys, str):
# bob.pk was not used to encrypt hello.c4gh and hello2.c4gh
keys = [request.getfixturevalue(keys)[1]]

with (patch_cli(["decrypt.py"] + [str(f) for f in (encrypted_files[:2] + keys)]),
caplog.at_level(logging.CRITICAL)):
main()
assert f"Private key for {encrypted_files[0].name} not provided" in caplog.text
assert f"Private key for {encrypted_files[1].name} not provided" in caplog.text


def test_one_sk_provided(encrypted_files, caplog, secret_keys):
"""Test that appropriate error message is printed when only one valid secret key is provided."""
with (patch_cli(["decrypt.py"] + [str(f) for f in (encrypted_files + [secret_keys[0]])]),
caplog.at_level(logging.CRITICAL)):
main()
assert f"Private key for {encrypted_files[2].name} not provided" in caplog.text


def test_invalid_output_dir(string_paths):
"""Test that an exception occurs when an invalid output directory is provided."""
with (patch_cli(["decrypt.py", "--output-dir", "bad_dir"] + string_paths),
pytest.raises(FileNotFoundError)):
main()


def test_output_dir_no_write_permission(string_paths, tmp_path):
"""Test handling of output directory without write permissions."""
no_write_dir = tmp_path/"no_write"
no_write_dir.mkdir()
no_write_dir.chmod(0o555)
with (patch_cli(["decrypt.py", "--output-dir", str(no_write_dir)] + string_paths),
pytest.raises(PermissionError)):
main()


def test_no_files_in_output_dir_on_exception(string_paths, tmp_path):
"""Test that no files are in the output directory when an exception occurs."""
with (patch_cli(["decrypt.py", "--output-dir", str(tmp_path)] + string_paths + ["bad_file"]),
pytest.raises(FileNotFoundError)):
main()
assert not any(file.exists() for file in tmp_path.iterdir())
4 changes: 4 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
""" Utility functions for tests."""
from functools import wraps
import contextlib
from pathlib import Path
import signal
from unittest import mock

INPUT_DIR = Path(__file__).parents[1]/"inputs"
INPUT_TEXT = "hello world from the input!"


@contextlib.contextmanager
def patch_cli(args):
Expand Down

0 comments on commit 40c1ab7

Please sign in to comment.