CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / TypeScript typecheck (push) Successful in 10s
CI & Build / integration (push) Successful in 31s
CI & Build / Python tests (push) Successful in 1m5s
CI & Build / Build & push image (push) Successful in 38s
#3127 checklist 12, plus rule 27 — a capability with no surface the operator can touch is not shipped. The step was planned on the premise that nothing read `/api/version`. Two things did, and the state was worse than nothing: - `App.vue` fetched it, wrote `version` into a ref initialised to the literal `"dev"`, and swallowed the error. An instance that could not answer rendered EXACTLY what a healthy local build renders. That is checklist 12's named failure — a blank standing in for `unknown` — in the one readout whose whole job is to say what is running, and it would have made #3298's debugging session no cheaper. - `SettingsView.vue` fetched the same endpoint again on every mount and wrote the result into a local ref no template ever read. A duplicate request whose answer was discarded. So this is not "add a readout"; it is "make the existing one honest, and give it the three fields nobody could see." The readout — Settings → Config, first section, beside the other "what is this instance doing" facts. Three states kept apart, because collapsing any two of them is the defect: not asked yet (tab unopened) nothing answered the values, each ABSENT field as "unknown" the fetch itself failed its own message, with a retry `version` and `channel` prominent, `commit` in full with a copy button so it can be pasted into a `:sha` lookup (rule 145 — the registry's identity and the artifact's own must be checkable against each other), `build` kept because its ABSENCE is the diagnostic part: no ordering key means this build is not in any update order, which is what a local or hand-built image looks like. Absence, not falsiness. The payload omits what it does not know rather than sending `""` or `0` (see `build_version_payload`), so the renderer uses `??` throughout — `build` is a number and `0` is a legitimate ordering key, which `||` would report as unknown. `tests/test_version_readout.py` pins that operator specifically, along with the "no plausible default" property, because `||` is the form a person reaches for by habit. Rule 156 — the fetch carries a deadline. This readout is consulted when an instance is misbehaving, which is exactly when it may never answer; without one the surface sits on "still loading" forever, which is the same blank arrived at from the other direction. `apiGet` gains an OPT-IN `timeoutMs` rather than a default, so no existing call site's behaviour moves. Every other call in the client still has no deadline — reported separately, not fixed here. No frontend test runner exists, so verification is the typecheck lane plus four source-inspection guards in the unit lane, each pinning one property. Also folded in: `plugin/README.md` now leads with the mint script and offers `make` second, since `make` is not installed on every workstation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TcCs1CcQ1ormdnzSshKqvN
87 lines
4.0 KiB
Python
87 lines
4.0 KiB
Python
"""The app must SAY what it is running, and must not lie when it cannot find out.
|
|
|
|
There is no frontend test runner in this repo, so these are source-inspection
|
|
guards in the unit lane — the same idiom `check_plugin.py` uses on the hook
|
|
shells. They are deliberately few and deliberately about ONE property each,
|
|
because a grep-shaped test that asserts a whole file's contents fails on every
|
|
refactor and gets deleted.
|
|
|
|
WHY THIS FILE EXISTS. #3298: with a deploy misbehaving, nothing on the instance
|
|
could say which commit was serving it, and the one endpoint whose job that is
|
|
answered with the name of a branch. The value was fixed then. This is the other
|
|
half — the value reaching a person — and #3127 checklist 12 is specific about
|
|
the way it goes wrong: *never let a blank stand in for `unknown`*. A readout
|
|
that renders a plausible value it never received is worse than one that renders
|
|
nothing, because it ends the investigation instead of starting it.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
FRONTEND = Path(__file__).resolve().parents[1] / "frontend" / "src"
|
|
|
|
|
|
def test_something_actually_reads_the_version_endpoint():
|
|
"""The endpoint is not enough; something must ask it.
|
|
|
|
`/api/version` answered correctly for weeks with no caller — an endpoint
|
|
reachable only by someone who already knew to curl it. Rule 27: a
|
|
capability with no surface the operator can touch is not shipped.
|
|
"""
|
|
hits = [p for p in FRONTEND.rglob("*.ts") if "/api/version" in p.read_text()]
|
|
assert hits, "nothing under frontend/src fetches /api/version"
|
|
|
|
|
|
def test_the_footer_does_not_default_to_a_plausible_version():
|
|
"""The regression this readout was built to remove.
|
|
|
|
`appVersion` used to start life as the literal `"dev"` and the fetch
|
|
swallowed its own failure, so an instance that could not answer rendered
|
|
exactly what a healthy local build renders. Two very different states, one
|
|
string, and no way to tell them apart from the page.
|
|
|
|
Pinned as "the ref does not start at a version-shaped literal" rather than
|
|
as an exact initialiser, so a later refactor can change how the state is
|
|
held without failing here — what must not come back is the plausible
|
|
default.
|
|
"""
|
|
app = (FRONTEND / "App.vue").read_text()
|
|
match = re.search(r"const appVersion = ref[^;]*;", app)
|
|
assert match, "App.vue no longer declares appVersion — update this guard"
|
|
decl = match.group(0)
|
|
assert '"dev"' not in decl and "'dev'" not in decl, (
|
|
f"appVersion defaults to a version-shaped literal: {decl}\n"
|
|
"A failed fetch would render as a real-looking version (#3127 "
|
|
"checklist 12). Start from a not-answered-yet value instead."
|
|
)
|
|
|
|
|
|
def test_optional_version_fields_are_read_by_absence_not_falsiness():
|
|
"""`build` is a number and 0 is a legitimate ordering key.
|
|
|
|
The payload omits what it does not know rather than sending `""` or `0`, so
|
|
the renderer's job is to distinguish ABSENT from present. `||` cannot: it
|
|
would report a real `build` of 0 as unknown, and it is the form a person
|
|
reaches for by habit. `??` is the correct one, which is why this pins the
|
|
operator rather than the rendered output.
|
|
"""
|
|
view = (FRONTEND / "views" / "SettingsView.vue").read_text()
|
|
for field in ("channel", "build"):
|
|
assert f'versionInfo.{field} ?? "unknown"' in view, (
|
|
f"the {field} readout must use `?? \"unknown\"`, never `|| \"unknown\"` — "
|
|
"an absent field and a falsy one are different answers"
|
|
)
|
|
|
|
|
|
def test_the_version_request_carries_a_deadline():
|
|
"""Rule 156. A wait with no deadline cannot report that it failed.
|
|
|
|
This readout is consulted when an instance is misbehaving, which is exactly
|
|
when it may never answer. Without a deadline the surface sits on "still
|
|
loading" forever — the blank standing in for `unknown` again, arrived at
|
|
from the other direction.
|
|
"""
|
|
src = (FRONTEND / "api" / "version.ts").read_text()
|
|
assert "timeoutMs" in src, "the version fetch must pass a deadline"
|