98 lines
3.1 KiB
Python
98 lines
3.1 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
import pytest
|
|
|
|
from devplacepy.services.sync.normalize import (
|
|
clamp_instant,
|
|
clean_text,
|
|
fingerprint,
|
|
instant_iso,
|
|
normalize_origin,
|
|
normalize_origin_uid,
|
|
normalize_username,
|
|
parse_instant,
|
|
username_candidates,
|
|
)
|
|
from devplacepy.services.sync.spec import LIMITS
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"raw,expected",
|
|
[
|
|
("Other.Example.ORG", "other.example.org"),
|
|
(" a1 ", "a1"),
|
|
("devplace-net", "devplace-net"),
|
|
],
|
|
)
|
|
def test_origin_is_normalized(raw, expected):
|
|
assert normalize_origin(raw) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("raw", ["", "a", "-bad", "bad-", "a..b", "has space", "x" * 64, "ünï.org"])
|
|
def test_origin_is_rejected(raw):
|
|
with pytest.raises(ValueError):
|
|
normalize_origin(raw)
|
|
|
|
|
|
@pytest.mark.parametrize("raw", ["", " ", "a b", "x" * 65, "a/b"])
|
|
def test_origin_uid_is_rejected(raw):
|
|
with pytest.raises(ValueError):
|
|
normalize_origin_uid(raw)
|
|
|
|
|
|
def test_origin_uid_accepts_integers_and_uuids():
|
|
assert normalize_origin_uid(42) == "42"
|
|
assert normalize_origin_uid("01a139e9-a2f0-7fa1-8635-1a98fbf95e3d")
|
|
|
|
|
|
def test_clean_text_strips_control_characters_and_newlines():
|
|
assert clean_text("a\x00b\r\nc\rd\x07", multiline=True) == "ab\nc\nd"
|
|
assert clean_text("a \n b\t c", multiline=False) == "a b c"
|
|
|
|
|
|
def test_username_always_valid_across_inputs():
|
|
samples = ["", "a", "ab", "Ünï cøde!", "x" * 80, "--__--", "john.doe@mail", "日本語", "ok_name-1"]
|
|
for sample in samples:
|
|
name = normalize_username(sample, "uid-1")
|
|
assert LIMITS.min_username_length <= len(name) <= LIMITS.max_username_length
|
|
assert all(ch.isalnum() or ch in "_-" for ch in name) and name.isascii()
|
|
|
|
|
|
def test_username_is_stable_for_same_input():
|
|
assert normalize_username("a", "u1") == normalize_username("a", "u1")
|
|
assert normalize_username("a", "u1") != normalize_username("a", "u2")
|
|
|
|
|
|
def test_username_candidates_are_distinct_and_bounded():
|
|
seen = []
|
|
for candidate in username_candidates("x" * 32, "other.example.org"):
|
|
seen.append(candidate)
|
|
assert len(candidate) <= LIMITS.max_username_length
|
|
if len(seen) == 50:
|
|
break
|
|
assert len(set(seen)) == 50
|
|
assert seen[1].endswith("-other")
|
|
|
|
|
|
def test_instants_are_utc_iso_and_clamped():
|
|
assert instant_iso("2025-01-01T01:00:00+01:00") == "2025-01-01T00:00:00+00:00"
|
|
assert instant_iso("2025-01-01").startswith("2025-01-01T00:00:00")
|
|
assert parse_instant(1700000000).year == 2023
|
|
now = datetime.now(timezone.utc)
|
|
future = now + timedelta(days=30)
|
|
assert clamp_instant(future, now) == now
|
|
assert instant_iso("1970-01-01").startswith("2000-01-01")
|
|
|
|
|
|
@pytest.mark.parametrize("raw", ["", None, "nope", True, "2025-13-45"])
|
|
def test_instant_rejects_garbage(raw):
|
|
with pytest.raises(ValueError):
|
|
parse_instant(raw)
|
|
|
|
|
|
def test_fingerprint_ignores_key_order_and_detects_change():
|
|
assert fingerprint({"a": 1, "b": 2}) == fingerprint({"b": 2, "a": 1})
|
|
assert fingerprint({"a": 1}) != fingerprint({"a": 2})
|