Files
brain-of-reese/tests/unit/test_date_editor.py
T
ducoterra ee3efb28c9
Build and Push Containers / build-and-push-app (push) Successful in 4m35s
Build and Push Containers / build-and-push-db (push) Successful in 14s
phase: 106_document_dates
Everything is verified green. Final report:

**Phase 106 — Document dates (final verification pass; all 10 tasks already complete)**

- Verified all phase artifacts: alembic `0020` (dev DB at `0020`), `doc_dates.py`, git `file_commit_dates`, importer `doc_dates_by_root`/`dates_updated`, both entry-point wirings, date APIs + tree `created_at`/`updated_at`, LLM surfaces (prompt block, `read` line 2, appended `ls` field), `apply_recency_boost` in `retrieve()`, UI columns/badge, admin editor, mock-LLM regex — all present and correct; no defects found, no fixes needed.
- `uv run pytest --cov=app --cov-report=term-missing` → **2299 passed, TOTAL 99%** (>90% ✓)
- `uv run pytest tests/e2e/test_document_dates.py -v --no-cov` → **6/6 passed** in isolation (DB up)
- 12 regression E2E suites (retrieval_quality, whole_document_context, agent_document_tools, ls_tree_drilldown, read_truncation_cap, kb_tree, kb_tree_nav, document_viewer, edit_summaries, import_documents, sync_button, hidden_folders_toggle, smoke) → **all green in isolation**
- `uv run ruff check .` → clean; `uv run pyright` → **0 errors, 0 warnings**

**Completion criteria:** 1) non-null `created_at` + 0020 upgrade/downgrade on dev DB ✓ (real-Alembic integration tests) 2) sync refresh/older/manual-persists/content-reset/no sources_meta bump ✓ 3) zip/tar mtime + future→today ✓ 4) LLM date surfaces + cross-check ✓ 5) UI Created/Updated/badge positions ✓ 6) admin editor set+revert round-trip ✓ 7) old-correct-beats-new-similar (defaults & boost-off) + near-tie + `BOR_RECENCY_BOOST=0` byte-identical ✓ 8) full gate ✓ 9) commit/phase-move — left to harness per instructions.

- **Notable:** recency default tuned 0.001 → **0.0007** (task 07 step 5 explicitly permits; measured margins recorded in `test_recency_boost.py` docstring).
- **Next pending phase:** none — `todo/` holds only this phase.
2026-09-13 19:28:05 -04:00

471 lines
22 KiB
Python

"""Unit: the admin-only date editor in the viewer (phase 106, task 09,
D7).
The owner asked for the creation date to be editable "so users can
correct for errors" — admin-only, in the shared viewer core (modal +
``/document.html`` through the ONE ``renderDocument`` — no per-surface
copy), on the phase-57 ``wireSummaryEdit`` idiom (the same
``docAdminReady()`` gate on the module-cached whoami promise — no
second request per page; the public badge row stays byte-for-byte the
task-08 shape for non-admins). The task-05 endpoint
(``PATCH /api/documents/date`` — set + flag manual; null = the D7
CLEAR, the flag drops) is the single source this file cross-checks
against.
The browser behavior itself is E2E-gated by the phase's dedicated
suite (``tests/e2e/test_document_dates.py``, task 10); like the other
frontend-adjacent unit files (the ``test_summary_edit_ui.py`` /
``test_sources_dates.py`` house pattern), this module pins the
source-level contract a silent regression would break:
* the ``docAdminReady()`` gate — ``wireDateEdit`` is called ONLY in
the gate's ``if (admin)`` branch (one call site, after the badge
row is built for everyone — the anonymous DOM is never touched);
* the ``Edit date`` affordance — a real ``type="button"`` with the
``aria-label`` ``Edit creation date: <source>/<path>``
(setAttribute, never innerHTML), inserted AFTER the task-08 Created
badge;
* the editor construction — the button swaps in-place for a box with
a native ``<input type="date">`` (``aria-label="Document
creation date"``, prefilled with the stored date's UTC date part
via ``.value`` — never innerHTML), Save / Cancel text buttons, the
muted "Revert to sync" clear affordance (the phase-57 "clear =
explicit" contrast), a ``role="status"`` live line and a
``role="alert"`` error line;
* the exact PATCH — ``/api/documents/date`` with method PATCH and the
``{source, path, date}`` body; the endpoint string appears exactly
ONCE in the JS (the single-source cross-file check against
``app/api/docs.py``'s route); Revert sends ``date: null``;
* the response-driven re-render — the badge is re-rendered from the
RESPONSE's ``created_at`` (``res.created_at`` — never the
input's optimistic value);
* the §7.4 never-stale lifecycle — the controls disable IMMEDIATELY
on Save/Revert (one PATCH at a time), an EMPTY input disables Save
(the explicit Revert is the only clear path — no accidental
wipes), a failure (non-2xx or network) lands the server detail
(or the canned retry copy) in the ``role="alert"`` line, reverts
the input to the stored date, keeps the editor open, and
re-enables in the ``finally``; the success confirmation lands
AFTER the badge update (the phase-89 last-announce order);
* styles.css — the eight editor classes next to the summary-editor
family, the phase-106 D7 provenance comment with the recorded WCAG
pairs, the ``[hidden]`` override, the ``cursor: wait`` disabled
idiom, the global 3px ``:focus-visible`` ring (no per-control
rule), no CDN.
"""
from __future__ import annotations
import re
from pathlib import Path
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
DOCUMENT_JS = FRONTEND / "assets" / "document.js"
STYLES_CSS = FRONTEND / "assets" / "styles.css"
DOCS_PY = Path(__file__).resolve().parents[2] / "app" / "api" / "docs.py"
def _js() -> str:
return DOCUMENT_JS.read_text(encoding="utf-8")
def _css() -> str:
return STYLES_CSS.read_text(encoding="utf-8")
def _fn(js: str, name: str) -> str:
"""The source of a (possibly async, possibly nested) function via
balanced-brace counting. The brace count starts AFTER the
parameter list (a destructured parameter may carry braces of its
own — renderDocument's target object)."""
for prefix in ("async function ", "function "):
start = js.find(f"{prefix}{name}(")
if start != -1:
depth = 0
i = js.find("(", start)
close = i
while i < len(js):
if js[i] == "(":
depth += 1
elif js[i] == ")":
depth -= 1
if depth == 0:
close = i
break
i += 1
brace = js.find("{", close)
depth = 0
for j in range(brace, len(js)):
if js[j] == "{":
depth += 1
elif js[j] == "}":
depth -= 1
if depth == 0:
return js[start : j + 1]
raise AssertionError(f"unbalanced braces in {name}()")
raise AssertionError(f"{name}() must exist in document.js")
# ---------- the admin gate (the phase-57 split) ----------
def test_wire_date_edit_is_called_only_behind_doc_admin_ready() -> None:
"""renderDocument (the ONE shared core — modal + page): the badge
row is built for EVERYONE first (task-08 shape), and the wiring
runs ONLY in the gate's ``if (admin)`` branch on the
module-cached ``docAdminReady()`` promise — the string sequence
``docAdminReady().then`` … ``wireDateEdit``, one call site in
the whole file (definition + call), so a non-admin / token
holder / failed whoami keeps exactly the task-08 badge row (no
button, no wiring, no admin-only network call)."""
js = _js()
render = _fn(js, "renderDocument")
row = render.find("metaEl.replaceChildren(")
gate = render.find("void docAdminReady().then(")
wiring = render.find("if (admin) wireDateEdit(metaEl, doc);")
assert -1 < row < gate < wiring, (
"the badge row is built for everyone BEFORE the admin gate; "
"the wiring is only in the gate's success branch"
)
# The gate's .then lands wireDateEdit (not the summary edit).
assert "wireDateEdit(metaEl, doc)" in render[gate : gate + 120]
# Exactly two occurrences in the whole file: the definition and
# the gated call — no second wiring site (no per-surface copy).
assert js.count("wireDateEdit(") == 2, (
"wireDateEdit has one definition and one (gated) call site"
)
def test_gate_reuses_the_cached_whoami_promise() -> None:
"""docAdminReady() resolves header.js's fetchIsAdmin (the SAME
single request per page the shared header makes — no second
whoami call site in document.js, no admin-only network call for
anonymous visitors)."""
js = _js()
body = _fn(js, "docAdminReady")
assert "await fetchIsAdmin()" in body
assert "=== true" in body
assert 'fetch("/api/whoami")' not in js, (
"whoami must come from the header.js cached promise"
)
# ---------- the Edit date affordance ----------
def test_edit_date_button_is_a_real_button_after_the_created_badge() -> None:
"""The affordance: a real ``type="button"`` with the visible
text "Edit date", the .doc-date-edit class, and the aria-label
``Edit creation date: <source>/<path>`` (setAttribute — the
document-derived pair is user-storable, never innerHTML),
inserted AFTER the task-08 Created badge (the insertion point).
A meta row without the Created badge (should not happen — the
core always builds it) is a no-op, not a crash."""
body = _fn(_js(), "wireDateEdit")
assert 'metaEl.querySelector(".doc-created")' in body
assert "if (!createdBadge) return;" in body
assert 'editBtn.type = "button"' in body
assert 'editBtn.className = "doc-date-edit"' in body
assert 'editBtn.textContent = "Edit date"' in body
assert "editBtn.setAttribute(" in body
assert "Edit creation date: ${doc.source}/${doc.path}" in body, (
"the aria-label is the 'Edit creation date: <source>/<path>' template"
)
assert 'createdBadge.insertAdjacentElement("afterend", editBtn)' in body, (
"the button lands AFTER the Created badge"
)
# ---------- the editor construction ----------
def test_editor_swaps_in_date_input_save_cancel_revert_and_live_lines() -> None:
"""Edit swaps the button for an inline box in the badge row: a
native <input type="date"> (aria-label "Document creation
date", prefilled via ``.value`` with the stored date's UTC date
part — ``new Date(doc.created_at).toISOString().slice(0, 10)`` —
NEVER innerHTML), Save / Cancel real type=buttons, the muted
"Revert to sync" clear affordance, a role=status/aria-live=polite
live line, and a role=alert error line. The order is hide the
button → insert the box after the Created badge → focus the
input (the phase-57 swap pattern)."""
body = _fn(_js(), "wireDateEdit")
assert 'input.type = "date"' in body
assert 'input.className = "doc-date-input"' in body
assert 'input.setAttribute("aria-label", "Document creation date")' in body
assert "new Date(doc.created_at).toISOString().slice(0, 10)" in body, (
"the prefill is the stored date's UTC date part (D3 stores UTC)"
)
assert 'input.value = storedValue()' in body, "prefill via .value (XSS contract)"
assert 'saveBtn.type = "button"' in body
assert 'saveBtn.className = "doc-date-save"' in body
assert 'saveBtn.textContent = "Save"' in body
assert 'cancelBtn.type = "button"' in body
assert 'cancelBtn.className = "doc-date-cancel"' in body
assert 'cancelBtn.textContent = "Cancel"' in body
assert 'revertBtn.type = "button"' in body
assert 'revertBtn.className = "doc-date-revert"' in body
assert 'revertBtn.textContent = "Revert to sync"' in body
assert 'status.className = "doc-date-status"' in body
assert 'status.setAttribute("role", "status")' in body
assert 'status.setAttribute("aria-live", "polite")' in body
assert 'errorLine.className = "doc-date-error"' in body
assert 'errorLine.setAttribute("role", "alert")' in body
hide = body.find("editBtn.hidden = true")
swap = body.find('createdBadge.insertAdjacentElement("afterend", box)')
focus = body.find("input.focus()")
assert -1 < hide < swap < focus, "hide Edit → insert box → focus the input"
# The swap keeps all six editor parts in the box.
assert (
"box.replaceChildren(input, saveBtn, cancelBtn, revertBtn, status, errorLine)"
in body
)
# XSS contract: the whole wiring is textContent/.value only
# (comments stripped — the word may appear in a note, never in code).
code = re.sub(r"//.*?$|/\*.*?\*/", "", body, flags=re.S | re.M)
assert "innerHTML" not in code, "XSS contract: no innerHTML in the wiring"
# ---------- the PATCH round-trip (the single source) ----------
def test_save_patches_the_single_date_endpoint() -> None:
"""Save → PATCH /api/documents/date (the task-05 admin endpoint)
with the EXACT body shape {source, path, date} — the pair from
the doc object, JSON content type. The endpoint string appears
exactly ONCE in the JS (the single-source cross-file check — the
one call site inside the wired editor matches app/api/docs.py's
route; anonymous visitors never have it)."""
js = _js()
assert js.count('"/api/documents/date"') == 1, (
"exactly one occurrence of the endpoint string in document.js"
)
body = _fn(js, "wireDateEdit")
fetch_i = body.find('fetch("/api/documents/date"')
assert fetch_i != -1, "the PATCH must live in the wired editor"
assert 'method: "PATCH"' in body[fetch_i:]
assert '"Content-Type": "application/json"' in body[fetch_i:]
assert "date: dateValue" in body, "the body shape: {source, path, date}"
assert "source: doc.source" in body and "path: doc.path" in body
# The cross-file check: the JS endpoint matches the Python route.
docs = DOCS_PY.read_text(encoding="utf-8")
assert '@router.patch("/documents/date"' in docs, (
"app/api/docs.py registers the route the JS PATCHes"
)
def test_revert_sends_date_null_the_clear_path() -> None:
"""The "Revert to sync" affordance (the D7 CLEAR — the manual
flag drops, the stored date stands until the next sync) sends
``{source, path, date: null}``: the revert binding calls
saveDate(null) — the ONLY null call site."""
body = _fn(_js(), "wireDateEdit")
assert body.count("void saveDate(null)") == 1, (
"exactly one null (clear) call site"
)
revert_i = body.find('revertBtn.addEventListener("click"')
null_i = body.find("void saveDate(null)", revert_i)
assert -1 < revert_i < null_i, "the null call is the revert binding"
save_i = body.find('saveBtn.addEventListener("click"')
value_i = body.find("void saveDate(input.value)", save_i)
assert -1 < save_i < value_i, "Save sends the input's (non-empty) value"
def test_badge_rerenders_from_the_response_not_the_input() -> None:
"""The UI shows exactly what the server stored: on 200 the doc
object syncs from the RESPONSE (``res.created_at``) and the
badge's text re-renders from ``res.created_at`` (plus the ISO
title — the ellipsis-precision idiom). The input's optimistic
value never feeds the badge anywhere in the file."""
js = _js()
body = _fn(js, "wireDateEdit")
json_i = body.find("const res = await r.json();")
sync_i = body.find("doc.created_at = res.created_at;")
badge_i = body.find("createdBadge.textContent = `Created ${fmtDate(res.created_at)}`")
title_i = body.find('createdBadge.setAttribute("title", res.created_at);')
assert -1 < json_i < sync_i < badge_i, "JSON → doc sync → badge re-render"
assert -1 < badge_i < title_i, "the title follows the same response"
assert "fmtDate(input.value)" not in js, (
"the badge is response-driven — never the input's optimistic value"
)
def test_status_announces_after_the_badge_update() -> None:
"""The success confirmations (the role=status live line) land
AFTER the badge re-render (the phase-89 last-announce order):
'Date saved for <source>/<path>.' for a set, 'Reverted to
sync-managed date.' for the clear."""
body = _fn(_js(), "wireDateEdit")
badge_i = body.find("createdBadge.textContent = `Created ${fmtDate(res.created_at)}`")
saved_i = body.find("`Date saved for ${doc.source}/${doc.path}.`")
reverted_i = body.find('"Reverted to sync-managed date."')
assert -1 < badge_i < reverted_i < saved_i, (
"the badge updates BEFORE either confirmation (last-announce order)"
)
# ---------- §7.4 never-stale lifecycle ----------
def test_submit_disables_controls_before_the_fetch() -> None:
"""One PATCH at a time: Save/Revert lock ALL the editor controls
(input + Save + Cancel + Revert) IMMEDIATELY — before the fetch
— so a double-submit is impossible (PLAN §7.4)."""
body = _fn(_js(), "wireDateEdit")
lock_fn = _fn(body, "setControlsLocked")
assert "input.disabled = locked" in lock_fn
assert "cancelBtn.disabled = locked" in lock_fn
assert "revertBtn.disabled = locked" in lock_fn
lock_i = body.find("setControlsLocked(true)")
fetch_i = body.find('fetch("/api/documents/date"')
assert -1 < lock_i < fetch_i, "the controls lock BEFORE the fetch"
def test_empty_input_disables_save() -> None:
"""An empty type=date input is NOT the clear path: Save disables
itself on an empty input (the explicit Revert below handles the
clear — no accidental wipes). The locked state owns the controls
while a PATCH is in flight (the input is disabled then)."""
body = _fn(_js(), "wireDateEdit")
lock_fn = _fn(body, "setControlsLocked")
assert 'saveBtn.disabled = locked || input.value === ""' in lock_fn
listener_i = body.find('input.addEventListener("input"')
assert listener_i != -1, "the input event keeps Save in sync"
assert 'if (!input.disabled) saveBtn.disabled = input.value === "";' in body
def test_failure_reverts_input_announces_alert_and_reenables() -> None:
"""A failed Save/Revert (non-2xx OR network) lands the server
detail (the git-sources.js apiDetail shape) — or the canned
'Couldn't save the date — try again.' on a non-JSON body — into
the role=alert line, reverts the input to the stored date, and
re-enables the controls in the ``finally`` (every outcome, never
stale). The UI never claims a state the server didn't save."""
body = _fn(_js(), "wireDateEdit")
nonok = body.find("if (!r.ok)")
detail = body.find("errorLine.textContent = await apiDetail(")
canned = body.find("Couldn't save the date — try again.")
revert_nonok = body.find("input.value = storedValue()", nonok)
assert -1 < nonok < detail < canned < revert_nonok, (
"non-ok: server detail (canned fallback) → alert line → input reverts"
)
catch_i = body.find("} catch {")
canned2 = body.find("Couldn't save the date — try again.", catch_i)
revert_catch = body.find("input.value = storedValue()", catch_i)
assert -1 < catch_i < canned2 < revert_catch, (
"network failure: canned retry copy → input reverts"
)
finally_i = body.find("finally {")
unlock = body.find("setControlsLocked(false)", finally_i)
assert -1 < finally_i < unlock, "the controls re-enable in the finally"
def test_failure_keeps_the_editor_open() -> None:
"""Neither failure branch (the non-ok early return, the network
catch) collapses the editor or clears the user's view — the
alert line + the reverted stored value are visible (the editor
stays open; only Cancel and the success beat collapse it)."""
body = _fn(_js(), "wireDateEdit")
nonok_slice = body[body.find("if (!r.ok)") : body.find("const res = await r.json();")]
assert "closeEditor" not in nonok_slice, "non-ok keeps the editor open"
catch_slice = body[body.find("} catch {") : body.find("finally {")]
assert "closeEditor" not in catch_slice, "the network catch keeps the editor open"
def test_cancel_closes_without_a_patch() -> None:
"""Cancel collapses back to the badge row + the Edit button
(focus returns to the opener) and sends NO PATCH (the stored
value is untouched — the badge was never mutated)."""
body = _fn(_js(), "wireDateEdit")
cancel_i = body.find('cancelBtn.addEventListener("click"')
close_i = body.find("closeEditor()", cancel_i)
assert -1 < cancel_i < close_i, "Cancel closes the editor"
assert "fetch" not in body[cancel_i : close_i], "Cancel sends no PATCH"
close_fn = _fn(body, "closeEditor")
assert 'createdBadge.insertAdjacentElement("afterend", editBtn)' in close_fn
assert "box.remove()" in close_fn
assert "editBtn.focus()" in close_fn, "focus returns to the opener"
# ---------- styles.css ----------
def test_date_editor_classes_present_next_to_the_summary_family() -> None:
"""styles.css carries the eight editor classes (the button, the
box, the input, Save / Cancel, the muted revert, the status and
the alert line) placed NEXT to the summary-editor rule family
(after .doc-summary-status, before the raw-format block), with
the house palette (phase-08 tokens) and no CDN."""
css = _css()
for cls in (
".doc-date-edit",
".doc-date-editor",
".doc-date-input",
".doc-date-save",
".doc-date-cancel",
".doc-date-revert",
".doc-date-status",
".doc-date-error",
):
assert f"{cls} " in css, f"styles.css must style {cls}"
assert (
css.find(".doc-summary-status:empty")
< css.find(".doc-date-edit {")
< css.find(".doc-raw {")
), "the editor family sits next to the summary-editor rules"
assert "url(http" not in css and "@import url(" not in css, (
"no CDN (AGENTS.md rule 6)"
)
def test_date_editor_css_provenance_and_contrast() -> None:
"""The phase-106 D7 provenance comment sits directly above the
button rule with the verified WCAG pairs recorded (house
style): the ink-soft row family (5.1:1 on --surface), the brand
pill Save (--bg on --brand = 5.2:1), the input ink on --bg
(16.7:1), and the alert line's err pair (9.1:1 on --err-bg) —
all ≥4.5:1. The [hidden] override beats the button's display,
and :focus-visible rides the global 3px outline rule (no
per-control rule — the phase-105 checkbox idiom)."""
css = _css()
rule_i = css.find(".doc-date-edit {")
comment_start = css.rfind("/*", 0, rule_i)
comment_end = css.find("*/", comment_start)
assert -1 < comment_start < rule_i and comment_end < rule_i, (
"a comment block must sit directly above the button rule"
)
header = css[comment_start:comment_end]
assert "phase 106" in header.lower() and "D7" in header, (
"the provenance comment cites phase 106 + D7"
)
for pair in ("5.1:1", "5.2:1", "9.1:1", "16.7:1"):
assert pair in header, f"the verified contrast pair {pair} is recorded"
hidden = css.find(".doc-date-edit[hidden]")
assert hidden != -1 and "display: none" in css[hidden : hidden + 80], (
"the hidden attr must beat the button's display rule"
)
assert ":focus-visible {" in css
assert "outline: 3px solid var(--brand)" in css
def test_date_editor_disabled_uses_the_wait_idiom() -> None:
"""The :disabled state on the editor controls is the
.git-source-remove:disabled idiom (opacity + cursor: wait — one
PATCH at a time), and the empty live lines take no space
(display: none on :empty)."""
css = _css()
dis = css.find(".doc-date-save:disabled")
assert dis != -1, "the disabled rule names the editor controls"
block = css[dis : css.find("}", dis)]
for cls in (
".doc-date-save:disabled",
".doc-date-cancel:disabled",
".doc-date-revert:disabled",
".doc-date-input:disabled",
):
assert cls in block, f"the disabled idiom covers {cls}"
assert "opacity: 0.5" in block and "cursor: wait" in block
empty = css.find(".doc-date-status:empty")
assert empty != -1, "the empty live lines hide themselves"
empty_block = css[empty : css.find("}", empty)]
assert "display: none" in empty_block