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

174 lines
7.2 KiB
Python

# retoor <retoor@molodetz.nl>
from devplacepy.database import get_table, set_setting
from tests.api.sync.conftest import make_account, sync, sync_ok, unique, user_record
def test_requires_authentication(client, origin):
response = client.post(
"/sync/users", json={"origin": origin, "records": [user_record()]}
)
assert response.status_code == 401
def test_member_is_refused(client, origin):
member = make_account()
response = sync(client, member, "users", origin, [user_record()])
assert response.status_code == 403
def test_kill_switch_refuses_when_disabled(client, admin, origin):
set_setting("sync_enabled", "0")
try:
response = sync(client, admin, "users", origin, [user_record()])
assert response.status_code == 503
finally:
set_setting("sync_enabled", "1")
def test_origin_allowlist(client, admin, origin):
set_setting("sync_origins", "trusted.example.org")
try:
assert sync(client, admin, "users", origin, [user_record()]).status_code == 403
allowed = sync(client, admin, "users", "trusted.example.org", [user_record(unique("a"), unique("allow"))])
assert allowed.status_code == 200
finally:
set_setting("sync_origins", "")
def test_invalid_envelope_is_422(client, admin, origin):
assert sync(client, admin, "users", origin, []).status_code == 422
assert sync(client, admin, "users", "bad origin!", [user_record()]).status_code == 422
def test_create_then_replay_is_idempotent(client, admin, origin):
name = unique("bob")
first = sync_ok(client, admin, "users", origin, [user_record("u1", name, bio="hi")])
result = first["results"][0]
assert result["status"] == "created" and result["local_uid"]
again = sync_ok(client, admin, "users", origin, [user_record("u1", name, bio="hi")])
assert again["results"][0]["status"] == "unchanged"
assert again["results"][0]["local_uid"] == result["local_uid"]
assert get_table("users").count(username=name) == 1
def test_update_changes_profile_and_keeps_local_uid(client, admin, origin):
name = unique("upd")
created = sync_ok(client, admin, "users", origin, [user_record("u1", name)])
uid = created["results"][0]["local_uid"]
updated = sync_ok(
client,
admin,
"users",
origin,
[user_record("u1", name, bio="new bio", updated_at="2025-06-01T00:00:00Z")],
)
assert updated["results"][0]["status"] == "updated"
assert get_table("users").find_one(uid=uid)["bio"] == "new bio"
def test_older_update_is_stale(client, admin, origin):
name = unique("stale")
sync_ok(client, admin, "users", origin, [user_record("u1", name, updated_at="2025-06-01T00:00:00Z")])
older = sync_ok(
client, admin, "users", origin,
[user_record("u1", name, bio="old", updated_at="2025-02-01T00:00:00Z")],
)
assert older["results"][0]["status"] == "stale"
def test_username_is_normalized_and_collisions_get_a_suffix(client, admin, origin):
taken = unique("taken")
make_account_row = get_table("users")
sync_ok(client, admin, "users", origin, [user_record("u0", taken)])
other = sync_ok(client, admin, "users", origin, [user_record("u1", taken)])
local = make_account_row.find_one(uid=other["results"][0]["local_uid"])
assert local["username"] != taken and local["username"].startswith(taken[:10])
odd = sync_ok(client, admin, "users", origin, [user_record("u2", "Ünï cøde!")])
created = make_account_row.find_one(uid=odd["results"][0]["local_uid"])
assert created["username"].replace("_", "").replace("-", "").isalnum()
def test_counters_credentials_and_role_are_ignored_and_reported(client, admin, origin):
body = sync_ok(
client, admin, "users", origin,
[user_record("u1", unique("priv"), xp=999, level=50, stars=7, role="Admin", email="x@y.z", password_hash="p")],
)
result = body["results"][0]
assert set(result["ignored"]) == {"xp", "level", "stars", "role", "email", "password_hash"}
row = get_table("users").find_one(uid=result["local_uid"])
assert row["role"] == "Member" and row["xp"] == 0 and row["level"] == 1
assert row["email"].endswith("@sync.invalid")
def test_synced_account_cannot_log_in(client, admin, origin):
body = sync_ok(client, admin, "users", origin, [user_record("u1", unique("nologin"))])
row = get_table("users").find_one(uid=body["results"][0]["local_uid"])
response = client.post(
"/auth/login",
json={"email": row["email"], "password": "anything1"},
headers={"Accept": "application/json"},
)
assert response.status_code == 401
def test_dry_run_writes_nothing(client, admin, origin):
name = unique("dry")
body = sync_ok(client, admin, "users", origin, [user_record("u1", name)], dry_run=True)
assert body["dry_run"] is True and body["results"][0]["status"] == "created"
assert get_table("users").count(username=name) == 0
assert get_table("sync_links").count(origin=origin) == 0
def test_one_bad_record_does_not_fail_the_batch(client, admin, origin):
body = sync_ok(
client, admin, "users", origin,
[user_record("u1", unique("good")), {"origin_uid": "", "username": "x"}, user_record("u3", unique("good"), created_at="nonsense")],
)
assert [r["status"] for r in body["results"]] == ["created", "rejected", "rejected"]
assert body["counts"]["created"] == 1 and body["counts"]["rejected"] == 2
assert all(r["errors"] for r in body["results"][1:])
def test_deleted_flag_deactivates_account(client, admin, origin):
name = unique("gone")
created = sync_ok(client, admin, "users", origin, [user_record("u1", name)])
sync_ok(client, admin, "users", origin, [user_record("u1", name, deleted=True, updated_at="2025-05-01T00:00:00Z")])
assert not get_table("users").find_one(uid=created["results"][0]["local_uid"])["is_active"]
def test_tombstone_for_unknown_record_creates_nothing(client, admin, origin):
body = sync_ok(client, admin, "users", origin, [user_record("u1", unique("ghost"), deleted=True)])
assert body["results"][0]["status"] == "unchanged"
assert body["results"][0]["reason"] == "tombstone_for_unknown"
assert get_table("sync_links").count(origin=origin) == 0
def test_same_origin_uid_in_different_origins_are_distinct(client, admin, origin):
first = sync_ok(client, admin, "users", origin, [user_record("u1", unique("one"))])
second = sync_ok(client, admin, "users", f"b.{origin}", [user_record("u1", unique("two"))])
assert first["results"][0]["local_uid"] != second["results"][0]["local_uid"]
def test_form_encoded_body_with_json_records_is_accepted(client, admin, origin):
import json
name = unique("form")
response = client.post(
"/sync/users",
data={"origin": origin, "records": json.dumps([user_record("u1", name)]), "dry_run": "true"},
headers=admin["headers"],
)
assert response.status_code == 200, response.text
assert response.json()["results"][0]["status"] == "created"
assert get_table("users").count(username=name) == 0
def test_malformed_records_string_is_422(client, admin, origin):
response = client.post(
"/sync/users",
data={"origin": origin, "records": "not json"},
headers=admin["headers"],
)
assert response.status_code in (400, 422)