feature(keyringctl): adding basic infrastructure for running tests

This commit is contained in:
Levente Polyak 2021-10-31 18:53:20 +01:00
parent 8ba7dc1dc9
commit 0ca74e243b
No known key found for this signature in database
GPG Key ID: FC1B547C8D8172C8
8 changed files with 299 additions and 37 deletions

View File

@ -2,49 +2,39 @@
image: archlinux:latest
stages:
- lint
- integration
variables:
PACMAN_CACHE: "${CI_PROJECT_DIR}/.pacman/pkg"
cache:
paths:
- .pacman/pkg
key: ${CI_JOB_NAME}
check-new-key:
stage: lint
needs: []
script:
- install -d "${PACMAN_CACHE}"
- pacman -Syu --needed --noconfirm --cachedir "${PACMAN_CACHE}" git grep hopenpgp-tools sequoia-keyring-linter
- ./.gitlab/check-keyids-change
only:
refs:
- merge_requests
changes:
- master-keyids
- packager-keyids
- test
lint:
stage: lint
stage: test
needs: []
before_script:
- install -d "${PACMAN_CACHE}"
- pacman -Syu --needed --noconfirm --cachedir "${PACMAN_CACHE}" make flake8 mypy python-black python-isort
- pacman -Syu --needed --noconfirm make flake8 mypy python-black python-isort
script:
- make lint
only:
refs:
- merge_requests
changes:
- keyringctl
- libkeyringctl/*
- test/*
test:
stage: test
needs: []
before_script:
- pacman -Syu --needed --noconfirm make python sequoia-sq python-pytest
script:
- make test
only:
changes:
- keyringctl
- libkeyringctl/*
- test/*
build_install:
stage: integration
stage: test
needs: []
before_script:
- install -d "${PACMAN_CACHE}"
- pacman -Syu --needed --noconfirm --cachedir "${PACMAN_CACHE}" make python sequoia-sq
- pacman -Syu --needed --noconfirm make python sequoia-sq
script:
- ./keyringctl import --main master master-revoked
- ./keyringctl import packager packager-revoked

View File

@ -31,6 +31,7 @@ and develop this project:
* python-black
* python-isort
* python-pytest
* flake8
* mypy
@ -41,3 +42,15 @@ The `keyringctl` script is written in typed python, which makes use of
The script is type checked, linted and formatted using standard tooling.
When providing a merge request make sure to run `make lint`.
## Testing
Test cases are developed per module in the [test](test) directory and should
consist of atomic single expectation tests. A Huge test case asserting various
different expectations are discouraged and should be split into finer grained
test cases.
To execute all tests using pytest
```bash
make test
```

View File

@ -14,6 +14,9 @@ fmt:
black .
isort .
test:
py.test
build:
./keyringctl -v build
@ -24,4 +27,4 @@ uninstall:
rm -f $(KEYRING_TARGET_DIR)/archlinux{.gpg,-trusted,-revoked}
rmdir -p --ignore-fail-on-non-empty $(KEYRING_TARGET_DIR)
.PHONY: build install lint uninstall
.PHONY: all lint fmt test build install uninstall

View File

@ -118,7 +118,7 @@ def convert_certificate( # noqa: ignore=C901
Returns
-------
The path of the user_dir (which is located below working_dir)
The path of the key directory (which is located below working_dir below the user_dir)
"""
# root packets
@ -279,7 +279,7 @@ def convert_certificate( # noqa: ignore=C901
key_dir=key_dir,
)
return user_dir
return key_dir
def persist_public_key(
@ -579,8 +579,9 @@ def convert(
)
for path in directories:
(target_dir / path.name).mkdir(parents=True, exist_ok=True)
copytree(src=path, dst=(target_dir / path.name), dirs_exist_ok=True)
user_dir = path.parent
(target_dir / user_dir.name).mkdir(parents=True, exist_ok=True)
copytree(src=user_dir, dst=(target_dir / user_dir.name), dirs_exist_ok=True)
return target_dir

View File

@ -11,6 +11,7 @@ from typing import List
from typing import Optional
from .types import Fingerprint
from .types import Uid
from .types import Username
from .util import cwd
from .util import natural_sort_path
@ -222,3 +223,63 @@ def latest_certification(certifications: Iterable[Path]) -> Path:
lambda a, b: a if packet_signature_creation_time(a) > packet_signature_creation_time(b) else b,
certifications,
)
def key_generate(uids: List[Uid], outfile: Path) -> str:
"""Generate a PGP key with specific uids
Parameters
----------
uids: List of uids that the key should have
outfile: Path to the file to which the key should be written to
Returns
-------
The result of the key generate call
"""
cmd = ["sq", "key", "generate"]
for uid in uids:
cmd.extend(["--userid", str(uid)])
cmd.extend(["--export", str(outfile)])
return system(cmd)
def key_extract_certificate(key: Path, output: Optional[Path]) -> str:
"""Extracts the non secret part from a key into a certificate
Parameters
----------
key: Path to a file that contain secret key material
output: Path to the file to which the key should be written to, stdout if None
Returns
-------
The result of the extract in case output is None
"""
cmd = ["sq", "key", "extract-cert", str(key)]
if output:
cmd.extend(["--output", str(output)])
return system(cmd)
def certify(key: Path, certificate: Path, uid: Uid, output: Optional[Path]) -> str:
"""Inspect PGP packet data and return the result
Parameters
----------
key: Path to a file that contain secret key material
certificate: Path to a certificate file whose uid should be certified
uid: Uid contain in the certificate that should be certified
output: Path to the file to which the key should be written to, stdout if None
Returns
-------
The result of the certification in case output is None
"""
cmd = ["sq", "certify", str(key), str(certificate), uid]
if output:
cmd.extend(["--output", str(output)])
return system(cmd)

0
test/__init__.py Normal file
View File

126
test/conftest.py Normal file
View File

@ -0,0 +1,126 @@
from collections import defaultdict
from functools import wraps
from pathlib import Path
from shutil import copytree
from tempfile import TemporaryDirectory
from typing import Dict
from typing import List
from typing import Set
from pytest import fixture
from libkeyringctl.keyring import convert_certificate
from libkeyringctl.keyring import simplify_user_id
from libkeyringctl.sequoia import certify
from libkeyringctl.sequoia import key_extract_certificate
from libkeyringctl.sequoia import key_generate
from libkeyringctl.types import Fingerprint
from libkeyringctl.types import Uid
from libkeyringctl.types import Username
from libkeyringctl.util import cwd
test_keys: Dict[Username, List[Path]] = defaultdict(list)
test_certificates: Dict[Username, List[Path]] = defaultdict(list)
test_keyring_certificates: Dict[Username, List[Path]] = defaultdict(list)
test_main_fingerprints: Set[Fingerprint] = set()
@fixture(autouse=True)
def reset_storage():
test_keys.clear()
test_certificates.clear()
test_keyring_certificates.clear()
test_main_fingerprints.clear()
def create_certificate(username: Username, uids: List[Uid], keyring_type: str = "packager", func=None):
def decorator(decorated_func):
@wraps(decorated_func)
def wrapper(working_dir: Path, *args, **kwargs):
print(username)
key_directory = working_dir / "secret" / f"{id}"
key_directory.mkdir(parents=True, exist_ok=True)
key_file: Path = key_directory / f"{username}.asc"
key_generate(uids=uids, outfile=key_file)
test_keys[username].append(key_file)
certificate_directory = working_dir / "certificate" / f"{id}"
certificate_directory.mkdir(parents=True, exist_ok=True)
keyring_root: Path = working_dir / "keyring"
keyring_root.mkdir(parents=True, exist_ok=True)
certificate_file: Path = certificate_directory / f"{username}.asc"
key_extract_certificate(key=key_file, output=certificate_file)
test_certificates[username].append(certificate_file)
target_dir = keyring_root / keyring_type
decomposed_path: Path = convert_certificate(
working_dir=working_dir,
certificate=certificate_file,
keyring_dir=keyring_root / keyring_type,
)
user_dir = decomposed_path.parent
(target_dir / user_dir.name).mkdir(parents=True, exist_ok=True)
copytree(src=user_dir, dst=(target_dir / user_dir.name), dirs_exist_ok=True)
test_keyring_certificates[username].append(target_dir / user_dir.name / decomposed_path.name)
if "main" == keyring_type:
test_main_fingerprints.add(Fingerprint(decomposed_path.name))
decorated_func(working_dir=working_dir, *args, **kwargs)
return wrapper
if not func:
return decorator
return decorator(func)
def create_uid_certification(issuer: Username, certified: Username, uid: Uid, func=None):
def decorator(decorated_func):
@wraps(decorated_func)
def wrapper(working_dir: Path, *args, **kwargs):
key: Path = test_keys[issuer][0]
certificate: Path = test_certificates[certified][0]
fingerprint: Fingerprint = Fingerprint(test_keyring_certificates[certified][0].name)
issuer_fingerprint: Fingerprint = Fingerprint(test_keyring_certificates[issuer][0].name)
simplified_uid = simplify_user_id(uid)
output: Path = (
working_dir
/ "keyring"
/ "packager"
/ certified
/ fingerprint
/ "uid"
/ simplified_uid
/ "certification"
/ f"{issuer_fingerprint}.asc"
)
output.parent.mkdir(parents=True, exist_ok=True)
certify(key, certificate, uid, output)
decorated_func(working_dir=working_dir, *args, **kwargs)
return wrapper
if not func:
return decorator
return decorator(func)
@fixture(scope="function")
def working_dir():
with TemporaryDirectory(prefix="arch-keyringctl-test-") as tempdir:
with cwd(tempdir):
yield Path(tempdir)
@fixture(scope="function")
def keyring_dir(working_dir: Path):
yield working_dir / "keyring"

68
test/test_trust.py Normal file
View File

@ -0,0 +1,68 @@
from pathlib import Path
from libkeyringctl.trust import certificate_trust
from libkeyringctl.types import Trust
from libkeyringctl.types import Uid
from libkeyringctl.types import Username
from .conftest import create_certificate
from .conftest import create_uid_certification
from .conftest import test_keyring_certificates
from .conftest import test_main_fingerprints
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")], keyring_type="main")
def test_certificate_trust_main_key_has_full_trust(working_dir: Path, keyring_dir: Path):
trust = certificate_trust(
test_keyring_certificates[Username("foobar")][0],
test_main_fingerprints,
)
assert Trust.full == trust
@create_certificate(username=Username("main"), uids=[Uid("main <foo@bar.xyz>")])
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")])
def test_certificate_trust_no_signature_is_unknown(working_dir: Path, keyring_dir: Path):
trust = certificate_trust(
test_keyring_certificates[Username("foobar")][0],
test_main_fingerprints,
)
assert Trust.unknown == trust
@create_certificate(username=Username("main"), uids=[Uid("main <foo@bar.xyz>")], keyring_type="main")
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")])
@create_uid_certification(issuer=Username("main"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
def test_certificate_trust_one_signature_is_marginal(working_dir: Path, keyring_dir: Path):
trust = certificate_trust(
test_keyring_certificates[Username("foobar")][0],
test_main_fingerprints,
)
assert Trust.marginal == trust
@create_certificate(username=Username("main"), uids=[Uid("main <foo@bar.xyz>")], keyring_type="main")
@create_certificate(username=Username("not_main"), uids=[Uid("main <foo@bar.xyz>")])
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")])
@create_uid_certification(issuer=Username("not_main"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
def test_certificate_trust_one_none_main_signature_gives_no_trust(working_dir: Path, keyring_dir: Path):
trust = certificate_trust(
test_keyring_certificates[Username("foobar")][0],
test_main_fingerprints,
)
assert Trust.unknown == trust
@create_certificate(username=Username("main1"), uids=[Uid("main1 <foo@bar.xyz>")], keyring_type="main")
@create_certificate(username=Username("main2"), uids=[Uid("main2 <foo@bar.xyz>")], keyring_type="main")
@create_certificate(username=Username("main3"), uids=[Uid("main3 <foo@bar.xyz>")], keyring_type="main")
@create_certificate(username=Username("foobar"), uids=[Uid("foobar <foo@bar.xyz>")])
@create_uid_certification(issuer=Username("main1"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
@create_uid_certification(issuer=Username("main2"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
@create_uid_certification(issuer=Username("main3"), certified=Username("foobar"), uid=Uid("foobar <foo@bar.xyz>"))
def test_certificate_trust_three_main_signature_gives_full_trust(working_dir: Path, keyring_dir: Path):
trust = certificate_trust(
test_keyring_certificates[Username("foobar")][0],
test_main_fingerprints,
)
assert Trust.full == trust