Files
devplacepy/tests/unit/services/sync/normalize.py
T
retoor 6f80a820da
DevPlace CI / test (push) Failing after 3m13s
Zo doen wijdat in oost
2026-10-16 07:20:44 +02:00

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})