167 lines
5.1 KiB
Python
167 lines
5.1 KiB
Python
|
|
# retoor <retoor@molodetz.nl>
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from devplacepy.database import CONSENTS_TABLE, get_setting, get_table, set_setting
|
||
|
|
from devplacepy.services.acceptance.agreements import agreement_for
|
||
|
|
from devplacepy.services.acceptance.pending import (
|
||
|
|
LIVE_ACCOUNT_CLAUSES,
|
||
|
|
current_version,
|
||
|
|
live_account_clauses,
|
||
|
|
pending,
|
||
|
|
)
|
||
|
|
from devplacepy.utils import generate_uid
|
||
|
|
|
||
|
|
TERMS = agreement_for("terms")
|
||
|
|
PRIVACY = agreement_for("privacy")
|
||
|
|
AI = agreement_for("ai_third_party")
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def account(local_db):
|
||
|
|
uid = generate_uid()
|
||
|
|
get_table("users").insert(
|
||
|
|
{
|
||
|
|
"uid": uid,
|
||
|
|
"username": f"pendingprobe_{uid[:8]}",
|
||
|
|
"email": f"pendingprobe_{uid[:8]}@example.com",
|
||
|
|
"password_hash": "x",
|
||
|
|
"role": "Member",
|
||
|
|
"terms_version": "",
|
||
|
|
"terms_accepted_at": "",
|
||
|
|
"deletion_requested_at": "",
|
||
|
|
"created_at": "2020-01-01T00:00:00",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
yield uid
|
||
|
|
get_table("users").delete(uid=uid)
|
||
|
|
get_table(CONSENTS_TABLE).delete(owner_id=uid)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def versions():
|
||
|
|
before = {
|
||
|
|
"terms_version": get_setting("terms_version", "1"),
|
||
|
|
"privacy_version": get_setting("privacy_version", "1"),
|
||
|
|
}
|
||
|
|
yield
|
||
|
|
for key, value in before.items():
|
||
|
|
set_setting(key, value)
|
||
|
|
|
||
|
|
|
||
|
|
def write_consent(uid, kind, state, version):
|
||
|
|
get_table(CONSENTS_TABLE).insert(
|
||
|
|
{
|
||
|
|
"uid": generate_uid(),
|
||
|
|
"owner_kind": "user",
|
||
|
|
"owner_id": uid,
|
||
|
|
"kind": kind,
|
||
|
|
"version": version,
|
||
|
|
"state": state,
|
||
|
|
"granted_at": "2021-01-01T00:00:00" if state == "granted" else "",
|
||
|
|
"withdrawn_at": "" if state == "granted" else "2021-01-01T00:00:00",
|
||
|
|
"created_at": "2021-01-01T00:00:00",
|
||
|
|
"deleted_at": None,
|
||
|
|
"deleted_by": None,
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def is_pending(agreement, uid):
|
||
|
|
return uid in {row["uid"] for row in pending(agreement, 5000)}
|
||
|
|
|
||
|
|
|
||
|
|
def test_an_account_that_never_accepted_is_pending(account):
|
||
|
|
assert is_pending(TERMS, account)
|
||
|
|
assert is_pending(AI, account)
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_null_gate_column_is_pending_not_invisible(account, versions):
|
||
|
|
set_setting("terms_version", "1")
|
||
|
|
get_table("users").update({"uid": account, "terms_version": None}, ["uid"])
|
||
|
|
assert is_pending(TERMS, account)
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_satisfied_gate_column_is_not_pending(account, versions):
|
||
|
|
set_setting("terms_version", "7")
|
||
|
|
get_table("users").update({"uid": account, "terms_version": "7"}, ["uid"])
|
||
|
|
assert not is_pending(TERMS, account)
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_stale_gate_column_is_pending_again(account, versions):
|
||
|
|
set_setting("terms_version", "7")
|
||
|
|
get_table("users").update({"uid": account, "terms_version": "7"}, ["uid"])
|
||
|
|
set_setting("terms_version", "8")
|
||
|
|
assert is_pending(TERMS, account)
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_withdrawal_removes_the_account_permanently(account, versions):
|
||
|
|
set_setting("terms_version", "1")
|
||
|
|
write_consent(account, "terms", "withdrawn", "1")
|
||
|
|
assert not is_pending(TERMS, account)
|
||
|
|
set_setting("terms_version", "2")
|
||
|
|
assert not is_pending(TERMS, account)
|
||
|
|
get_table("users").update({"uid": account, "terms_version": None}, ["uid"])
|
||
|
|
assert not is_pending(TERMS, account)
|
||
|
|
|
||
|
|
|
||
|
|
def test_an_unversioned_agreement_is_satisfied_by_a_granted_row(account):
|
||
|
|
write_consent(account, "ai_third_party", "granted", "1")
|
||
|
|
assert not is_pending(AI, account)
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_versioned_ledger_agreement_tracks_the_policy_version(account, versions):
|
||
|
|
set_setting("privacy_version", "3")
|
||
|
|
write_consent(account, "privacy", "granted", "3")
|
||
|
|
assert not is_pending(PRIVACY, account)
|
||
|
|
set_setting("privacy_version", "4")
|
||
|
|
assert is_pending(PRIVACY, account)
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_legacy_consent_row_without_a_version_is_stale(account, versions):
|
||
|
|
set_setting("privacy_version", "1")
|
||
|
|
write_consent(account, "privacy", "granted", None)
|
||
|
|
assert is_pending(PRIVACY, account)
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_deleted_account_is_never_pending(account):
|
||
|
|
get_table("users").update(
|
||
|
|
{"uid": account, "deletion_requested_at": "2024-01-01T00:00:00"}, ["uid"]
|
||
|
|
)
|
||
|
|
assert not is_pending(TERMS, account)
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_deactivated_account_is_never_pending(account):
|
||
|
|
get_table("users").update({"uid": account, "is_active": False}, ["uid"])
|
||
|
|
assert not is_pending(TERMS, account)
|
||
|
|
|
||
|
|
|
||
|
|
def test_the_batch_size_bounds_the_sweep_and_the_order_is_stable(account):
|
||
|
|
first = [row["uid"] for row in pending(TERMS, 1)]
|
||
|
|
second = [row["uid"] for row in pending(TERMS, 1)]
|
||
|
|
assert len(first) <= 1
|
||
|
|
assert first == second
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_zero_batch_size_still_returns_at_most_one_row(account):
|
||
|
|
assert len(pending(TERMS, 0)) <= 1
|
||
|
|
|
||
|
|
|
||
|
|
def test_the_live_account_clauses_only_name_existing_columns(local_db):
|
||
|
|
users = get_table("users")
|
||
|
|
expected = [
|
||
|
|
clause
|
||
|
|
for column, clause in LIVE_ACCOUNT_CLAUSES
|
||
|
|
if users.has_column(column)
|
||
|
|
]
|
||
|
|
assert live_account_clauses() == expected
|
||
|
|
|
||
|
|
|
||
|
|
def test_an_empty_version_setting_falls_back_to_one(versions):
|
||
|
|
set_setting("terms_version", "")
|
||
|
|
assert current_version(TERMS) == "1"
|
||
|
|
|
||
|
|
|
||
|
|
def test_an_unversioned_agreement_reports_version_one():
|
||
|
|
assert current_version(AI) == "1"
|