phase: 154_sync_status_freshness
All 4 task files exist in `complete/`, and only `00_phase.md` remains in `todo/`. This is the final verification pass. I'll read through the task files and check the current state of the code.
This commit is contained in:
@@ -149,6 +149,46 @@ positions are distinct and each disappears independently. The node
|
||||
harness gains the two in-flight image rows (both parts at once —
|
||||
the owner's stuck label — and the image-only pictorial PDF) and
|
||||
pins the composed full string byte-for-byte.
|
||||
|
||||
Phase 154 (task 02) extends the Sources-page upload line with the
|
||||
COMPLETION STAMP (the phase's date-qualified freshness): the settled
|
||||
line reads "Uploaded <name> (<stamp>) — press Sync sources to import
|
||||
it." (the fresh 202 settle, the page-local name) or the nameless
|
||||
"Uploaded <stamp> — press Sync sources to import it." (the boot
|
||||
re-render — the stamp comes from the SERVER payload, so the line is
|
||||
self-dating even with the name unknown); a running / legacy / partial
|
||||
payload without timestamps renders the byte-identical pre-phase-154
|
||||
strings (the nameful / nameless "Uploaded … — press Sync sources to
|
||||
import it." lines). The stamp is PRECOMPUTED by the page-local
|
||||
fmtUploadStamp(status) (the per-page duplication idiom — the same A1
|
||||
date-qualifier as sources.js's fmtSyncTime, duplicated, not shared;
|
||||
the rule's boundary set is pinned in test_sync_button.py on a fixed
|
||||
clock): it reads status.finished_at first (the success state always
|
||||
has it on the server), falls back to status.started_at (a defensive
|
||||
guard — a corrupt finished_at degrades to the started_at stamp rather
|
||||
than to no stamp), and yields "" when both are absent or every ISO is
|
||||
unparseable. Both render sites — the poll's success settle and the
|
||||
boot re-attach — pass fmtUploadStamp(status) as the third argument;
|
||||
the NO server change (the /upload/status payload is untouched —
|
||||
started_at / finished_at already shipped, phase 64).
|
||||
|
||||
Phase 154 (task 03) pins the BYTE-IDENTITY of the no-stamp fallbacks
|
||||
against the VERBATIM pre-phase-154 strings: the old 2-arg
|
||||
fmtUploadResult (git history) is pinned in this file as a reference
|
||||
implementation (the name renamed, the body byte-for-byte) and run
|
||||
side by side with the real 3-arg function under node — the no-stamp
|
||||
(nameful / nameless) outputs + the legacy 2-arg call are asserted
|
||||
byte-identical to the reference's output, and the reference's
|
||||
output to the verbatim old line strings (assertable, not assumed).
|
||||
Clock-free: `when` is a precomputed argument, so the no-stamp
|
||||
shapes need no clock at all. The /upload/status full-dict pin
|
||||
(tests/integration/test_git_sources_upload.py, the phase-64
|
||||
precedent) stays UNCHANGED — this phase adds no server keys (the
|
||||
app/ no-touch regression guard). The qualifier's byte-identity
|
||||
(same-day + fallbacks vs the verbatim pre-phase fmtSyncTime) is
|
||||
pinned in test_sync_button.py on the shared fixed clock; the four
|
||||
shapes and the fmtUploadStamp behavior (finished / started /
|
||||
missing / garbage) ride the task-02 pins below.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -1639,23 +1679,42 @@ def test_processing_state_is_bare_for_the_whole_run() -> None:
|
||||
|
||||
|
||||
def test_fmt_upload_result_is_the_ready_for_sync_line() -> None:
|
||||
"""Phase 90 (A2/A3): the result line reads the no-count
|
||||
{"message": "uploaded"} detail and renders "Uploaded <name> —
|
||||
press Sync sources to import it." (the name from the accepted
|
||||
202); without a name (a reload re-render, the 409 re-attach) it
|
||||
renders the nameless variant; the old sync-style count keys
|
||||
"""Phase 90 (A2/A3) extended by Phase 154 (task 02): the result
|
||||
line reads the no-count {"message": "uploaded"} detail and renders
|
||||
the ready-for-sync line in the FOUR phase-154 shapes —
|
||||
"Uploaded <name> (<stamp>) — press Sync sources to import it." (the
|
||||
name + the completion stamp), "Uploaded <name> — press Sync sources
|
||||
to import it." (name, no stamp — BYTE-IDENTICAL to the pre-phase
|
||||
string), "Uploaded <stamp> — press Sync sources to import it." (no
|
||||
name + stamp — the boot re-render, self-dating with the name
|
||||
unknown), "Uploaded — press Sync sources to import it." (no name,
|
||||
no stamp — BYTE-IDENTICAL to the pre-phase string; the running /
|
||||
legacy / partial payload). The non-uploaded detail keeps the
|
||||
unchanged "The upload finished."; the old sync-style count keys
|
||||
(added / updated / unchanged / pruned) are no longer read at all.
|
||||
The 202 branch records the safe name (lastUploadName); the poll's
|
||||
settle and the boot re-attach both render the line from
|
||||
(detail, lastUploadName); the button's idle label is "Upload"."""
|
||||
settle and the boot re-attach both render the line from (detail,
|
||||
lastUploadName, fmtUploadStamp(status)); the button's idle label
|
||||
is "Upload"."""
|
||||
js = _gjs()
|
||||
body = _gfn(js, "fmtUploadResult")
|
||||
assert "function fmtUploadResult(detail, name)" in body
|
||||
assert 'detail.message === "uploaded"' in body
|
||||
assert "Uploaded ${name} — press Sync sources to import it." in body
|
||||
assert "Uploaded — press Sync sources to import it." in body, (
|
||||
"the nameless variant (reload / 409 re-attach)"
|
||||
assert "function fmtUploadResult(detail, name, when)" in body, (
|
||||
"phase 154: the precomputed completion stamp is the third parameter"
|
||||
)
|
||||
assert 'detail.message === "uploaded"' in body
|
||||
# The four shapes (name×stamp, name×no-stamp, no-name×stamp,
|
||||
# no-name×no-stamp).
|
||||
assert "Uploaded ${name} (${when}) — press Sync sources to import it." in body
|
||||
assert "Uploaded ${name} — press Sync sources to import it." in body, (
|
||||
"the no-stamp nameful variant — byte-identical to pre-phase"
|
||||
)
|
||||
assert "Uploaded ${when} — press Sync sources to import it." in body, (
|
||||
"the nameless + stamp variant (the boot re-render — self-dating)"
|
||||
)
|
||||
assert "Uploaded — press Sync sources to import it." in body, (
|
||||
"the nameless no-stamp variant — byte-identical to pre-phase"
|
||||
)
|
||||
assert '"The upload finished."' in body, ("the non-uploaded branch is unchanged")
|
||||
for key in ("added", "updated", "unchanged", "pruned"):
|
||||
assert key not in body, f"the sync-style count {key!r} is gone"
|
||||
# The 202 branch records the safe name for the settled line.
|
||||
@@ -1664,9 +1723,19 @@ def test_fmt_upload_result_is_the_ready_for_sync_line() -> None:
|
||||
i409 = sub.find("r.status === 409")
|
||||
assert "lastUploadName = name" in sub[i202:i409]
|
||||
assert "let lastUploadName = null" in js, "page-local (null after a reload)"
|
||||
# Both render sites pass (detail, lastUploadName).
|
||||
assert "fmtUploadResult(detail, lastUploadName)" in _utick(js)
|
||||
assert "fmtUploadResult(status.detail, lastUploadName)" in _gfn(js, "initUploadStatus")
|
||||
# Both render sites pass (…, lastUploadName, fmtUploadStamp(status)).
|
||||
tick = _utick(js)
|
||||
assert re.search(
|
||||
r"uploadResult\.textContent = fmtUploadResult\(\s*detail,\s*"
|
||||
r"lastUploadName,\s*fmtUploadStamp\(status\)\s*\)",
|
||||
tick,
|
||||
), "the poll's success settle passes the completion stamp (phase 154)"
|
||||
init = _gfn(js, "initUploadStatus")
|
||||
assert re.search(
|
||||
r"uploadResult\.textContent = fmtUploadResult\(\s*status\.detail,\s*"
|
||||
r"lastUploadName,\s*fmtUploadStamp\(status\)\s*\)",
|
||||
init,
|
||||
), "the boot re-attach passes the completion stamp (phase 154)"
|
||||
# The button's idle label (static + the poll's restores).
|
||||
restore = _gfn(js, "restoreUploadButton")
|
||||
assert 'uploadBtn.textContent = "Upload"' in restore
|
||||
@@ -1751,7 +1820,7 @@ def test_upload_polling_decision_tree() -> None:
|
||||
# next-step announce + reload, NO toast.
|
||||
ok = tick[i_ok:i_fail]
|
||||
for line in (
|
||||
"fmtUploadResult(detail, lastUploadName)",
|
||||
"fmtUploadResult(",
|
||||
"uploadResult.hidden = false",
|
||||
'announce("Archive uploaded — press Sync sources to import it.")',
|
||||
'uploadFileInput.value = ""',
|
||||
@@ -1759,6 +1828,11 @@ def test_upload_polling_decision_tree() -> None:
|
||||
"loadSources()",
|
||||
):
|
||||
assert line in ok, f"the success settle must carry {line!r}"
|
||||
assert re.search(
|
||||
r"uploadResult\.textContent = fmtUploadResult\(\s*detail,\s*"
|
||||
r"lastUploadName,\s*fmtUploadStamp\(status\)\s*\)",
|
||||
ok,
|
||||
), "the success settle passes the completion stamp (phase 154)"
|
||||
assert "showUploadToast" not in tick, "no toast in the poll — it fired at the 202 (A2)"
|
||||
# failed: the sanitized error banner + reload.
|
||||
fail = tick[i_fail:]
|
||||
@@ -1815,8 +1889,14 @@ def test_boot_reattach_branches() -> None:
|
||||
assert "uploadError" not in run and "showUploadToast" not in run
|
||||
ok = body[i_ok:i_fail]
|
||||
# The last result line — the nameless variant after a reload
|
||||
# (lastUploadName is null: the safe name was page-local, phase 90).
|
||||
assert "fmtUploadResult(status.detail, lastUploadName)" in ok
|
||||
# (lastUploadName is null: the safe name was page-local, phase 90),
|
||||
# self-dating through the completion stamp (phase 154 — the stamp
|
||||
# comes from the server payload).
|
||||
assert re.search(
|
||||
r"uploadResult\.textContent = fmtUploadResult\(\s*status\.detail,\s*"
|
||||
r"lastUploadName,\s*fmtUploadStamp\(status\)\s*\)",
|
||||
ok,
|
||||
), "the boot re-attach passes the completion stamp (phase 154)"
|
||||
assert "uploadResult.hidden = false" in ok
|
||||
assert "announce(" not in ok, "no announce at boot (A2)"
|
||||
assert "showUploadToast" not in ok, "no toast at boot (A2)"
|
||||
@@ -1860,3 +1940,330 @@ def test_git_sources_html_comment_documents_the_202_contract() -> None:
|
||||
assert "Phase 90" in comment, "the unpack-only rework is documented"
|
||||
assert "UNPACK + REGISTER ONLY" in comment
|
||||
assert "Sync sources" in comment, "the result line points at the RAG page's button"
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# Phase 154 (task 02) — the Sources upload line's completion stamp
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def test_fmt_upload_stamp_is_the_duplicated_qualifier() -> None:
|
||||
"""Phase 154 (task 02): ``fmtUploadStamp(status)`` — the page-local
|
||||
DUPLICATE of sources.js's ``fmtSyncTime`` date-qualifier (the
|
||||
per-page duplication idiom — not shared). It reads
|
||||
``status.finished_at`` first (the success state always has it on
|
||||
the server), falls back to ``status.started_at`` (the defensive
|
||||
guard — a corrupt ``finished_at`` degrades to the ``started_at``
|
||||
stamp rather than to no stamp), and yields ``""`` when both are
|
||||
missing/absent or every ISO is unparseable (the byte-identical
|
||||
pre-phase-154 fallback). The qualifier is the SAME A1 structure as
|
||||
the phase-154 task-01 pin in test_sync_button.py — local calendar
|
||||
dates, ``HH:MM`` same day, ``M/D, h:MM AM/PM`` same year,
|
||||
``M/D/YYYY, h:MM AM/PM`` across years, the 12-hour form WITHOUT a
|
||||
leading zero on the hour, month/day without leading zeros — cited
|
||||
in sources.js, not restated. The ``status || {}`` guard defends a
|
||||
missing status object."""
|
||||
js = _gjs()
|
||||
body = _gfn(js, "fmtUploadStamp")
|
||||
assert "function fmtUploadStamp(status)" in body
|
||||
assert "const s = status || {};" in body, (
|
||||
"defensive: a missing / null status object yields ''"
|
||||
)
|
||||
assert "s.finished_at" in body and "s.started_at" in body
|
||||
i_fin = body.find("s.finished_at")
|
||||
i_st = body.find("s.started_at")
|
||||
assert -1 < i_fin < i_st, "finished_at is read first — the started_at fallback"
|
||||
assert "if (!iso) continue;" in body, (
|
||||
"absent / null timestamps are skipped (the fallback reads)"
|
||||
)
|
||||
assert "if (!Number.isNaN(t.getTime()))" in body, (
|
||||
"an unparseable ISO is skipped — a corrupt finished_at degrades"
|
||||
" to started_at, '' when neither parses"
|
||||
)
|
||||
assert 'if (d === null) return "";' in body, (
|
||||
"both absent (or unparseable) → '' — the byte-identical pre-"
|
||||
"phase-154 fallback"
|
||||
)
|
||||
# The duplicated A1 qualifier (cited in sources.js's fmtSyncTime,
|
||||
# not restated) — the same structure as the task-01 pin.
|
||||
assert "const now = new Date();" in body, "'now' is the page's clock"
|
||||
assert (
|
||||
"d.getFullYear() === now.getFullYear() &&" in body
|
||||
and "d.getMonth() === now.getMonth()" in body
|
||||
and "d.getDate() === now.getDate()" in body
|
||||
), "same local calendar day as now → HH:MM (byte-identical to pre-phase)"
|
||||
assert "d.getHours() % 12 || 12" in body, (
|
||||
"12-hour WITHOUT leading zero on the hour (the 12 o'clock edge)"
|
||||
)
|
||||
assert '"AM"' in body and '"PM"' in body, "the AM/PM family"
|
||||
assert "pad(d.getMinutes())" in body, "MM zero-padded in every form"
|
||||
assert "`${d.getMonth() + 1}/${d.getDate()}`" in body, (
|
||||
"M/D, no leading zeros (same year, different day)"
|
||||
)
|
||||
assert "`${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`" in body, (
|
||||
"M/D/YYYY across years"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
|
||||
def test_upload_result_four_shapes_under_node() -> None:
|
||||
"""Phase 154 (task 02): the REAL git-sources.js ``fmtUploadResult``
|
||||
under node — the four shapes, ``when`` PRECOMPUTED by the caller
|
||||
(so the shape itself is clock-free):
|
||||
|
||||
* name + stamp: ``Uploaded docs.zip (21:14) — press Sync sources
|
||||
to import it.`` (the fresh 202 settle, the page-local name);
|
||||
* name, no stamp (``""``): ``Uploaded docs.zip — press Sync
|
||||
sources to import it.`` — BYTE-IDENTICAL to the pre-phase-154
|
||||
string (the running / legacy / partial payload);
|
||||
* no name + stamp: ``Uploaded 10/2, 3:14 PM — press Sync sources
|
||||
to import it.`` (the boot re-render — self-dating with the name
|
||||
unknown);
|
||||
* no name, no stamp (``""``): ``Uploaded — press Sync sources to
|
||||
import it.`` — BYTE-IDENTICAL to the pre-phase-154 string;
|
||||
* a legacy 2-arg call (``when`` undefined) renders the pre-phase
|
||||
nameful string (the fallback is falsy-driven);
|
||||
* the non-``uploaded`` detail and a missing detail keep the
|
||||
unchanged ``The upload finished.``."""
|
||||
js = _gjs()
|
||||
fn = _gfn(js, "fmtUploadResult")
|
||||
script = (
|
||||
fn
|
||||
+ "\n"
|
||||
'const S = { message: "uploaded" };\n'
|
||||
'const O = { message: "other" };\n'
|
||||
"console.log(JSON.stringify([\n"
|
||||
' fmtUploadResult(S, "docs.zip", "21:14"),\n'
|
||||
' fmtUploadResult(S, "docs.zip", ""),\n'
|
||||
' fmtUploadResult(S, null, "10/2, 3:14 PM"),\n'
|
||||
' fmtUploadResult(S, null, ""),\n'
|
||||
' fmtUploadResult(S, "docs.zip"),\n'
|
||||
' fmtUploadResult(O, "docs.zip", "21:14"),\n'
|
||||
" fmtUploadResult(null, \"docs.zip\", \"21:14\"),\n"
|
||||
"]));\n"
|
||||
)
|
||||
rows = json.loads(_run_node(script))
|
||||
assert rows == [
|
||||
"Uploaded docs.zip (21:14) — press Sync sources to import it.",
|
||||
# byte-identical to pre-phase (the no-stamp nameful variant):
|
||||
"Uploaded docs.zip — press Sync sources to import it.",
|
||||
"Uploaded 10/2, 3:14 PM — press Sync sources to import it.",
|
||||
# byte-identical to pre-phase (the no-stamp nameless variant):
|
||||
"Uploaded — press Sync sources to import it.",
|
||||
# the legacy 2-arg call (when undefined → falsy → pre-phase):
|
||||
"Uploaded docs.zip — press Sync sources to import it.",
|
||||
"The upload finished.",
|
||||
"The upload finished.",
|
||||
], rows
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# Phase 154 (task 03) — the byte-identity pins (the pre-phase
|
||||
# strings, pinned verbatim — assertable, not assumed)
|
||||
# =====================================================================
|
||||
|
||||
#: The pre-phase-154 fmtUploadResult, pinned VERBATIM from git
|
||||
#: history (the 2-arg no-count formatter — the phase-90 contract
|
||||
#: pre-stamp). Only the function NAME is renamed (so the real + the
|
||||
#: reference run side by side under node); the body is the old code
|
||||
#: byte-for-byte — the two old line strings + "The upload finished."
|
||||
#: live in this constant, so the byte-identity below is assertable,
|
||||
#: not assumed.
|
||||
PRE_PHASE_FMT_UPLOAD_RESULT_JS = """
|
||||
function prePhaseUploadResult(detail, name) {
|
||||
if (detail && detail.message === "uploaded") {
|
||||
return name
|
||||
? `Uploaded ${name} — press Sync sources to import it.`
|
||||
: "Uploaded — press Sync sources to import it.";
|
||||
}
|
||||
return "The upload finished.";
|
||||
}
|
||||
"""
|
||||
|
||||
#: The two pre-phase-154 result-line strings, pinned verbatim as
|
||||
#: constants (the byte-identity targets — the task-03 completion
|
||||
#: criterion). The nameful one is the old template instantiated with
|
||||
#: the test's input name.
|
||||
PRE_PHASE_UPLOAD_LINE_NAMEFUL = "Uploaded docs.zip — press Sync sources to import it."
|
||||
PRE_PHASE_UPLOAD_LINE_NAMELESS = "Uploaded — press Sync sources to import it."
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
|
||||
def test_upload_result_no_stamp_is_byte_identical_to_pre_phase() -> None:
|
||||
"""Phase 154 (task 03): the two BYTE-IDENTITY fallbacks — the
|
||||
REAL 3-arg ``fmtUploadResult`` and the VERBATIM pre-phase 2-arg
|
||||
formatter (pinned above) run side by side under node (clock-free
|
||||
— ``when`` is precomputed):
|
||||
|
||||
* name + ``when === ""`` → the pre-phase nameful string,
|
||||
byte-identical (the running / legacy / partial payload);
|
||||
* no name + ``when === ""`` → the pre-phase nameless string,
|
||||
byte-identical (the same payload, the name unknown);
|
||||
* the legacy 2-arg call (``when`` undefined → falsy) renders the
|
||||
SAME two byte-identical strings (the old call shape is
|
||||
unchanged in output);
|
||||
* the non-``uploaded`` detail (and a missing detail) keep
|
||||
``The upload finished.`` on BOTH sides (the branch the phase
|
||||
never touched).
|
||||
|
||||
The reference implementation's own output is asserted against the
|
||||
verbatim old strings FIRST (a self-check on the pinned constant —
|
||||
a typo in the pinned function fails here, not silently), so the
|
||||
real-vs-reference identity is an assertion against the verbatim
|
||||
pre-phase strings (the completion criterion), not an
|
||||
assumption."""
|
||||
# Source-level self-check: the pinned constant carries the old
|
||||
# strings verbatim (both line templates + the non-uploaded
|
||||
# branch).
|
||||
assert (
|
||||
"return name\n"
|
||||
" ? `Uploaded ${name} — press Sync sources to import it.`\n"
|
||||
' : "Uploaded — press Sync sources to import it.";'
|
||||
in PRE_PHASE_FMT_UPLOAD_RESULT_JS
|
||||
), "the old nameful + nameless line templates are pinned verbatim"
|
||||
assert '"The upload finished."' in PRE_PHASE_FMT_UPLOAD_RESULT_JS, (
|
||||
"the non-uploaded branch is pinned verbatim"
|
||||
)
|
||||
|
||||
fn = _gfn(_gjs(), "fmtUploadResult")
|
||||
script = (
|
||||
fn
|
||||
+ "\n"
|
||||
+ PRE_PHASE_FMT_UPLOAD_RESULT_JS
|
||||
+ "\n"
|
||||
'const S = { message: "uploaded" };\n'
|
||||
'const O = { message: "other" };\n'
|
||||
"console.log(JSON.stringify({\n"
|
||||
" REAL: [\n"
|
||||
' fmtUploadResult(S, "docs.zip", ""),\n'
|
||||
" fmtUploadResult(S, null, \"\"),\n"
|
||||
' fmtUploadResult(S, "docs.zip"),\n' # legacy 2-arg call
|
||||
" fmtUploadResult(S, null),\n" # legacy 2-arg call
|
||||
' fmtUploadResult(O, "docs.zip", "21:14"),\n'
|
||||
' fmtUploadResult(null, "docs.zip", "21:14"),\n'
|
||||
" ],\n"
|
||||
" PRE: [\n"
|
||||
' prePhaseUploadResult(S, "docs.zip"),\n'
|
||||
" prePhaseUploadResult(S, null),\n"
|
||||
' prePhaseUploadResult(O, "docs.zip"),\n'
|
||||
' prePhaseUploadResult(null, "docs.zip"),\n'
|
||||
" ]\n"
|
||||
"}));\n"
|
||||
)
|
||||
rows = json.loads(_run_node(script))
|
||||
# 1. the pinned reference reproduces the verbatim old strings
|
||||
# (self-check on the constant — assertable, not assumed).
|
||||
assert rows["PRE"] == [
|
||||
PRE_PHASE_UPLOAD_LINE_NAMEFUL,
|
||||
PRE_PHASE_UPLOAD_LINE_NAMELESS,
|
||||
"The upload finished.",
|
||||
"The upload finished.",
|
||||
], rows
|
||||
# 2. the no-stamp + legacy shapes are byte-identical to the
|
||||
# reference (i.e. to the verbatim old strings).
|
||||
assert rows["REAL"][:4] == [
|
||||
PRE_PHASE_UPLOAD_LINE_NAMEFUL, # name + when === ""
|
||||
PRE_PHASE_UPLOAD_LINE_NAMELESS, # no name + when === ""
|
||||
PRE_PHASE_UPLOAD_LINE_NAMEFUL, # legacy 2-arg call
|
||||
PRE_PHASE_UPLOAD_LINE_NAMELESS, # legacy 2-arg call
|
||||
], rows
|
||||
assert rows["REAL"][0] == rows["PRE"][0], (
|
||||
"the nameful no-stamp shape is byte-identical to pre-phase"
|
||||
)
|
||||
assert rows["REAL"][1] == rows["PRE"][1], (
|
||||
"the nameless no-stamp shape is byte-identical to pre-phase"
|
||||
)
|
||||
# 3. the non-uploaded branch is unchanged on both sides.
|
||||
assert rows["REAL"][4] == rows["PRE"][2] == "The upload finished."
|
||||
assert rows["REAL"][5] == rows["PRE"][3] == "The upload finished."
|
||||
|
||||
|
||||
#: Phase 154 (task 02): the fixed clock for the stamp's boundary set —
|
||||
#: the SAME fixed now as test_sync_button.py's task-01 pin (Oct 3,
|
||||
#: 2026, 18:30 LOCAL). The test body never calls `new Date()` for its
|
||||
#: own timing: the fake clock is installed in the driver (global Date
|
||||
#: patched BEFORE the call — `new Date()` → the fixed now,
|
||||
#: `new Date(iso)` → the real parse), and every ISO is
|
||||
#: designator-less (local) so the comparison is
|
||||
#: timezone-independent.
|
||||
|
||||
_UPLOAD_STAMP_NOW_JS = "new RealDate(2026, 9, 3, 18, 30, 0)"
|
||||
|
||||
|
||||
def _upload_stamp_driver(fn_src: str, expr: str) -> str:
|
||||
"""Run `expr` under node with the REAL git-sources.js function +
|
||||
the fake clock (the idiom: the Date patch precedes the call)."""
|
||||
driver = (
|
||||
f"{fn_src}\n"
|
||||
"const RealDate = Date;\n"
|
||||
f"const NOW = {_UPLOAD_STAMP_NOW_JS};\n"
|
||||
"globalThis.Date = function (...args) {\n"
|
||||
" return args.length === 0 ? NOW : new RealDate(...args);\n"
|
||||
"};\n"
|
||||
f"{expr}\n"
|
||||
)
|
||||
proc = subprocess.run(
|
||||
["node", "-e", driver], capture_output=True, text=True, timeout=60
|
||||
)
|
||||
assert proc.returncode == 0, f"node failed: {proc.stderr}"
|
||||
return proc.stdout
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
|
||||
def test_upload_stamp_behavior_fixed_clock() -> None:
|
||||
"""Phase 154 (task 02): the REAL git-sources.js
|
||||
``fmtUploadStamp`` under node (fake clock, the fixed now Oct 3
|
||||
2026 18:30 local — the same clock as test_sync_button.py's
|
||||
task-01 pin): finished wins over started; started when finished is
|
||||
missing/null; ``""`` when both are absent (or the status object is
|
||||
missing — the defensive guard); a garbage ISO → ``""`` with no
|
||||
fallback (and a corrupt finished_at degrades to the started_at
|
||||
stamp when a started_at is present — the defensive guard); the A1
|
||||
date-qualifier's boundary set (same day ``HH:MM`` — the audit's
|
||||
own ``21:14`` case + the end-of-day edge; yesterday
|
||||
``M/D, h:MM AM/PM`` with the ``12:05 AM`` edge; last year
|
||||
``M/D/YYYY, h:MM AM/PM``). The wall clock never appears in an
|
||||
assertion."""
|
||||
fn = _gfn(_gjs(), "fmtUploadStamp")
|
||||
|
||||
def _lit(status: object) -> str:
|
||||
if status is None:
|
||||
return "null"
|
||||
assert isinstance(status, dict)
|
||||
parts = []
|
||||
for k, v in status.items():
|
||||
if v is None:
|
||||
parts.append(f"{k}: null")
|
||||
else:
|
||||
parts.append(f'{k}: "{v}"')
|
||||
return "{" + ", ".join(parts) + "}"
|
||||
|
||||
# (status, expected) — the expected strings are the pins.
|
||||
cases = [
|
||||
(
|
||||
{"finished_at": "2026-10-03T16:06:00", "started_at": "2026-10-03T16:05:00"},
|
||||
"16:06", # finished wins over started
|
||||
),
|
||||
({"started_at": "2026-10-02T15:14:00"}, "10/2, 3:14 PM"), # no finished → started
|
||||
(
|
||||
{"finished_at": None, "started_at": "2025-10-03T21:12:00"},
|
||||
"10/3/2025, 9:12 PM", # null finished → started, last year
|
||||
),
|
||||
({"finished_at": "garbage"}, ""), # unparseable, no fallback
|
||||
(
|
||||
{"finished_at": "garbage", "started_at": "2026-10-03T16:06:00"},
|
||||
"16:06", # corrupt finished degrades to the started_at stamp
|
||||
),
|
||||
({"finished_at": None, "started_at": None}, ""), # both absent
|
||||
({}, ""), # both missing
|
||||
(None, ""), # the status object itself missing (the guard)
|
||||
({"finished_at": "2026-10-02T00:05:00"}, "10/2, 12:05 AM"), # 12:05 AM edge
|
||||
({"finished_at": "2026-10-03T23:59:00"}, "23:59"), # same day, end-of-day
|
||||
]
|
||||
driver_expr = (
|
||||
"const CASES = [" + ", ".join(_lit(s) for s, _ in cases) + "];\n"
|
||||
"const STAMPS = CASES.map((c) => fmtUploadStamp(c));\n"
|
||||
"console.log(JSON.stringify(STAMPS));"
|
||||
)
|
||||
out = json.loads(_upload_stamp_driver(fn, driver_expr))
|
||||
assert out == [expected for _, expected in cases], out
|
||||
|
||||
Reference in New Issue
Block a user