DevPlace CI / test (push) Failing after 44m45s
Continuous Delivery could never be turned off once a crop was planted. Add a one-shot per-plot "stop after harvest" flag (POST /game/plot/stop, Devii tool game_plot_stop, game_plots.stop_after_harvest), cleared by the next plant, autoreplant claim or Refactor. Autoreplant now also respects crop locks through the shared _crop_lock predicate. Plan and measurements in happyplanting.md. Also includes the pending community sync, node network and related work in the tree; the full suite passed (4999) with all of it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
135 lines
5.4 KiB
Python
135 lines
5.4 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
from collections import Counter
|
|
from itertools import islice
|
|
|
|
import pytest
|
|
|
|
from devplacepy.database import get_table, init_db
|
|
from devplacepy.database.sync import claim_identity
|
|
from devplacepy.services.sync.batch import BatchRunner
|
|
from devplacepy.services.sync.handles import HandleBuilder
|
|
from devplacepy.services.sync.records import SyncBatchForm
|
|
from devplacepy.services.sync.spec import HANDLES
|
|
from devplacepy.utils import generate_uid
|
|
|
|
ORIGIN = "handles.example.org"
|
|
OWN_PERSON = {
|
|
"abner_page_by_abnercoimbre": "abnercoimbre",
|
|
"jakearchibald_com_by_jaffathecak": "jaffathecak",
|
|
"spritely_institute_via_untrusem": "untrusem",
|
|
"libreboot_org_via_runxiyu": "runxiyu",
|
|
"lumi_codeberg_page_via_stilic": "stilic",
|
|
"decomp_dev_via_FedericoSchonborn": "federicoschonborn",
|
|
"via_rberger_2026-10-15_12_36_38_": "rberger",
|
|
"via_veqq_2026-10-14_23_47_30_cac": "veqq",
|
|
"costa_jacobpalm_dk_via_FedericoS": "federicos",
|
|
"entropicthoughts_com_via_lcapald": "lcapald",
|
|
"github_com_via_gmem": "gmem",
|
|
"try_haxy_net_via_krig": "krig",
|
|
"youtu_be_via_jeffective": "jeffective",
|
|
}
|
|
DOMAIN_ONLY = {
|
|
"costa_jacobpalm_dk": "jacobpalm",
|
|
"techcommunity_microsoft_com": "microsoft",
|
|
"gtoal_com": "gtoal",
|
|
"javidjamae": "javidjamae",
|
|
"john_smith": "john_smith",
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize("raw,expected", sorted((OWN_PERSON | DOMAIN_ONLY).items()))
|
|
def test_the_person_is_found_behind_the_site_and_the_stamp(raw, expected):
|
|
assert HandleBuilder.person(raw) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("raw", sorted(OWN_PERSON))
|
|
def test_no_candidate_keeps_the_site_the_stamp_or_the_relation_word(raw):
|
|
for candidate in islice(HandleBuilder(ORIGIN, raw).candidates(raw), HANDLES.rerolls):
|
|
lowered = candidate.lower()
|
|
assert "_via_" not in lowered and "_by_" not in lowered
|
|
assert "2026" not in lowered and not lowered.startswith("via_")
|
|
|
|
|
|
def test_every_candidate_is_a_valid_handle_across_the_input_domain():
|
|
raws = ["", "a", "ab", "x" * 80, "UPPER_case", "ünï-cödé", "__", "-a-", "via", "by_via", "9999", "john.smith", "a_b_c_d_e_f_g_h"]
|
|
raws += [f"user{index}_by_someone{index}" for index in range(300)]
|
|
for index, raw in enumerate(raws):
|
|
seen = list(islice(HandleBuilder(ORIGIN, f"u{index}").candidates(raw), 60))
|
|
assert seen
|
|
for candidate in seen:
|
|
assert HANDLES.min_length <= len(candidate) <= HANDLES.max_length, (raw, candidate)
|
|
assert HandleBuilder.valid(candidate), (raw, candidate)
|
|
|
|
|
|
def test_the_same_record_always_gets_the_same_candidates():
|
|
first = list(islice(HandleBuilder(ORIGIN, "u1").candidates("someone_via_other"), 40))
|
|
second = list(islice(HandleBuilder(ORIGIN, "u1").candidates("someone_via_other"), 40))
|
|
assert first == second
|
|
|
|
|
|
def test_the_mix_follows_the_spec_weights():
|
|
counts = Counter()
|
|
total = 4000
|
|
for index in range(total):
|
|
first = next(iter(HandleBuilder(ORIGIN, f"id{index}").candidates("kernelhacker")))
|
|
counts["plain" if first == "kernelhacker" else "digits" if any(char.isdigit() for char in first) else "word"] += 1
|
|
plain = counts["plain"] / total * 100
|
|
assert abs(plain - HANDLES.plain_weight) < 5
|
|
assert counts["digits"] / total * 100 > 25
|
|
|
|
|
|
def test_candidates_differ_between_records_with_the_same_name():
|
|
firsts = {next(iter(HandleBuilder(ORIGIN, f"id{index}").candidates("same"))) for index in range(200)}
|
|
assert len(firsts) > 40
|
|
|
|
|
|
@pytest.fixture
|
|
def clean(local_db):
|
|
init_db()
|
|
yield
|
|
for table in ("sync_links", "sync_identities"):
|
|
get_table(table).delete(origin=ORIGIN)
|
|
|
|
|
|
def push(records, humanize):
|
|
return BatchRunner("users", humanize=humanize).run(SyncBatchForm(origin=ORIGIN, records=records))
|
|
|
|
|
|
def stored_name(origin_uid):
|
|
link = get_table("sync_links").find_one(origin=ORIGIN, kind="users", origin_uid=origin_uid)
|
|
return get_table("users").find_one(uid=link["local_uid"])["username"]
|
|
|
|
|
|
def test_a_new_user_gets_a_realistic_handle_and_an_update_keeps_it(clean):
|
|
raw = f"abner_page_by_abnercoimbre{generate_uid()[-4:]}"
|
|
record = {"origin_uid": "h1", "username": raw, "created_at": "2025-01-01T00:00:00Z"}
|
|
push([record], humanize=True)
|
|
name = stored_name("h1")
|
|
assert name != raw and "_by_" not in name and name.startswith("abnercoimbre")
|
|
push([{**record, "bio": "changed bio", "updated_at": "2025-02-01T00:00:00Z"}], humanize=True)
|
|
assert stored_name("h1") == name
|
|
|
|
|
|
def test_without_the_flag_the_original_behaviour_is_untouched(clean):
|
|
raw = f"plain_sender_{generate_uid()[-6:]}"
|
|
push([{"origin_uid": "h2", "username": raw, "created_at": "2025-01-01T00:00:00Z"}], humanize=False)
|
|
assert stored_name("h2") == raw
|
|
|
|
|
|
def test_an_alias_clone_name_is_never_decorated(clean):
|
|
alias = f"mirakou{generate_uid()[-5:]}"
|
|
claim_identity(ORIGIN, "alice", "alice", alias, "{}", False)
|
|
push([{"origin_uid": "alice", "username": alias, "created_at": "2025-01-01T00:00:00Z"}], humanize=True)
|
|
assert stored_name("alice") == alias
|
|
|
|
|
|
def test_two_senders_with_one_person_get_two_distinct_free_handles(clean):
|
|
base = f"samename{generate_uid()[-5:]}"
|
|
push(
|
|
[{"origin_uid": f"s{index}", "username": f"site_{index}_via_{base}", "created_at": "2025-01-01T00:00:00Z"} for index in range(12)],
|
|
humanize=True,
|
|
)
|
|
names = [stored_name(f"s{index}") for index in range(12)]
|
|
assert len({name.lower() for name in names}) == 12
|