The anti-slop run (design-dev, 2026-09-28; operator: "start the fix slices") found raw ISO stamps with microseconds and offsets, the poster's IP address, and HH:MM in board rows and <time> tooltips. Operator convention 2026-09-24: a clock the operator reads is 24-hour local time as four digits, no colon. - `clock` filter: ISO (any precision, any offset), epoch, or the board's `YYYY-MM-DD HH:MM` -> `28 Sep 0848` local, year only when not this year's. Never raises; what it cannot read is shown as given. No regex (INV-3). - `byline` filter: a handle is shown, an IP address is not. Stored `by` and `answered_by` are unchanged (u2 still records the client host). - Applied to the marks' answer and memo lines, the inline ask's state tag (so the embed chrome inherits it) and the link board's row time, each in a <time> whose datetime= carries the stored value exactly. - `date_stamp` (the created/updated tooltips) renders `YYYY-MM-DD HHMM`. Folded from the heid bug-hunt (panel 4/4, thread 01M3MGPFKWBX0SJK5HFE0P3AFM): clock converts a number inside its guard (an int past float range raised, Q1); a date or ISO week renders no invented 0000 (Q8); byline also hides addr:port, [v6]:port, addr/prefix and addresses behind invisible characters (Q7); the board row's author is bylined (Q5). Refuted: Q3 (default Jinja Undefined has length 0; the test stays as a StrictUndefined guard). Accepted with reasons: Q4, Q6. Contract: docs/contracts/as_antislop.contract.md S1. Falsifiers: antislop.toml 15/15 proved (S1); all 12 tables 295/295 proved on this tree.
177 lines
7.2 KiB
Python
177 lines
7.2 KiB
Python
"""The anti-slop fix slices (docs/contracts/as_antislop.contract.md).
|
|
|
|
S1, the house clock: a clock time the operator reads is local 24-hour time as
|
|
four digits with no colon (0848). Raw ISO stamps, HH:MM, microseconds, offsets
|
|
and a poster's IP address never reach visible text. The exact stored value
|
|
stays available in each <time>'s `datetime`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from booth.marks import answer_pick, declare_pick, marks_for, write_note
|
|
|
|
PACIFIC = "America/Los_Angeles"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def pacific():
|
|
"""Pin the zone: `clock` renders local time, and this box's local time is
|
|
the operator's. A test that inherited the runner's zone would pass or fail by
|
|
where it ran."""
|
|
old = os.environ.get("TZ")
|
|
os.environ["TZ"] = PACIFIC
|
|
time.tzset()
|
|
yield
|
|
if old is None:
|
|
os.environ.pop("TZ", None)
|
|
else:
|
|
os.environ["TZ"] = old
|
|
time.tzset()
|
|
|
|
|
|
def _now():
|
|
return time.mktime((2026, 9, 28, 12, 0, 0, 0, 0, -1))
|
|
|
|
|
|
@pytest.fixture
|
|
def client(tmp_path):
|
|
from fastapi.testclient import TestClient
|
|
|
|
from booth.app import create_app
|
|
return TestClient(create_app(tmp_path, ttl_hours=24, start_sweeper=False)), tmp_path
|
|
|
|
|
|
def _text(fragment: str) -> str:
|
|
return re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", fragment)).strip()
|
|
|
|
|
|
HHMM = re.compile(r"\b\d{1,2}:\d{2}\b")
|
|
ISO = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}")
|
|
IP = re.compile(r"\b\d{1,3}(?:\.\d{1,3}){3}\b|::1\b")
|
|
HOUSE = re.compile(r"\b\d{1,2} [A-Z][a-z]{2}(?: \d{4})? \d{4}\b")
|
|
|
|
|
|
# ---- the filters ---------------------------------------------------------------
|
|
|
|
def test_clock_forms():
|
|
from booth.app import clock
|
|
now = _now()
|
|
assert clock("2026-09-28T08:48:20.478024-07:00", now=now) == "28 Sep 0848"
|
|
assert clock("2026-09-28T15:48:20+00:00", now=now) == "28 Sep 0848" # converted to local
|
|
assert clock("2026-09-28T08:48:20", now=now) == "28 Sep 0848" # naive = local
|
|
assert clock("2026-09-06 23:35", now=now) == "6 Sep 2335" # the board's rows
|
|
assert clock("2025-09-06 23:35", now=now) == "6 Sep 2025 2335" # another year says so
|
|
assert clock(time.mktime((2026, 9, 28, 8, 48, 0, 0, 0, -1)), now=now) == "28 Sep 0848"
|
|
assert clock("", now=now) == "" and clock(None, now=now) == ""
|
|
assert clock("2026-09-28", now=now) == "28 Sep" # a date has no clock: no 0000
|
|
assert clock("2026-W39-1", now=now) == "21 Sep" # nor does an ISO week
|
|
|
|
|
|
def test_clock_never_raises():
|
|
"""The Desk and the board render many rows in ONE response: one bad stamp
|
|
must cost its own text, never the page. What cannot be read is shown as
|
|
given, never guessed."""
|
|
from booth.app import clock
|
|
for bad in ("not a time", "2026-13-45T99:99", "99999-01-01T00:00:00", 1e20, 10 ** 400, -(10 ** 400),
|
|
float("nan"), float("inf"), True, [1]):
|
|
assert isinstance(clock(bad, now=_now()), str)
|
|
assert clock("not a time", now=_now()) == "not a time"
|
|
assert clock("2026-13-45T99:99", now=_now()) == "2026-13-45T99:99"
|
|
|
|
|
|
def test_byline_hides_addresses():
|
|
from booth.app import byline
|
|
for addr in ("127.0.0.1", "10.100.10.5", "::1", "2001:db8::1", "10.100.10.5:443", "10.100.10.5/32",
|
|
"[2001:db8::1]:443", "\u200b10.100.10.5", " 127.0.0.1 "):
|
|
assert byline(addr) == "", addr
|
|
assert byline("design-dev") == "design-dev" and byline("host:8080") == "host:8080"
|
|
assert byline("") == "" and byline(None) == ""
|
|
|
|
|
|
def test_date_stamp_is_house_form():
|
|
from booth.app import date_stamp
|
|
assert re.fullmatch(r"\d{4}-\d{2}-\d{2} \d{4}", date_stamp(_now())), date_stamp(_now())
|
|
assert date_stamp(_now()) == "2026-09-28 1200"
|
|
|
|
|
|
# ---- where they apply ------------------------------------------------------------
|
|
|
|
def _answered_booth(data):
|
|
b = data / "b"
|
|
b.mkdir()
|
|
declare_pick(b, "p1", {"prompt": "Which?", "options": ["North", "South"]})
|
|
answer_pick(b, "p1", marks_for(b)[0].options[0]["id"], who="127.0.0.1")
|
|
write_note(b, None, "about the booth", who="10.100.10.5")
|
|
return b
|
|
|
|
|
|
def _whens(html):
|
|
return [_text(w) for w in re.findall(r'<span class="mark-when">(.*?)</span>', html, flags=re.S)]
|
|
|
|
|
|
@pytest.mark.parametrize("path", ["/b/b/marks", "/b/b/"])
|
|
def test_rendered_marks_use_the_house_clock(client, path):
|
|
c, data = client
|
|
_answered_booth(data)
|
|
html = c.get(path).text
|
|
whens = _whens(html)
|
|
assert whens, f"{path} renders no mark line at all (positive control)"
|
|
for w in whens:
|
|
assert HOUSE.search(w), w
|
|
assert not (HHMM.search(w) or ISO.search(w) or IP.search(w)), w
|
|
visible = _text(re.sub(r"<(script|style)\b.*?</\1>", " ", html, flags=re.S))
|
|
assert "127.0.0.1" not in html and "10.100.10.5" not in html, "no address anywhere, attributes included"
|
|
stored = {m.created for m in marks_for(data / "b")} | {m.answer["answered_at"] for m in marks_for(data / "b") if m.answer}
|
|
carried = set(re.findall(r'<time datetime="([^"]+)"', html))
|
|
assert stored & carried, "the exact stored stamp rides in datetime="
|
|
|
|
|
|
def test_embed_fragment_uses_the_house_clock(client):
|
|
c, data = client
|
|
_answered_booth(data)
|
|
(m,) = [m for m in c.get("/b/b/embed.json").json()["marks"] if m["id"] == "p1"]
|
|
# The SUBMIT block's tag carries the stamp (the per-question tag says only
|
|
# "✓ answered"); the embed places it from `submit`.
|
|
tag = _text(re.search(r'<span class="bk-ask-tag">(.*?)</span>', m["submit"], flags=re.S).group(1))
|
|
assert tag.startswith("✓ answered"), tag
|
|
assert HOUSE.search(tag) and not (ISO.search(tag) or HHMM.search(tag)), tag
|
|
|
|
|
|
def test_board_rows_use_the_house_clock(client):
|
|
from booth.links import LINKS_FILE
|
|
c, data = client
|
|
board = data / "links"
|
|
board.mkdir()
|
|
(board / LINKS_FILE).write_text("- [Booth](http://x/) <sub>· infra-ops · 2026-09-06 23:35</sub>\n"
|
|
"- [Other](http://y/) <sub>· 10.0.0.5 · 2026-09-06 23:36</sub>\n")
|
|
html = c.get("/b/links/").text
|
|
whens = [_text(w) for w in re.findall(r'<span class="board-when">(.*?)</span>', html, flags=re.S)]
|
|
assert "10.0.0.5" not in html, "a board row's author is bylined like every other"
|
|
assert len(whens) == 2 and all(HOUSE.search(w) and not HHMM.search(w) for w in whens), whens
|
|
assert "6 Sep 2335" in whens, whens
|
|
assert 'datetime="2026-09-06 23:35"' in html
|
|
|
|
|
|
def test_a_malformed_answer_costs_its_line_not_the_page(client):
|
|
"""One stored answer missing `unanswered` must not 500 the booth or marks
|
|
page (heid bug-hunt Q3). REFUTED as a live bug: the Booth's Jinja uses the
|
|
default `Undefined`, whose `|length` is 0. Kept as a guard: switching the
|
|
environment to StrictUndefined would turn this red."""
|
|
import json as _json
|
|
c, data = client
|
|
b = data / "b"
|
|
b.mkdir()
|
|
(b / "a.png").write_bytes(b"\x89PNG\r\n\x1a\n")
|
|
declare_pick(b, "p1", {"prompt": "Which?", "options": ["North", "South"]})
|
|
doc = _json.loads((b / ".marks.json").read_text())
|
|
doc["marks"][0]["answer"] = {"answers": []}
|
|
(b / ".marks.json").write_text(_json.dumps(doc))
|
|
for url in ("/b/b/", "/b/b/marks"):
|
|
assert c.get(url).status_code == 200, url
|