harness: claper fixture — its own release CLI (rpc register_user / authenticate), proven on 9202
gates / gates (push) Successful in 2s
gates / gates (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -376,6 +376,61 @@ class Django:
|
|||||||
return found is True
|
return found is True
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================================
|
||||||
|
class Claper:
|
||||||
|
"""Claper's OWN release CLI inside its own container: `bin/claper rpc` runs Elixir code in the
|
||||||
|
RUNNING node, against the app's own `Claper.Accounts` context (its changeset, its password hash).
|
||||||
|
Not SQL, not a planted file (R-156). `eval` would boot a second, app-less VM — `rpc` asks the
|
||||||
|
live one. Measured on 9202 2026-09-28 (claper 2.5.1, PostgreSQL 16): register_user → {:ok, id=2};
|
||||||
|
the readback authenticated with the right password and REFUSED a wrong one.
|
||||||
|
|
||||||
|
THE FIXTURE PROVES ITSELF ON EVERY CALL: verify() also asks the same function with a password
|
||||||
|
that was never set and requires False — a readback that has broken into "always found" fails.
|
||||||
|
"""
|
||||||
|
container = "claper"
|
||||||
|
|
||||||
|
def _rpc(self, w, code):
|
||||||
|
# ELIXIR_ERL_OPTIONS=+fnu: the release otherwise warns about latin1 on every call (noise only).
|
||||||
|
out = w.guest(f"docker exec -e ELIXIR_ERL_OPTIONS=+fnu {self.container} /app/bin/claper rpc "
|
||||||
|
f"{json.dumps(code)} 2>&1", timeout=240)
|
||||||
|
return " ".join(out.split())
|
||||||
|
|
||||||
|
def _auth(self, w, email, pw):
|
||||||
|
out = self._rpc(w, f'IO.puts("DRILL_ANSWER=#{{Claper.Accounts.get_user_by_email_and_password({json.dumps(email)}, {json.dumps(pw)}) != nil}}")')
|
||||||
|
m = re.search(r"DRILL_ANSWER=(true|false)", out)
|
||||||
|
return (m.group(1) == "true") if m else None, out[-300:]
|
||||||
|
|
||||||
|
def seed(self, w, sub, say):
|
||||||
|
if not w.wait_app(sub, "/", want=("200", "302"), tries=90):
|
||||||
|
return None
|
||||||
|
email = f"drill-{secrets.token_hex(4)}@gate.invalid"
|
||||||
|
pw = "Drill-" + secrets.token_hex(10)
|
||||||
|
out = self._rpc(w, ('case Claper.Accounts.register_user(%{email: ' + json.dumps(email) + ', password: '
|
||||||
|
+ json.dumps(pw) + '}) do {:ok, u} -> IO.puts("DRILL_SEEDED=#{u.id}"); '
|
||||||
|
'{:error, cs} -> IO.inspect(cs.errors, label: "DRILL_REFUSED") end'))
|
||||||
|
say(f" claper: register_user :: {out[-160:]}")
|
||||||
|
got, detail = self._auth(w, email, pw)
|
||||||
|
if got is not True:
|
||||||
|
say(f" claper: the account does not authenticate in the app's own context :: {detail[:200]}")
|
||||||
|
return None
|
||||||
|
say(f" claper: seeded user {email}")
|
||||||
|
return {"email": email, "pw": pw}
|
||||||
|
|
||||||
|
def verify(self, w, sub, t, say):
|
||||||
|
if not w.wait_app(sub, "/", want=("200", "302"), tries=90):
|
||||||
|
say(" claper: the app never served /")
|
||||||
|
return False
|
||||||
|
wrong, detail = self._auth(w, t["email"], "definitely-" + secrets.token_hex(8))
|
||||||
|
if wrong is not False:
|
||||||
|
say(f" claper: READBACK UNUSABLE — a wrong password did not read as refused ({wrong}) :: {detail[:200]}")
|
||||||
|
return False
|
||||||
|
ok, detail = self._auth(w, t["email"], t["pw"])
|
||||||
|
say(f" claper: readback — the seeded account authenticates={ok}")
|
||||||
|
if ok is not True:
|
||||||
|
say(f" claper: :: {detail[:250]}")
|
||||||
|
return ok is True
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================================
|
# =============================================================================================
|
||||||
class Nextcloud:
|
class Nextcloud:
|
||||||
"""Nextcloud's OWN admin CLI, `occ`, inside its own container: its own code, its own user
|
"""Nextcloud's OWN admin CLI, `occ`, inside its own container: its own code, its own user
|
||||||
@@ -1089,4 +1144,5 @@ FIXTURES = {
|
|||||||
"navidrome": Navidrome(),
|
"navidrome": Navidrome(),
|
||||||
"vaultwarden": Vaultwarden(),
|
"vaultwarden": Vaultwarden(),
|
||||||
"wishlist": Wishlist(),
|
"wishlist": Wishlist(),
|
||||||
|
"claper": Claper(),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user