feature(keyringctl): adding basic infrastructure for running tests
This commit is contained in:
parent
8ba7dc1dc9
commit
0ca74e243b
@ -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
|
||||
|
@ -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
|
||||
```
|
||||
|
5
Makefile
5
Makefile
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
0
test/__init__.py
Normal file
126
test/conftest.py
Normal file
126
test/conftest.py
Normal 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
68
test/test_trust.py
Normal 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
|
Loading…
Reference in New Issue
Block a user