@@ -0,0 +1,97 @@
|
||||
# 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})
|
||||
Reference in New Issue
Block a user