parent
b585814ab8
commit
d602d9c3ba
2 changed files with 174 additions and 0 deletions
@ -0,0 +1,61 @@ |
||||
"""Share handling""" |
||||
|
||||
from random import choice |
||||
import re |
||||
from typing import Optional |
||||
|
||||
from .exc import ValidationError |
||||
from .types import ALPHA_LOWER, ALPHA_LOWER_OR_DIGIT |
||||
|
||||
|
||||
class Share: |
||||
"""Class to handle a share (space or workspace in older terminology)""" |
||||
|
||||
_NAME_PATTERN = f'[{ALPHA_LOWER}][{ALPHA_LOWER_OR_DIGIT}]{{0,14}}' |
||||
_SUFFIX_PATTERN = f'[{ALPHA_LOWER}][{ALPHA_LOWER_OR_DIGIT}]{{0,52}}' |
||||
PATTERN = f'^[+]{_NAME_PATTERN}[.]{_SUFFIX_PATTERN}$' |
||||
|
||||
def __init__(self, name: str, suffix: Optional[str] = None) -> None: |
||||
suffix = suffix or self.generate_random_suffix() |
||||
|
||||
if not re.match(f'^{self._NAME_PATTERN}$', name): |
||||
raise ValidationError(f'Invalid name "{name}"') |
||||
|
||||
if not re.match(f'^{self._SUFFIX_PATTERN}$', suffix): |
||||
raise ValidationError(f'Invalid suffix "{suffix}"') |
||||
|
||||
self.name = name |
||||
self.suffix = suffix |
||||
|
||||
@classmethod |
||||
def validate_address(cls, share_address: str) -> None: |
||||
"""Check if share_address is a valid share address""" |
||||
|
||||
if not re.match(cls.PATTERN, share_address): |
||||
raise ValidationError(f'Invalid share address {share_address}') |
||||
|
||||
@classmethod |
||||
def from_address(cls, share_address: str) -> 'Share': |
||||
"""Create a Share object from a share address""" |
||||
|
||||
cls.validate_address(share_address) |
||||
|
||||
name, suffix = share_address[1:].split('.') |
||||
|
||||
return cls(name, suffix) |
||||
|
||||
@staticmethod |
||||
def generate_random_suffix(length: int = 53) -> str: |
||||
"""Generate a random share suffix of the desired length""" |
||||
|
||||
assert 54 > length > 0 |
||||
|
||||
return choice(ALPHA_LOWER) + ''.join( |
||||
choice(ALPHA_LOWER_OR_DIGIT) for _ in range(length - 1) |
||||
) |
||||
|
||||
def __str__(self) -> str: |
||||
return f'+{self.name}.{self.suffix}' |
||||
|
||||
def __repr__(self) -> str: |
||||
return f'<Share {self}>' |
@ -0,0 +1,113 @@ |
||||
"""Tests for the Share class""" |
||||
|
||||
import pytest |
||||
|
||||
from earthsnake.types import ALPHA_LOWER, ALPHA_LOWER_OR_DIGIT |
||||
from earthsnake.exc import ValidationError |
||||
from earthsnake.share import Share |
||||
|
||||
|
||||
@pytest.mark.parametrize( |
||||
'name,suffix,error', |
||||
[ |
||||
pytest.param('80smusic', 'suffix', 'Invalid name "80smusic"', id='name_digitstart'), |
||||
pytest.param('a.b', 'suffix', 'Invalid name "a.b"', id='name_period'), |
||||
pytest.param('PARTY', 'suffix', 'Invalid name "PARTY"', id='name_uppercase'), |
||||
pytest.param('test', 'b.c', 'Invalid suffix "b.c"', id='suffix_period'), |
||||
pytest.param('test', '4ever', 'Invalid suffix "4ever"', id='suffix_digitstart'), |
||||
pytest.param('test', 'TIME', 'Invalid suffix "TIME"', id='suffix_uppercase'), |
||||
], |
||||
) |
||||
def test_create_invalid(name: str, suffix: str, error: str) -> None: |
||||
"""Test if share creation fails with invalid values""" |
||||
|
||||
with pytest.raises(ValidationError) as ctx: |
||||
Share(name, suffix=suffix) |
||||
|
||||
assert str(ctx.value) == error |
||||
|
||||
|
||||
def test_create() -> None: |
||||
"""Test if creating a share with a name and a suffix succeeds""" |
||||
|
||||
share = Share('test', suffix='suffix') |
||||
|
||||
assert share.name == 'test' |
||||
assert share.suffix == 'suffix' |
||||
|
||||
|
||||
def test_create_no_suffix() -> None: |
||||
"""Test if creating succeeds if no suffix is given, and the suffix is randomly chosen""" |
||||
|
||||
share = Share('test') |
||||
|
||||
assert share.name == 'test' |
||||
assert len(share.suffix) == 53 |
||||
|
||||
|
||||
def test_str() -> None: |
||||
"""Test the __str__ method""" |
||||
|
||||
share = Share('test', 'suffix') |
||||
|
||||
assert str(share) == '+test.suffix' |
||||
|
||||
|
||||
def test_repr() -> None: |
||||
"""Test the __repr__ method""" |
||||
|
||||
share = Share('test', 'suffix') |
||||
|
||||
assert repr(share) == '<Share +test.suffix>' |
||||
|
||||
|
||||
@pytest.mark.parametrize( |
||||
'address', |
||||
( |
||||
pytest.param('test.suffix', id='no_prefix'), |
||||
pytest.param('+t.st.suffix', id='invalid_name_char'), |
||||
), |
||||
) |
||||
def test_validate_invalid(address: str) -> None: |
||||
"""Test if validate_address fails if the address is invalid""" |
||||
|
||||
with pytest.raises(ValidationError) as ctx: |
||||
Share.validate_address(address) |
||||
|
||||
assert str(ctx.value) == f'Invalid share address {address}' |
||||
|
||||
|
||||
@pytest.mark.parametrize( |
||||
'address', |
||||
( |
||||
pytest.param('test.suffix', id='no_prefix'), |
||||
pytest.param('+t.st.suffix', id='invalid_name_char'), |
||||
), |
||||
) |
||||
def test_from_address_invalid(address: str) -> None: |
||||
"""Test if from_address fails if the address is invalid""" |
||||
|
||||
with pytest.raises(ValidationError) as ctx: |
||||
Share.from_address(address) |
||||
|
||||
assert str(ctx.value) == f'Invalid share address {address}' |
||||
|
||||
|
||||
def test_from_address() -> None: |
||||
"""Test constructing a share from a string address""" |
||||
|
||||
share = Share.from_address('+test.suffix') |
||||
|
||||
assert share.name == 'test' |
||||
assert share.suffix == 'suffix' |
||||
|
||||
|
||||
@pytest.mark.parametrize('length', range(1, 54)) |
||||
def test_generate_suffix(length: int) -> None: |
||||
"""Test the random suffix generator""" |
||||
|
||||
suffix = Share.generate_random_suffix(length=length) |
||||
|
||||
assert suffix[0] in ALPHA_LOWER |
||||
assert all(char in ALPHA_LOWER_OR_DIGIT for char in suffix) |
||||
assert len(suffix) == length |
Loading…
Reference in new issue