Relay 0.7.0: een weg naar binnen, en de melding bij elke klik weg
Vier punten van de gebruiker, opgekomen tijdens het beproeven met de testclient. De getimede leerstand is eruit; de wachtlijst is de enige weg naar binnen. Zijn redenering: allebei de wegen vragen iemand die bij de app kan, dus het is dubbelop, en het venster is de zwakste omdat het iedereen toelaat die er toevallig in verbindt. Dat weegt zwaarder nu de relay op een publiek wss-adres kan staan. De oorspronkelijke reden voor de leerstand, dat je je eigen OwnerId nergens kon aflezen, verviel toen de weigerlijst dat id ging tonen. Daarmee verdwijnt ook de bug die hij dezelfde dag meldde: een geleerde eigenaar bleef in de weigerlijst staan terwijl hij al kon schrijven en lezen, want decideOwner haalde hem niet van die lijst af en de knop allow wel. "Refused owners" heet "Waiting list", met de badge Waiting en een teller waar de widget van het tijdvenster stond. Het veld op schijf blijft rejected: hernoemen zou een migratie zijn voor een woord dat niemand ziet. Het adres onderaan zei http:// en dat kan nergens werken, want de relay spreekt WebSocket en nooit HTTP. Nu ws://<host>:3852, met een regel over wss://<domein> zonder poort achter een reverse proxy. Dat is precies de fout die diezelfde dag een ronde kostte bij het koppelen van de testclient. De melding bij elke klik is weg. Die stond in de gewone stroom van de pagina, dus alles eronder schoof omlaag en weer omhoog. Nu gaan de knoppen in de lijsten even op slot tot de ronde de nieuwe stand heeft; foutmeldingen blijven wel staan, want die zeggen iets wat je nergens anders ziet. STATE_VERSION blijft 1 en een owners.json van 0.6.0 leest door: learning en learningUntil worden gelezen, genegeerd en niet teruggeschreven. Een verhoging zou store.js de allowlist van een werkende installatie opzij laten schuiven. Twee toetsen bewaken dat de leerstand niet terugsluipt: een onbekende eigenaar wordt geweigerd ook met learning: true in het bestand, en set-learning is een onbekende actie. Beide mutatie-getest. De compose staat op 0.7.0 zonder digest, zodat het hard faalt tot de image bestaat. Bouwen, duwen en pinnen ligt bij de gebruiker. Suite: 424 goed, 0 fout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+10
-110
@@ -1,4 +1,4 @@
|
||||
"""Toetst de agent van Evolu Relay: de labels en het tijdvenster.
|
||||
"""Toetst de agent van Evolu Relay: de labels.
|
||||
|
||||
Waarom deze test bestaat. De agent van deze app beslist niets over toegang, en dat
|
||||
is precies waarom hij tot 30-08-2026 ongetoetst kon blijven: hij las een bestand en
|
||||
@@ -9,9 +9,9 @@ eigen logica hebben:
|
||||
- **hij schrijft zelf een bestand**, `labels.json`, en is daar de enige schrijver
|
||||
van. Dat is de eerste plek waar de agent staat bijhoudt in plaats van doorgeeft,
|
||||
en labelen is lezen-wijzigen-schrijven, dus er zit een slot om;
|
||||
- **hij rekent het tijdvenster uit** dat de pagina toont. Een verlopen venster moet
|
||||
hij als dicht rapporteren, ook al staat er in owners.json nog dat de leerstand
|
||||
aan is: het relay-proces ruimt dat op zijn eigen moment op.
|
||||
Tot 0.6.0 rekende hij daarnaast het tijdvenster voor nieuwe eigenaars uit. Dat
|
||||
venster is er in 0.7.0 uit: er is nog één weg naar binnen, en dat is de wachtlijst
|
||||
met handmatig toelaten. De toetsen daarover zijn met de functie mee verdwenen.
|
||||
|
||||
Wat deze test NIET dekt: de HTTP-laag. De handlers zitten in een
|
||||
BaseHTTPRequestHandler en die is zonder socket niet aan te roepen; wat eronder
|
||||
@@ -105,66 +105,6 @@ def test_template_is_invulbaar_zonder_schade(u):
|
||||
"een envsubst-stap zou die invullen en de Python-code slopen")
|
||||
|
||||
|
||||
# ── Het tijdvenster ─────────────────────────────────────────────────────────
|
||||
|
||||
def test_venster_dat_nog_loopt(agent, u):
|
||||
feiten = agent.learning_facts({"learning": True, "learningUntil": iso(90)})
|
||||
u.check("een lopend venster leest als open", feiten["learning"] is True)
|
||||
u.check("met een resterende tijd", isinstance(feiten["learningSecondsLeft"], int))
|
||||
u.check("die ergens rond de negentig seconden ligt",
|
||||
85 <= feiten["learningSecondsLeft"] <= 90,
|
||||
"kreeg %r" % feiten["learningSecondsLeft"])
|
||||
|
||||
|
||||
def test_venster_dat_verlopen_is(agent, u):
|
||||
"""De toets die er het meest toe doet aan deze kant.
|
||||
|
||||
In het bestand staat `learning: true` en een tijdstip dat voorbij is. Het
|
||||
relay-proces ruimt dat op in zijn lus van twee seconden, dus er is een moment
|
||||
waarop het bestand nog "aan" zegt terwijl de deur dicht is. De pagina hoort dan
|
||||
"closed" te tonen en geen open deur.
|
||||
"""
|
||||
feiten = agent.learning_facts({"learning": True, "learningUntil": iso(-30)})
|
||||
u.check("een verlopen venster leest als dicht", feiten["learning"] is False)
|
||||
u.check("met nul seconden over", feiten["learningSecondsLeft"] == 0)
|
||||
u.check("en zonder tijdstip", feiten["learningUntil"] is None)
|
||||
|
||||
|
||||
def test_venster_zonder_tijdslot(agent, u):
|
||||
feiten = agent.learning_facts({"learning": True, "learningUntil": None})
|
||||
u.check("open zonder tijdslot blijft open", feiten["learning"] is True)
|
||||
u.check("en heeft geen teller", feiten["learningSecondsLeft"] is None)
|
||||
|
||||
|
||||
def test_onleesbaar_tijdslot(agent, u):
|
||||
"""Onleesbaar hoort niet als "voor altijd open" te lezen, maar wel als open.
|
||||
|
||||
Anders dan bij `policy.js`, en dat is met opzet: dit is de weergave en niet de
|
||||
beslissing. Het relay-proces beslist, en dáár is een onleesbaar tijdstip dicht.
|
||||
De pagina die iets anders toont dan het bestand zegt zou verwarrender zijn dan
|
||||
een pagina die de leerstand toont die er staat.
|
||||
"""
|
||||
for onzin in ["morgen", "", 42, {}, []]:
|
||||
feiten = agent.learning_facts({"learning": True, "learningUntil": onzin})
|
||||
u.check("een onleesbaar tijdslot valt terug op de leerstand zelf: %r" % (onzin,),
|
||||
feiten["learning"] is True and feiten["learningSecondsLeft"] is None)
|
||||
|
||||
|
||||
def test_leerstand_onbekend(agent, u):
|
||||
"""Onbekend is geen synonym voor uit.
|
||||
|
||||
Ontbreekt owners.json, dan is er nog nooit iets geschreven en weet niemand wat
|
||||
de leerstand is. Uit is een keuze, onbekend is een reden om te kijken, en de
|
||||
pagina hoort dat verschil te tonen.
|
||||
"""
|
||||
feiten = agent.learning_facts({})
|
||||
u.check("een ontbrekende leerstand blijft onbekend", feiten["learning"] is None)
|
||||
u.check("en heeft geen teller", feiten["learningSecondsLeft"] is None)
|
||||
|
||||
uit = agent.learning_facts({"learning": False, "learningUntil": None})
|
||||
u.check("en uit blijft uit", uit["learning"] is False)
|
||||
|
||||
|
||||
# ── De labels ───────────────────────────────────────────────────────────────
|
||||
|
||||
def test_label_zetten_en_weghalen(agent, u):
|
||||
@@ -253,43 +193,7 @@ def test_onleesbare_labels_zijn_geen_ramp(agent, u, state_dir):
|
||||
u.check("een label van alleen witruimte valt weg", "owner-f" not in labels)
|
||||
|
||||
|
||||
# ── De opdracht met seconden ────────────────────────────────────────────────
|
||||
|
||||
def test_set_learning_met_seconden(agent, u):
|
||||
goed, problem = agent.valid_command({"action": "set-learning", "value": True, "seconds": 120})
|
||||
u.check("openzetten met seconden mag", problem is None)
|
||||
u.check("en de seconden gaan mee naar de relay", goed.get("seconds") == 120)
|
||||
|
||||
zonder, problem = agent.valid_command({"action": "set-learning", "value": True})
|
||||
u.check("openzetten zonder seconden mag ook", problem is None)
|
||||
u.check("en dan staat er geen seconds in de opdracht", "seconds" not in zonder)
|
||||
|
||||
for seconden, waarom in [
|
||||
(0, "nul"),
|
||||
(-60, "negatief"),
|
||||
(1.5, "geen heel getal"),
|
||||
("120", "tekst"),
|
||||
# isinstance(True, int) is in Python waar, dus zonder de uitsluiting in de
|
||||
# agent zou een boolean hier als aantal seconden doorglippen.
|
||||
(True, "een boolean"),
|
||||
(agent.MAX_LEARNING_SECONDS + 1, "boven de bovengrens"),
|
||||
]:
|
||||
_, problem = agent.valid_command(
|
||||
{"action": "set-learning", "value": True, "seconds": seconden})
|
||||
u.check("een onbruikbaar aantal seconden wordt geweigerd: %s" % waarom,
|
||||
problem is not None)
|
||||
|
||||
_, problem = agent.valid_command(
|
||||
{"action": "set-learning", "value": True, "seconds": agent.MAX_LEARNING_SECONDS})
|
||||
u.check("de bovengrens zelf mag wel", problem is None)
|
||||
|
||||
# Seconden bij dichtzetten is onzin en hoort dus geweigerd te worden in plaats
|
||||
# van stil genegeerd: stil negeren zou een sluitopdracht met een tikfout laten
|
||||
# slagen terwijl er iets anders gebeurt dan er staat.
|
||||
_, problem = agent.valid_command(
|
||||
{"action": "set-learning", "value": False, "seconds": 120})
|
||||
u.check("seconden bij dichtzetten wordt geweigerd", problem is not None)
|
||||
|
||||
# ── De status ───────────────────────────────────────────────────────────────
|
||||
|
||||
def test_status_hangt_labels_aan_de_regels(agent, u, state_dir):
|
||||
"""De pagina krijgt het label bij de eigenaar, en het bestand van de relay
|
||||
@@ -320,9 +224,11 @@ def test_status_hangt_labels_aan_de_regels(agent, u, state_dir):
|
||||
u.check("en krijgt zijn label", owners["rejected"][0]["label"] == "Onbekend apparaat")
|
||||
u.check("het aantal pogingen gaat mee", owners["rejected"][0]["attempts"] == 3)
|
||||
|
||||
u.check("de leerstand komt uit learning_facts", owners["learning"] is False)
|
||||
u.check("en de statusvelden voor de teller staan erin",
|
||||
"learningSecondsLeft" in owners and "learningUntil" in owners)
|
||||
# De leerstand is er in 0.7.0 uit. Deze toets houdt vast dat de agent hem ook
|
||||
# niet meer dóórgeeft: een pagina die het veld nog zou lezen, zou anders een
|
||||
# knop kunnen tonen voor iets dat niet meer bestaat.
|
||||
u.check("de leerstand staat niet meer in de status",
|
||||
"learning" not in owners and "learningSecondsLeft" not in owners)
|
||||
|
||||
with open(os.path.join(state_dir, "owners.json"), "r", encoding="utf-8") as f:
|
||||
op_schijf = json.load(f)
|
||||
@@ -338,18 +244,12 @@ def main():
|
||||
with tempfile.TemporaryDirectory() as state_dir:
|
||||
agent = load_agent(state_dir)
|
||||
|
||||
test_venster_dat_nog_loopt(agent, u)
|
||||
test_venster_dat_verlopen_is(agent, u)
|
||||
test_venster_zonder_tijdslot(agent, u)
|
||||
test_onleesbaar_tijdslot(agent, u)
|
||||
test_leerstand_onbekend(agent, u)
|
||||
|
||||
test_label_zetten_en_weghalen(agent, u)
|
||||
test_label_blijft_op_schijf(agent, u, state_dir)
|
||||
test_label_validatie(agent, u)
|
||||
test_onleesbare_labels_zijn_geen_ramp(agent, u, state_dir)
|
||||
|
||||
test_set_learning_met_seconden(agent, u)
|
||||
test_status_hangt_labels_aan_de_regels(agent, u, state_dir)
|
||||
|
||||
# Als laatste: deze vult het labelbestand tot het plafond en laat dus geen
|
||||
|
||||
Reference in New Issue
Block a user