The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").
Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.
Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).
One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
117 lines
4.9 KiB
Python
117 lines
4.9 KiB
Python
"""_claim_running is the single-run guard that both the scheduled fire and the manual trigger go
|
|
through (at the top of _job): holding _activity_lock across the check-and-set means exactly one
|
|
caller can hold a job's "running" claim at a time, and it's claimable again once released."""
|
|
|
|
from sqlalchemy import select
|
|
|
|
from app import scheduler
|
|
from app.models import Notification
|
|
|
|
_JOB = "unit_test_claim" # not a real job id — exercises the primitive without touching live jobs
|
|
|
|
|
|
def _release():
|
|
scheduler._record(_JOB, running=False)
|
|
|
|
|
|
def test_claim_is_exclusive_until_released():
|
|
_release()
|
|
# First caller claims it.
|
|
assert scheduler._claim_running(_JOB) is True
|
|
# A second caller is refused while the first still holds it (the TOCTOU the fix closes).
|
|
assert scheduler._claim_running(_JOB) is False
|
|
assert scheduler._claim_running(_JOB) is False
|
|
# _job's finally releases the claim...
|
|
_release()
|
|
# ...and now it can be claimed again.
|
|
assert scheduler._claim_running(_JOB) is True
|
|
_release()
|
|
|
|
|
|
# ---- the result as data --------------------------------------------------------------------------
|
|
|
|
|
|
def _snapshot_job(job_id):
|
|
return next(j for j in scheduler.scheduler_snapshot()["jobs"] if j["id"] == job_id)
|
|
|
|
|
|
def test_a_run_keeps_its_result_as_data_for_the_readers_language(db, make_user):
|
|
"""The dashboard and the manual-run notice showed `_summarize`'s English one-liner ("new=3,
|
|
failed=0", a Python repr for a nested phase). The result rides along as data, so the client names
|
|
it; the one-liner stays as the fallback and the audit text."""
|
|
admin = make_user("sched@example.com", role="admin")
|
|
token = scheduler._manual_actor.set(admin.id)
|
|
try:
|
|
scheduler._job("rss_poll", lambda _db: {"new": 3, "failed": 0, "fallback": 1})
|
|
scheduler._job("enrich", lambda _db: 5) # enrich returns a bare count
|
|
finally:
|
|
scheduler._manual_actor.reset(token)
|
|
|
|
rss = _snapshot_job("rss_poll")
|
|
assert rss["result"] == {"new": 3, "failed": 0, "fallback": 1}
|
|
assert rss["last_result"] == "new=3, failed=0, fallback=1"
|
|
assert _snapshot_job("enrich")["result"] == {"enriched": 5}
|
|
|
|
notices = db.execute(
|
|
select(Notification).where(Notification.user_id == admin.id).order_by(Notification.id)
|
|
).scalars()
|
|
assert [n.data["result"] for n in notices] == [
|
|
{"new": 3, "failed": 0, "fallback": 1},
|
|
{"enriched": 5},
|
|
]
|
|
|
|
|
|
def test_a_failed_run_drops_the_previous_result(db):
|
|
scheduler._job("rss_poll", lambda _db: {"new": 1})
|
|
scheduler._job("rss_poll", lambda _db: (_ for _ in ()).throw(RuntimeError("boom")))
|
|
rss = _snapshot_job("rss_poll")
|
|
assert rss["last_error"] == "boom"
|
|
assert rss["result"] is None
|
|
|
|
|
|
def test_a_job_runs_audit_row_carries_its_result_as_data(db):
|
|
"""The audit log names a run's result in the reader's language, as the inbox does —
|
|
from data, not from the English one-liner (a nested phase there was a Python dict repr)."""
|
|
from app.models import AuditLog, User
|
|
|
|
admin = User(email="auditrun@example.com")
|
|
db.add(admin)
|
|
db.commit()
|
|
scheduler._audit_run(db, None, "people_enrich", "ok", "x", {"bios": {"enriched": 2}})
|
|
scheduler._audit_run(db, admin.id, "rss_poll", "skipped", "sync paused", scheduler._PAUSED)
|
|
scheduler._audit_run(db, admin.id, "rss_poll", "error", "boom", None)
|
|
rows = db.execute(select(AuditLog).order_by(AuditLog.id)).scalars().all()
|
|
assert [r.after for r in rows] == [
|
|
{"status": "ok", "summary": "x", "result": {"bios": {"enriched": 2}}},
|
|
{"status": "skipped", "summary": "sync paused", "result": {"skipped": "sync paused"}},
|
|
{"status": "error", "summary": "boom"},
|
|
]
|
|
|
|
|
|
def test_a_failure_the_job_names_rides_along_as_data(db, make_user):
|
|
"""A failed RSS poll reached the Hungarian audit log as the server's English sentence. A
|
|
failure the job names (`progress.JobFailed`) is stored as data beside that sentence — on the
|
|
dashboard, the manual-run notice and the audit row — so the client says it in the reader's
|
|
language. Any other failure keeps only its own text (the test above)."""
|
|
from app import progress
|
|
from app.models import AuditLog
|
|
|
|
def outage(_db):
|
|
raise progress.JobFailed("rss_all_failed", "RSS poll failed", total=2, cause="transport")
|
|
|
|
admin = make_user("failure@example.com", role="admin")
|
|
token = scheduler._manual_actor.set(admin.id)
|
|
try:
|
|
scheduler._job("rss_poll", outage)
|
|
finally:
|
|
scheduler._manual_actor.reset(token)
|
|
|
|
named = {"failure": "rss_all_failed", "total": 2, "cause": "transport"}
|
|
rss = _snapshot_job("rss_poll")
|
|
assert rss["last_error"] == "RSS poll failed"
|
|
assert rss["result"] == named
|
|
notice = db.execute(select(Notification).where(Notification.user_id == admin.id)).scalar_one()
|
|
assert notice.data["result"] == named
|
|
row = db.execute(select(AuditLog).order_by(AuditLog.id.desc())).scalars().first()
|
|
assert row is not None and row.after["result"] == named
|