Initial version with identity handling
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
30
tests/conftest.py
Normal file
30
tests/conftest.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Test configuration and global fixtures
|
||||
"""
|
||||
|
||||
from ed25519 import SigningKey
|
||||
import pytest
|
||||
from _pytest.fixtures import SubRequest
|
||||
|
||||
from earthsnake.identity import Identity
|
||||
|
||||
from .helpers import random_name
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def identity(request: SubRequest) -> Identity:
|
||||
"""A valid identity"""
|
||||
|
||||
seed = b''
|
||||
name = random_name()
|
||||
|
||||
for marker in request.node.iter_markers('id_key_seed'):
|
||||
for seed in marker.args:
|
||||
pass
|
||||
|
||||
for marker in request.node.iter_markers('id_name'):
|
||||
for name in marker.args:
|
||||
pass
|
||||
|
||||
sign = SigningKey(seed)
|
||||
|
||||
return Identity(name, sign_key=sign)
|
12
tests/helpers.py
Normal file
12
tests/helpers.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""Helper functions for tests
|
||||
"""
|
||||
|
||||
from random import choice
|
||||
|
||||
from earthsnake.types import ALPHA_LOWER, ALPHA_LOWER_OR_DIGIT
|
||||
|
||||
|
||||
def random_name() -> str:
|
||||
"""Generate a valid random author name"""
|
||||
|
||||
return choice(ALPHA_LOWER) + ''.join(choice(ALPHA_LOWER_OR_DIGIT) for _ in range(3))
|
56
tests/test_base32.py
Normal file
56
tests/test_base32.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""Tests for the base32 encode/decode utilities
|
||||
"""
|
||||
|
||||
from base64 import _b32decode, _b32encode # type: ignore
|
||||
from base64 import b32decode, b32encode
|
||||
from random import choice
|
||||
|
||||
import pytest
|
||||
|
||||
from earthsnake.base32 import base32_bytes_to_string, base32_string_to_bytes
|
||||
from earthsnake.types import B32_CHAR
|
||||
|
||||
|
||||
def test_alphabet() -> None:
|
||||
"""Test if Python’s Base32 alphabet is the uppercase version of the one we expect"""
|
||||
|
||||
alphabet = B32_CHAR.encode('utf-8')
|
||||
test_data = b'\x00D2\x14\xc7BT\xb65\xcf\x84e:V\xd7\xc6u\xbew\xdf'
|
||||
test_encoded = B32_CHAR.upper().encode('utf-8')
|
||||
|
||||
assert _b32encode(alphabet, test_data) == b32encode(test_data).lower()
|
||||
assert _b32decode(alphabet, alphabet) == b32decode(test_encoded)
|
||||
|
||||
|
||||
def test_bytes_to_string() -> None:
|
||||
"""Test if base32_bytes_to_string encodes bytes as expected"""
|
||||
|
||||
data = bytes(choice(range(255)) for _ in range(10))
|
||||
our_version = base32_bytes_to_string(data)
|
||||
py_version = 'b' + b32encode(data).lower().strip(b'=').decode('utf-8')
|
||||
|
||||
assert our_version == py_version
|
||||
|
||||
|
||||
def test_string_to_bytes_noprefix() -> None:
|
||||
"""Test if base32_string_to_bytes refuses to decode strings without the 'b' prefix"""
|
||||
|
||||
with pytest.raises(ValueError) as ctx:
|
||||
base32_string_to_bytes('test')
|
||||
|
||||
assert 'it should start with a \'b\'' in str(ctx.value)
|
||||
|
||||
|
||||
def test_string_to_bytes_padding() -> None:
|
||||
"""Test if base32_string_to_bytes refuses to decode strings with padding characters"""
|
||||
|
||||
with pytest.raises(ValueError) as ctx:
|
||||
base32_string_to_bytes('btest=')
|
||||
|
||||
assert 'it contains padding characters (\'=\')' in str(ctx.value)
|
||||
|
||||
|
||||
def test_string_to_bytes() -> None:
|
||||
"""Test if base32_string_to_bytes dedoces strings as expected"""
|
||||
|
||||
assert base32_string_to_bytes('borsxg5a') == b'test'
|
185
tests/test_identity.py
Normal file
185
tests/test_identity.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""Tests for the Identity class
|
||||
"""
|
||||
|
||||
import bip39
|
||||
from ed25519 import SigningKey, create_keypair
|
||||
import pytest
|
||||
from pytest_mock.plugin import MockerFixture
|
||||
|
||||
from earthsnake.exc import ValidationError
|
||||
from earthsnake.identity import Identity
|
||||
|
||||
from .helpers import random_name
|
||||
|
||||
TEST_SEED = (
|
||||
b'\xe5:\xc9\x95$\x9d\xc5F\xee\xe6\x84\xbe\xcc\xda^\xc4'
|
||||
b'z\x84\xb7\xd2\x02q\xfa\xe8W\xd8z\x05E\xfb2\xd5'
|
||||
)
|
||||
TEST_MNEMONIC = bip39.encode_bytes(TEST_SEED)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'name',
|
||||
[
|
||||
pytest.param('s', id='veryshort'),
|
||||
pytest.param('sho', id='short'),
|
||||
pytest.param('longy', id='long'),
|
||||
pytest.param('verylong', id='verylong'),
|
||||
pytest.param('0num', id='numberstart'),
|
||||
],
|
||||
)
|
||||
def test_init_bad_name(name: str) -> None:
|
||||
"""Test if initialisation is not possible with an invalid name"""
|
||||
|
||||
sign, verify = create_keypair()
|
||||
|
||||
with pytest.raises(ValidationError) as ctx:
|
||||
Identity(name, verify_key=verify, sign_key=sign)
|
||||
|
||||
assert 'Invalid name' in str(ctx.value)
|
||||
|
||||
|
||||
def test_init_key_mismatch() -> None:
|
||||
"""Test if initialisation fails if the signing and verifying keys don’t match"""
|
||||
|
||||
sign1, _ = create_keypair()
|
||||
_, verify2 = create_keypair()
|
||||
|
||||
with pytest.raises(ValidationError) as ctx:
|
||||
Identity('name', verify_key=verify2, sign_key=sign1)
|
||||
|
||||
assert 'Signing and verifying keys don’t match' in str(ctx.value)
|
||||
|
||||
|
||||
def test_init_no_keys() -> None:
|
||||
"""Test if initialisation is not possible without keys"""
|
||||
|
||||
with pytest.raises(ValidationError) as ctx:
|
||||
Identity('name')
|
||||
|
||||
assert 'At least verify_key must be present' in str(ctx.value)
|
||||
|
||||
|
||||
@pytest.mark.id_key_seed(TEST_SEED)
|
||||
@pytest.mark.id_name('test')
|
||||
def test_str(identity: Identity) -> None:
|
||||
"""Test if the __str__ method returns the author address"""
|
||||
|
||||
assert (
|
||||
str(identity) == '@test.bcz76z52y5dlpohtkmpuj3jsdcvfmebzpcgfmtmhu4u7hlexzreya'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.id_key_seed(TEST_SEED)
|
||||
@pytest.mark.id_name('test')
|
||||
def test_repr(identity: Identity) -> None:
|
||||
"""Test if the __str__ method returns the author address"""
|
||||
|
||||
assert (
|
||||
repr(identity) == '<Identity @test.bcz76z52y5dlpohtkmpuj3jsdcvfmebzpcgfmtmhu4u7hlexzreya>'
|
||||
)
|
||||
|
||||
|
||||
def test_from_address() -> None:
|
||||
"""Test loading an identity from an author address"""
|
||||
|
||||
skey = SigningKey(TEST_SEED)
|
||||
vkey = skey.get_verifying_key()
|
||||
identity = Identity.from_address(
|
||||
'@test.bcz76z52y5dlpohtkmpuj3jsdcvfmebzpcgfmtmhu4u7hlexzreya'
|
||||
)
|
||||
|
||||
assert identity.name == 'test'
|
||||
assert identity.sign_key is None
|
||||
assert identity.verify_key.to_bytes() == vkey.to_bytes()
|
||||
|
||||
|
||||
def test_from_invalid_address() -> None:
|
||||
"""Test loading an identity from an invalid address"""
|
||||
|
||||
with pytest.raises(ValidationError) as ctx:
|
||||
Identity.from_address('@inva.lid')
|
||||
|
||||
assert 'Invalid address @inva.lid' in str(ctx.value)
|
||||
|
||||
|
||||
def test_generate(mocker: MockerFixture) -> None:
|
||||
"""Test the generate property"""
|
||||
|
||||
skey = SigningKey(TEST_SEED)
|
||||
vkey = skey.get_verifying_key()
|
||||
|
||||
mocker.patch('earthsnake.identity.create_keypair', return_value=(skey, vkey))
|
||||
identity = Identity.generate('test')
|
||||
|
||||
assert (
|
||||
str(identity) == '@test.bcz76z52y5dlpohtkmpuj3jsdcvfmebzpcgfmtmhu4u7hlexzreya'
|
||||
)
|
||||
assert identity.sign_key
|
||||
assert identity.verify_key
|
||||
assert identity.name == 'test'
|
||||
|
||||
|
||||
@pytest.mark.id_key_seed(TEST_SEED)
|
||||
@pytest.mark.id_name('test')
|
||||
def test_mnemonic(identity: Identity) -> None:
|
||||
"""Test the mnemonic property"""
|
||||
|
||||
assert identity.mnemonic == f'test {TEST_MNEMONIC}'
|
||||
|
||||
|
||||
def test_mnemonic_no_signing_key() -> None:
|
||||
"""Test if the mnemonic property returns None if there is no signing key"""
|
||||
|
||||
identity = Identity.from_address(
|
||||
'@test.bcz76z52y5dlpohtkmpuj3jsdcvfmebzpcgfmtmhu4u7hlexzreya'
|
||||
)
|
||||
|
||||
assert identity.mnemonic is None
|
||||
|
||||
|
||||
def test_from_mnemonic() -> None:
|
||||
"""Test if identities can be loaded from mnemonics"""
|
||||
|
||||
name = random_name()
|
||||
identity = Identity.from_mnemonic(f'{name} {TEST_MNEMONIC}')
|
||||
|
||||
assert identity.name == name
|
||||
assert identity.sign_key
|
||||
assert identity.sign_key.to_seed() == TEST_SEED
|
||||
assert (
|
||||
str(identity)
|
||||
== f'@{name}.bcz76z52y5dlpohtkmpuj3jsdcvfmebzpcgfmtmhu4u7hlexzreya'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'address',
|
||||
[
|
||||
pytest.param(
|
||||
'noat.bcz76z52y5dlpohtkmpuj3jsdcvfmebzpcgfmtmhu4u7hlexzreya', id='no_at'
|
||||
),
|
||||
pytest.param(
|
||||
'@toolong.bcz76z52y5dlpohtkmpuj3jsdcvfmebzpcgfmtmhu4u7hlexzreya',
|
||||
id='too_long',
|
||||
),
|
||||
pytest.param('@test.invalidkey', id='invalid_key'),
|
||||
pytest.param(
|
||||
'@test.cz76z52y5dlpohtkmpuj3jsdcvfmebzpcgfmtmhu4u7hlexzreya',
|
||||
id='nonprefixed_key',
|
||||
),
|
||||
pytest.param('@test.many.periods', id='many_periods'),
|
||||
],
|
||||
)
|
||||
def test_valid_address_invalid(address: str) -> None:
|
||||
"""Test if valid_address fails for invalid addresses"""
|
||||
|
||||
assert Identity.valid_address(address) is False
|
||||
|
||||
|
||||
def test_valid_address() -> None:
|
||||
"""Test if valid_address passes on valid addresses"""
|
||||
|
||||
assert Identity.valid_address(
|
||||
'@test.bcz76z52y5dlpohtkmpuj3jsdcvfmebzpcgfmtmhu4u7hlexzreya'
|
||||
)
|
Reference in New Issue
Block a user